Skip to content

Commit

Permalink
ETH: Add message signing
Browse files Browse the repository at this point in the history
  • Loading branch information
NickeZ committed Feb 10, 2020
1 parent 0d8787b commit bb7c748
Show file tree
Hide file tree
Showing 12 changed files with 484 additions and 15 deletions.
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 @@ -318,6 +319,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 @@ -366,7 +381,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

0 comments on commit bb7c748

Please sign in to comment.