Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ETH: Add message signing #321

Merged
merged 1 commit into from
Feb 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions messages/eth.options
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,6 @@ ETHSignRequest.gas_limit max_size:16;
ETHSignRequest.recipient fixed_length:true max_size:20
ETHSignRequest.value max_size:32
ETHSignRequest.data max_size:1024
ETHSignMessageRequest.msg max_size:1024
ETHSignMessageRequest.keypath max_count:10
ETHSignResponse.signature fixed_length:true max_size:65
7 changes: 7 additions & 0 deletions messages/eth.proto
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ message ETHSignRequest {
bytes data = 8;
}

message ETHSignMessageRequest {
ETHCoin coin = 1;
repeated uint32 keypath = 2;
bytes msg = 3;
}

message ETHSignResponse {
bytes signature = 1; // 65 bytes, last byte is the recid
}
Expand All @@ -53,6 +59,7 @@ message ETHRequest {
oneof request {
ETHPubRequest pub = 1;
ETHSignRequest sign = 2;
ETHSignMessageRequest sign_msg = 3;
}
}

Expand Down
10 changes: 10 additions & 0 deletions py/bitbox02/bitbox02/bitbox02/bitbox02.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,16 @@ def eth_sign(
)
return self._eth_msg_query(request, expected_response="sign").sign.signature

def eth_sign_msg(self, msg: bytes, keypath: List[int], coin: eth.ETHCoin = eth.ETH) -> bytes:
"""
Signs message, the msg will be prefixed with "\x19Ethereum message\n" + len(msg) in the
hardware
"""
request = eth.ETHRequest()
# pylint: disable=no-member
request.sign_msg.CopyFrom(eth.ETHSignMessageRequest(coin=coin, keypath=keypath, msg=msg))
return self._eth_msg_query(request, expected_response="sign").sign.signature

def reset(self) -> bool:
"""
Factory reset the device. Returns True on success.
Expand Down
83 changes: 74 additions & 9 deletions py/bitbox02/bitbox02/communication/generated/eth_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 29 additions & 5 deletions py/bitbox02/bitbox02/communication/generated/eth_pb2.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,26 @@ class ETHSignRequest(google___protobuf___message___Message):
else:
def ClearField(self, field_name: typing_extensions___Literal[u"coin",b"coin",u"data",b"data",u"gas_limit",b"gas_limit",u"gas_price",b"gas_price",u"keypath",b"keypath",u"nonce",b"nonce",u"recipient",b"recipient",u"value",b"value"]) -> None: ...

class ETHSignMessageRequest(google___protobuf___message___Message):
coin = ... # type: ETHCoin
keypath = ... # type: google___protobuf___internal___containers___RepeatedScalarFieldContainer[int]
msg = ... # type: bytes

def __init__(self,
*,
coin : typing___Optional[ETHCoin] = None,
keypath : typing___Optional[typing___Iterable[int]] = None,
msg : typing___Optional[bytes] = None,
) -> None: ...
@classmethod
def FromString(cls, s: bytes) -> ETHSignMessageRequest: ...
def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ...
def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ...
if sys.version_info >= (3,):
def ClearField(self, field_name: typing_extensions___Literal[u"coin",u"keypath",u"msg"]) -> None: ...
else:
def ClearField(self, field_name: typing_extensions___Literal[u"coin",b"coin",u"keypath",b"keypath",u"msg",b"msg"]) -> None: ...

class ETHSignResponse(google___protobuf___message___Message):
signature = ... # type: bytes

Expand All @@ -143,22 +163,26 @@ class ETHRequest(google___protobuf___message___Message):
@property
def sign(self) -> ETHSignRequest: ...

@property
def sign_msg(self) -> ETHSignMessageRequest: ...

def __init__(self,
*,
pub : typing___Optional[ETHPubRequest] = None,
sign : typing___Optional[ETHSignRequest] = None,
sign_msg : typing___Optional[ETHSignMessageRequest] = None,
) -> None: ...
@classmethod
def FromString(cls, s: bytes) -> ETHRequest: ...
def MergeFrom(self, other_msg: google___protobuf___message___Message) -> None: ...
def CopyFrom(self, other_msg: google___protobuf___message___Message) -> None: ...
if sys.version_info >= (3,):
def HasField(self, field_name: typing_extensions___Literal[u"pub",u"request",u"sign"]) -> bool: ...
def ClearField(self, field_name: typing_extensions___Literal[u"pub",u"request",u"sign"]) -> None: ...
def HasField(self, field_name: typing_extensions___Literal[u"pub",u"request",u"sign",u"sign_msg"]) -> bool: ...
def ClearField(self, field_name: typing_extensions___Literal[u"pub",u"request",u"sign",u"sign_msg"]) -> None: ...
else:
def HasField(self, field_name: typing_extensions___Literal[u"pub",b"pub",u"request",b"request",u"sign",b"sign"]) -> bool: ...
def ClearField(self, field_name: typing_extensions___Literal[u"pub",b"pub",u"request",b"request",u"sign",b"sign"]) -> None: ...
def WhichOneof(self, oneof_group: typing_extensions___Literal[u"request",b"request"]) -> typing_extensions___Literal["pub","sign"]: ...
def HasField(self, field_name: typing_extensions___Literal[u"pub",b"pub",u"request",b"request",u"sign",b"sign",u"sign_msg",b"sign_msg"]) -> bool: ...
def ClearField(self, field_name: typing_extensions___Literal[u"pub",b"pub",u"request",b"request",u"sign",b"sign",u"sign_msg",b"sign_msg"]) -> None: ...
def WhichOneof(self, oneof_group: typing_extensions___Literal[u"request",b"request"]) -> typing_extensions___Literal["pub","sign","sign_msg"]: ...

class ETHResponse(google___protobuf___message___Message):

Expand Down
18 changes: 17 additions & 1 deletion py/send_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from typing import List, Any, Optional, Callable, Union, Tuple, Sequence
import hashlib
import base64
import binascii

import hid
from tzlocal import get_localzone
Expand Down Expand Up @@ -325,6 +326,20 @@ def _sign_eth_tx(self) -> None:
# fmt: on
self._device.eth_sign(tx, keypath=[44 + HARDENED, 60 + HARDENED, 0 + HARDENED, 0, 0])

def _sign_eth_message(self) -> None:
msg = input("Message to sign: ")
if msg.startswith("0x"):
msg_bytes = binascii.unhexlify(msg[2:])
else:
msg_bytes = msg.encode("utf-8")
msg_hex = binascii.hexlify(msg_bytes).decode("utf-8")
print(f"signing\nbytes: {msg_bytes}\nhex: 0x{msg_hex}")
sig = self._device.eth_sign_msg(
msg=msg_bytes, keypath=[44 + HARDENED, 60 + HARDENED, 0 + HARDENED, 0, 0]
)

print("Signature: 0x{}".format(binascii.hexlify(sig).decode("utf-8")))

def _reset_device(self) -> None:
if self._device.reset():
print("Device RESET")
Expand Down Expand Up @@ -373,7 +388,8 @@ def _menu_init(self) -> None:
("Toggle BIP39 Mnemonic Passphrase", self._toggle_mnemonic_passphrase),
("Retrieve Ethereum xpub", self._get_eth_xpub),
("Retrieve Ethereum address", self._display_eth_address),
("Sign eth tx", self._sign_eth_tx),
("Sign Ethereum tx", self._sign_eth_tx),
("Sign Ethereum Message", self._sign_eth_message),
("Reset Device", self._reset_device),
)
choice = ask_user(choices)
Expand Down
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ set(ETHEREUM-SOURCES
${CMAKE_SOURCE_DIR}/src/apps/eth/eth_params.c
${CMAKE_SOURCE_DIR}/src/apps/eth/eth_sighash.c
${CMAKE_SOURCE_DIR}/src/apps/eth/eth_sign.c
${CMAKE_SOURCE_DIR}/src/apps/eth/eth_sign_msg.c
${CMAKE_SOURCE_DIR}/src/apps/eth/eth_verify.c
${CMAKE_SOURCE_DIR}/src/commander/commander_eth.c
)
Expand Down
Loading