diff --git a/common/protob/messages-binance.proto b/common/protob/messages-binance.proto index 60d677e3a..0483e548a 100644 --- a/common/protob/messages-binance.proto +++ b/common/protob/messages-binance.proto @@ -71,7 +71,6 @@ message BinanceTxRequest { /** * Request: Ask the device to include a Binance transfer msg in the tx. - * @next BinanceTxRequest * @next BinanceSignedTx * @next Failure */ @@ -92,7 +91,6 @@ message BinanceTransferMsg { /** * Request: Ask the device to include a Binance order msg in the tx. - * @next BinanceTxRequest * @next BinanceSignedTx * @next Failure */ @@ -129,7 +127,6 @@ message BinanceOrderMsg { /** * Request: Ask the device to include a Binance cancel msg in the tx. - * @next BinanceTxRequest * @next BinanceSignedTx * @next Failure */ @@ -146,6 +143,5 @@ message BinanceCancelMsg { message BinanceSignedTx { optional bytes signature = 1; optional bytes public_key = 2; - optional string json = 3; } diff --git a/core/docs/coins/README.md b/core/docs/coins/README.md index d8fd4bcfc..de6d6829a 100644 --- a/core/docs/coins/README.md +++ b/core/docs/coins/README.md @@ -21,6 +21,7 @@ algorithm, extended to work on other curves. | Ethereum | secp256k1 | `44'/c'/0'/0/a` | yes | [2](#Ethereum) | | Ripple | secp256k1 | `44'/144'/a'/0/0` | | [3](#Ripple) | | EOS | secp256k1 | `44'/194'/a'/0/0` | | [3](#Ripple) | +| Binance | secp256k1 | `44'/714'/a'/0/0` | | [3](#Ripple) | | Tron | secp256k1 | TODO | | TODO | | Ontology | nist256p1 | TODO | | TODO | | Cardano | ed25519 | `44'/1815'/a'/y/i` | yes | [4](#Cardano) | diff --git a/core/src/apps/binance/README.md b/core/src/apps/binance/README.md new file mode 100644 index 000000000..0ccac10cd --- /dev/null +++ b/core/src/apps/binance/README.md @@ -0,0 +1,25 @@ +# Binance + +MAINTAINER = Stanislav Marcinko + +AUTHOR = Stanislav Marcinko + +REVIEWER = Jan Matějek + +----- + +Implementation is based on [binance chain documentation](https://binance-chain.github.io/blockchain.html) and tested against fixtures from [binance javascript sdk](https://github.com/binance-chain/javascript-sdk/). Only a subset of messages needed for sdk integration is implemented. + +## Transactions + +Binance transaction consists of a transaction message wrapped in a standard transaction structure (see documentation). One message per transaction. + +Implemented subset of messages: +Place Order +Cancel Order +Transfer + +## Testing + +Mnemonic for recovering wallet used in binance sdk tests: +offer caution gift cross surge pretty orange during eye soldier popular holiday mention east eight office fashion ill parrot vault rent devote earth cousin \ No newline at end of file diff --git a/core/src/apps/binance/__init__.py b/core/src/apps/binance/__init__.py new file mode 100644 index 000000000..8f8a93962 --- /dev/null +++ b/core/src/apps/binance/__init__.py @@ -0,0 +1,13 @@ +from trezor import wire +from trezor.messages import MessageType + +from apps.common import HARDENED + +CURVE = "secp256k1" + + +def boot() -> None: + ns = [[CURVE, HARDENED | 44, HARDENED | 714]] + wire.add(MessageType.BinanceGetAddress, __name__, "get_address", ns) + wire.add(MessageType.BinanceGetPublicKey, __name__, "get_public_key", ns) + wire.add(MessageType.BinanceSignTx, __name__, "sign_tx", ns) diff --git a/core/src/apps/binance/get_address.py b/core/src/apps/binance/get_address.py new file mode 100644 index 000000000..9278f1bb2 --- /dev/null +++ b/core/src/apps/binance/get_address.py @@ -0,0 +1,27 @@ +from trezor.messages.BinanceAddress import BinanceAddress +from trezor.messages.BinanceGetAddress import BinanceGetAddress + +from apps.binance import CURVE, helpers +from apps.common import paths +from apps.common.layout import address_n_to_str, show_address, show_qr + + +async def get_address(ctx, msg: BinanceGetAddress, keychain): + HRP = "bnb" + + await paths.validate_path( + ctx, helpers.validate_full_path, keychain, msg.address_n, CURVE + ) + + node = keychain.derive(msg.address_n) + pubkey = node.public_key() + address = helpers.address_from_public_key(pubkey, HRP) + if msg.show_display: + desc = address_n_to_str(msg.address_n) + while True: + if await show_address(ctx, address, desc=desc): + break + if await show_qr(ctx, address, desc=desc): + break + + return BinanceAddress(address=address) diff --git a/core/src/apps/binance/get_public_key.py b/core/src/apps/binance/get_public_key.py new file mode 100644 index 000000000..bb201506b --- /dev/null +++ b/core/src/apps/binance/get_public_key.py @@ -0,0 +1,18 @@ +from trezor.messages.BinanceGetPublicKey import BinanceGetPublicKey +from trezor.messages.BinancePublicKey import BinancePublicKey + +from apps.binance import CURVE, helpers +from apps.common import layout, paths + + +async def get_public_key(ctx, msg: BinanceGetPublicKey, keychain): + await paths.validate_path( + ctx, helpers.validate_full_path, keychain, msg.address_n, CURVE + ) + node = keychain.derive(msg.address_n) + pubkey = node.public_key() + + if msg.show_display: + await layout.show_pubkey(ctx, pubkey) + + return BinancePublicKey(pubkey) diff --git a/core/src/apps/binance/helpers.py b/core/src/apps/binance/helpers.py new file mode 100644 index 000000000..0d3673f11 --- /dev/null +++ b/core/src/apps/binance/helpers.py @@ -0,0 +1,115 @@ +from micropython import const + +from trezor.crypto import bech32 +from trezor.crypto.scripts import sha256_ripemd160_digest +from trezor.messages.BinanceCancelMsg import BinanceCancelMsg +from trezor.messages.BinanceInputOutput import BinanceInputOutput +from trezor.messages.BinanceOrderMsg import BinanceOrderMsg +from trezor.messages.BinanceSignTx import BinanceSignTx +from trezor.messages.BinanceTransferMsg import BinanceTransferMsg + +from apps.common import HARDENED + +ENVELOPE_BLUEPRINT = '{{"account_number":"{account_number}","chain_id":"{chain_id}","data":null,"memo":"{memo}","msgs":[{msgs}],"sequence":"{sequence}","source":"{source}"}}' +MSG_TRANSFER_BLUEPRINT = '{{"inputs":[{inputs}],"outputs":[{outputs}]}}' +MSG_NEWORDER_BLUEPRINT = '{{"id":"{id}","ordertype":{ordertype},"price":{price},"quantity":{quantity},"sender":"{sender}","side":{side},"symbol":"{symbol}","timeinforce":{timeinforce}}}' +MSG_CANCEL_BLUEPRINT = '{{"refid":"{refid}","sender":"{sender}","symbol":"{symbol}"}}' +INPUT_OUTPUT_BLUEPRINT = '{{"address":"{address}","coins":[{coins}]}}' +COIN_BLUEPRINT = '{{"amount":"{amount}","denom":"{denom}"}}' + +# 1*10^18 Jagers equal 1 BNB https://www.binance.vision/glossary/jager +DIVISIBILITY = const(18) + + +def produce_json_for_signing(envelope: BinanceSignTx, msg) -> str: + if isinstance(msg, BinanceTransferMsg): + json_msg = produce_transfer_json(msg) + elif isinstance(msg, BinanceOrderMsg): + json_msg = produce_neworder_json(msg) + elif isinstance(msg, BinanceCancelMsg): + json_msg = produce_cancel_json(msg) + else: + raise ValueError("input message unrecognized, is of type " + type(msg).__name__) + + if envelope.source is None or envelope.source < 0: + raise ValueError("source missing or invalid") + + source = envelope.source + + return ENVELOPE_BLUEPRINT.format( + account_number=envelope.account_number, + chain_id=envelope.chain_id, + memo=envelope.memo, + msgs=json_msg, + sequence=envelope.sequence, + source=source, + ) + + +def produce_transfer_json(msg: BinanceTransferMsg) -> str: + def make_input_output(input_output: BinanceInputOutput): + coins = ",".join( + COIN_BLUEPRINT.format(amount=c.amount, denom=c.denom) + for c in input_output.coins + ) + return INPUT_OUTPUT_BLUEPRINT.format(address=input_output.address, coins=coins) + + inputs = ",".join(make_input_output(i) for i in msg.inputs) + outputs = ",".join(make_input_output(o) for o in msg.outputs) + + return MSG_TRANSFER_BLUEPRINT.format(inputs=inputs, outputs=outputs) + + +def produce_neworder_json(msg: BinanceOrderMsg) -> str: + return MSG_NEWORDER_BLUEPRINT.format( + id=msg.id, + ordertype=msg.ordertype, + price=msg.price, + quantity=msg.quantity, + sender=msg.sender, + side=msg.side, + symbol=msg.symbol, + timeinforce=msg.timeinforce, + ) + + +def produce_cancel_json(msg: BinanceCancelMsg) -> str: + return MSG_CANCEL_BLUEPRINT.format( + refid=msg.refid, sender=msg.sender, symbol=msg.symbol + ) + + +def address_from_public_key(pubkey: bytes, hrp: str) -> str: + """ + Address = RIPEMD160(SHA256(compressed public key)) + Address_Bech32 = HRP + '1' + bech32.encode(convert8BitsTo5Bits(RIPEMD160(SHA256(compressed public key)))) + HRP - bnb for productions, tbnb for tests + """ + + h = sha256_ripemd160_digest(pubkey) + + convertedbits = bech32.convertbits(h, 8, 5, False) + + return bech32.bech32_encode(hrp, convertedbits) + + +def validate_full_path(path: list) -> bool: + """ + Validates derivation path to equal 44'/714'/a'/0/0, + where `a` is an account index from 0 to 1 000 000. + Similar to Ethereum this should be 44'/714'/a', but for + compatibility with other HW vendors we use 44'/714'/a'/0/0. + """ + if len(path) != 5: + return False + if path[0] != 44 | HARDENED: + return False + if path[1] != 714 | HARDENED: + return False + if path[2] < HARDENED or path[2] > 1000000 | HARDENED: + return False + if path[3] != 0: + return False + if path[4] != 0: + return False + return True diff --git a/core/src/apps/binance/layout.py b/core/src/apps/binance/layout.py new file mode 100644 index 000000000..74af1acdb --- /dev/null +++ b/core/src/apps/binance/layout.py @@ -0,0 +1,71 @@ +from trezor import ui +from trezor.messages import ( + BinanceCancelMsg, + BinanceInputOutput, + BinanceOrderMsg, + BinanceOrderSide, + BinanceTransferMsg, + ButtonRequestType, +) +from trezor.ui.scroll import Paginated +from trezor.ui.text import Text +from trezor.utils import format_amount + +from . import helpers + +from apps.common.confirm import hold_to_confirm +from apps.common.layout import split_address + + +async def require_confirm_transfer(ctx, msg: BinanceTransferMsg): + def make_input_output_pages(msg: BinanceInputOutput, direction): + pages = [] + for coin in msg.coins: + coin_page = Text("Confirm " + direction, ui.ICON_SEND, icon_color=ui.GREEN) + coin_page.bold( + format_amount(coin.amount, helpers.DIVISIBILITY) + " " + coin.denom + ) + coin_page.normal("to") + coin_page.mono(*split_address(msg.address)) + pages.append(coin_page) + + return pages + + pages = [] + for txinput in msg.inputs: + pages.extend(make_input_output_pages(txinput, "input")) + + for txoutput in msg.outputs: + pages.extend(make_input_output_pages(txoutput, "output")) + + return await hold_to_confirm(ctx, Paginated(pages), ButtonRequestType.ConfirmOutput) + + +async def require_confirm_cancel(ctx, msg: BinanceCancelMsg): + text = Text("Confirm cancel", ui.ICON_SEND, icon_color=ui.GREEN) + text.normal("Reference id:") + text.bold(msg.refid) + return await hold_to_confirm(ctx, text, ButtonRequestType.SignTx) + + +async def require_confirm_order(ctx, msg: BinanceOrderMsg): + page1 = Text("Confirm order", ui.ICON_SEND, icon_color=ui.GREEN) + page1.normal("Sender address:") + page1.bold(msg.sender) + + page2 = Text("Confirm order", ui.ICON_SEND, icon_color=ui.GREEN) + page2.normal("side:") + if msg.side == BinanceOrderSide.BUY: + page2.bold("buy") + elif msg.side == BinanceOrderSide.SELL: + page2.bold("sell") + + page3 = Text("Confirm order", ui.ICON_SEND, icon_color=ui.GREEN) + page3.normal("Quantity:") + page3.bold(str(msg.quantity)) + page3.normal("Price:") + page3.bold(str(msg.price)) + + return await hold_to_confirm( + ctx, Paginated([page1, page2, page3]), ButtonRequestType.SignTx + ) diff --git a/core/src/apps/binance/sign_tx.py b/core/src/apps/binance/sign_tx.py new file mode 100644 index 000000000..94d6eea40 --- /dev/null +++ b/core/src/apps/binance/sign_tx.py @@ -0,0 +1,51 @@ +from trezor.crypto.curve import secp256k1 +from trezor.crypto.hashlib import sha256 +from trezor.messages import MessageType +from trezor.messages.BinanceCancelMsg import BinanceCancelMsg +from trezor.messages.BinanceOrderMsg import BinanceOrderMsg +from trezor.messages.BinanceSignedTx import BinanceSignedTx +from trezor.messages.BinanceTransferMsg import BinanceTransferMsg +from trezor.messages.BinanceTxRequest import BinanceTxRequest + +from apps.binance import CURVE, helpers, layout +from apps.common import paths + + +async def sign_tx(ctx, envelope, keychain): + # create transaction message -> sign it -> create signature/pubkey message -> serialize all + if envelope.msg_count > 1: + raise ValueError("Multiple messages not supported") + await paths.validate_path( + ctx, helpers.validate_full_path, keychain, envelope.address_n, CURVE + ) + + node = keychain.derive(envelope.address_n) + + tx_req = BinanceTxRequest() + + msg = await ctx.call_any( + tx_req, + MessageType.BinanceCancelMsg, + MessageType.BinanceOrderMsg, + MessageType.BinanceTransferMsg, + ) + + msg_json = helpers.produce_json_for_signing(envelope, msg) + + if isinstance(msg, BinanceTransferMsg): + await layout.require_confirm_transfer(ctx, msg) + elif isinstance(msg, BinanceOrderMsg): + await layout.require_confirm_order(ctx, msg) + elif isinstance(msg, BinanceCancelMsg): + await layout.require_confirm_cancel(ctx, msg) + else: + raise ValueError("input message unrecognized, is of type " + type(msg).__name__) + + signature_bytes = generate_content_signature(msg_json.encode(), node.private_key()) + + return BinanceSignedTx(signature=signature_bytes, public_key=node.public_key()) + + +def generate_content_signature(json: bytes, private_key: bytes) -> bytes: + msghash = sha256(json).digest() + return secp256k1.sign(private_key, msghash)[1:65] diff --git a/core/src/main.py b/core/src/main.py index 80141b7bc..7f1b30b90 100644 --- a/core/src/main.py +++ b/core/src/main.py @@ -41,6 +41,7 @@ def _boot_default(): import apps.cardano import apps.tezos import apps.eos + import apps.binance if __debug__: import apps.debug @@ -60,6 +61,7 @@ def _boot_default(): apps.cardano.boot() apps.tezos.boot() apps.eos.boot() + apps.binance.boot() if __debug__: apps.debug.boot() else: diff --git a/core/src/trezor/messages/BinanceAddress.py b/core/src/trezor/messages/BinanceAddress.py new file mode 100644 index 000000000..90f8d8421 --- /dev/null +++ b/core/src/trezor/messages/BinanceAddress.py @@ -0,0 +1,25 @@ +# Automatically generated by pb2py +# fmt: off +import protobuf as p + +if __debug__: + try: + from typing import Dict, List, Optional + except ImportError: + Dict, List, Optional = None, None, None # type: ignore + + +class BinanceAddress(p.MessageType): + MESSAGE_WIRE_TYPE = 701 + + def __init__( + self, + address: str = None, + ) -> None: + self.address = address + + @classmethod + def get_fields(cls) -> Dict: + return { + 1: ('address', p.UnicodeType, 0), + } diff --git a/core/src/trezor/messages/BinanceCancelMsg.py b/core/src/trezor/messages/BinanceCancelMsg.py new file mode 100644 index 000000000..338103a5a --- /dev/null +++ b/core/src/trezor/messages/BinanceCancelMsg.py @@ -0,0 +1,31 @@ +# Automatically generated by pb2py +# fmt: off +import protobuf as p + +if __debug__: + try: + from typing import Dict, List, Optional + except ImportError: + Dict, List, Optional = None, None, None # type: ignore + + +class BinanceCancelMsg(p.MessageType): + MESSAGE_WIRE_TYPE = 708 + + def __init__( + self, + refid: str = None, + sender: str = None, + symbol: str = None, + ) -> None: + self.refid = refid + self.sender = sender + self.symbol = symbol + + @classmethod + def get_fields(cls) -> Dict: + return { + 1: ('refid', p.UnicodeType, 0), + 2: ('sender', p.UnicodeType, 0), + 3: ('symbol', p.UnicodeType, 0), + } diff --git a/core/src/trezor/messages/BinanceCoin.py b/core/src/trezor/messages/BinanceCoin.py new file mode 100644 index 000000000..ffa6f019e --- /dev/null +++ b/core/src/trezor/messages/BinanceCoin.py @@ -0,0 +1,27 @@ +# Automatically generated by pb2py +# fmt: off +import protobuf as p + +if __debug__: + try: + from typing import Dict, List, Optional + except ImportError: + Dict, List, Optional = None, None, None # type: ignore + + +class BinanceCoin(p.MessageType): + + def __init__( + self, + amount: int = None, + denom: str = None, + ) -> None: + self.amount = amount + self.denom = denom + + @classmethod + def get_fields(cls) -> Dict: + return { + 1: ('amount', p.SVarintType, 0), + 2: ('denom', p.UnicodeType, 0), + } diff --git a/core/src/trezor/messages/BinanceGetAddress.py b/core/src/trezor/messages/BinanceGetAddress.py new file mode 100644 index 000000000..15292de2a --- /dev/null +++ b/core/src/trezor/messages/BinanceGetAddress.py @@ -0,0 +1,28 @@ +# Automatically generated by pb2py +# fmt: off +import protobuf as p + +if __debug__: + try: + from typing import Dict, List, Optional + except ImportError: + Dict, List, Optional = None, None, None # type: ignore + + +class BinanceGetAddress(p.MessageType): + MESSAGE_WIRE_TYPE = 700 + + def __init__( + self, + address_n: List[int] = None, + show_display: bool = None, + ) -> None: + self.address_n = address_n if address_n is not None else [] + self.show_display = show_display + + @classmethod + def get_fields(cls) -> Dict: + return { + 1: ('address_n', p.UVarintType, p.FLAG_REPEATED), + 2: ('show_display', p.BoolType, 0), + } diff --git a/core/src/trezor/messages/BinanceGetPublicKey.py b/core/src/trezor/messages/BinanceGetPublicKey.py new file mode 100644 index 000000000..1b5a9e505 --- /dev/null +++ b/core/src/trezor/messages/BinanceGetPublicKey.py @@ -0,0 +1,28 @@ +# Automatically generated by pb2py +# fmt: off +import protobuf as p + +if __debug__: + try: + from typing import Dict, List, Optional + except ImportError: + Dict, List, Optional = None, None, None # type: ignore + + +class BinanceGetPublicKey(p.MessageType): + MESSAGE_WIRE_TYPE = 702 + + def __init__( + self, + address_n: List[int] = None, + show_display: bool = None, + ) -> None: + self.address_n = address_n if address_n is not None else [] + self.show_display = show_display + + @classmethod + def get_fields(cls) -> Dict: + return { + 1: ('address_n', p.UVarintType, p.FLAG_REPEATED), + 2: ('show_display', p.BoolType, 0), + } diff --git a/core/src/trezor/messages/BinanceInputOutput.py b/core/src/trezor/messages/BinanceInputOutput.py new file mode 100644 index 000000000..3e212eb1a --- /dev/null +++ b/core/src/trezor/messages/BinanceInputOutput.py @@ -0,0 +1,29 @@ +# Automatically generated by pb2py +# fmt: off +import protobuf as p + +from .BinanceCoin import BinanceCoin + +if __debug__: + try: + from typing import Dict, List, Optional + except ImportError: + Dict, List, Optional = None, None, None # type: ignore + + +class BinanceInputOutput(p.MessageType): + + def __init__( + self, + address: str = None, + coins: List[BinanceCoin] = None, + ) -> None: + self.address = address + self.coins = coins if coins is not None else [] + + @classmethod + def get_fields(cls) -> Dict: + return { + 1: ('address', p.UnicodeType, 0), + 2: ('coins', BinanceCoin, p.FLAG_REPEATED), + } diff --git a/core/src/trezor/messages/BinanceOrderMsg.py b/core/src/trezor/messages/BinanceOrderMsg.py new file mode 100644 index 000000000..30e8c775d --- /dev/null +++ b/core/src/trezor/messages/BinanceOrderMsg.py @@ -0,0 +1,46 @@ +# Automatically generated by pb2py +# fmt: off +import protobuf as p + +if __debug__: + try: + from typing import Dict, List, Optional + except ImportError: + Dict, List, Optional = None, None, None # type: ignore + + +class BinanceOrderMsg(p.MessageType): + MESSAGE_WIRE_TYPE = 707 + + def __init__( + self, + id: str = None, + ordertype: int = None, + price: int = None, + quantity: int = None, + sender: str = None, + side: int = None, + symbol: str = None, + timeinforce: int = None, + ) -> None: + self.id = id + self.ordertype = ordertype + self.price = price + self.quantity = quantity + self.sender = sender + self.side = side + self.symbol = symbol + self.timeinforce = timeinforce + + @classmethod + def get_fields(cls) -> Dict: + return { + 1: ('id', p.UnicodeType, 0), + 2: ('ordertype', p.UVarintType, 0), + 3: ('price', p.SVarintType, 0), + 4: ('quantity', p.SVarintType, 0), + 5: ('sender', p.UnicodeType, 0), + 6: ('side', p.UVarintType, 0), + 7: ('symbol', p.UnicodeType, 0), + 8: ('timeinforce', p.UVarintType, 0), + } diff --git a/core/src/trezor/messages/BinanceOrderSide.py b/core/src/trezor/messages/BinanceOrderSide.py new file mode 100644 index 000000000..454fff0fe --- /dev/null +++ b/core/src/trezor/messages/BinanceOrderSide.py @@ -0,0 +1,5 @@ +# Automatically generated by pb2py +# fmt: off +SIDE_UNKNOWN = 0 +BUY = 1 +SELL = 2 diff --git a/core/src/trezor/messages/BinanceOrderType.py b/core/src/trezor/messages/BinanceOrderType.py new file mode 100644 index 000000000..3349a5fdb --- /dev/null +++ b/core/src/trezor/messages/BinanceOrderType.py @@ -0,0 +1,6 @@ +# Automatically generated by pb2py +# fmt: off +OT_UNKNOWN = 0 +MARKET = 1 +LIMIT = 2 +OT_RESERVED = 3 diff --git a/core/src/trezor/messages/BinancePublicKey.py b/core/src/trezor/messages/BinancePublicKey.py new file mode 100644 index 000000000..468ae6344 --- /dev/null +++ b/core/src/trezor/messages/BinancePublicKey.py @@ -0,0 +1,25 @@ +# Automatically generated by pb2py +# fmt: off +import protobuf as p + +if __debug__: + try: + from typing import Dict, List, Optional + except ImportError: + Dict, List, Optional = None, None, None # type: ignore + + +class BinancePublicKey(p.MessageType): + MESSAGE_WIRE_TYPE = 703 + + def __init__( + self, + public_key: bytes = None, + ) -> None: + self.public_key = public_key + + @classmethod + def get_fields(cls) -> Dict: + return { + 1: ('public_key', p.BytesType, 0), + } diff --git a/core/src/trezor/messages/BinanceSignTx.py b/core/src/trezor/messages/BinanceSignTx.py new file mode 100644 index 000000000..0c117b95f --- /dev/null +++ b/core/src/trezor/messages/BinanceSignTx.py @@ -0,0 +1,43 @@ +# Automatically generated by pb2py +# fmt: off +import protobuf as p + +if __debug__: + try: + from typing import Dict, List, Optional + except ImportError: + Dict, List, Optional = None, None, None # type: ignore + + +class BinanceSignTx(p.MessageType): + MESSAGE_WIRE_TYPE = 704 + + def __init__( + self, + address_n: List[int] = None, + msg_count: int = None, + account_number: int = None, + chain_id: str = None, + memo: str = None, + sequence: int = None, + source: int = None, + ) -> None: + self.address_n = address_n if address_n is not None else [] + self.msg_count = msg_count + self.account_number = account_number + self.chain_id = chain_id + self.memo = memo + self.sequence = sequence + self.source = source + + @classmethod + def get_fields(cls) -> Dict: + return { + 1: ('address_n', p.UVarintType, p.FLAG_REPEATED), + 2: ('msg_count', p.UVarintType, 0), + 3: ('account_number', p.SVarintType, 0), + 4: ('chain_id', p.UnicodeType, 0), + 5: ('memo', p.UnicodeType, 0), + 6: ('sequence', p.SVarintType, 0), + 7: ('source', p.SVarintType, 0), + } diff --git a/core/src/trezor/messages/BinanceSignedTx.py b/core/src/trezor/messages/BinanceSignedTx.py new file mode 100644 index 000000000..a861e5b78 --- /dev/null +++ b/core/src/trezor/messages/BinanceSignedTx.py @@ -0,0 +1,28 @@ +# Automatically generated by pb2py +# fmt: off +import protobuf as p + +if __debug__: + try: + from typing import Dict, List, Optional + except ImportError: + Dict, List, Optional = None, None, None # type: ignore + + +class BinanceSignedTx(p.MessageType): + MESSAGE_WIRE_TYPE = 709 + + def __init__( + self, + signature: bytes = None, + public_key: bytes = None, + ) -> None: + self.signature = signature + self.public_key = public_key + + @classmethod + def get_fields(cls) -> Dict: + return { + 1: ('signature', p.BytesType, 0), + 2: ('public_key', p.BytesType, 0), + } diff --git a/core/src/trezor/messages/BinanceTimeInForce.py b/core/src/trezor/messages/BinanceTimeInForce.py new file mode 100644 index 000000000..fa6e5f420 --- /dev/null +++ b/core/src/trezor/messages/BinanceTimeInForce.py @@ -0,0 +1,6 @@ +# Automatically generated by pb2py +# fmt: off +TIF_UNKNOWN = 0 +GTE = 1 +TIF_RESERVED = 2 +IOC = 3 diff --git a/core/src/trezor/messages/BinanceTransferMsg.py b/core/src/trezor/messages/BinanceTransferMsg.py new file mode 100644 index 000000000..37b76b21c --- /dev/null +++ b/core/src/trezor/messages/BinanceTransferMsg.py @@ -0,0 +1,30 @@ +# Automatically generated by pb2py +# fmt: off +import protobuf as p + +from .BinanceInputOutput import BinanceInputOutput + +if __debug__: + try: + from typing import Dict, List, Optional + except ImportError: + Dict, List, Optional = None, None, None # type: ignore + + +class BinanceTransferMsg(p.MessageType): + MESSAGE_WIRE_TYPE = 706 + + def __init__( + self, + inputs: List[BinanceInputOutput] = None, + outputs: List[BinanceInputOutput] = None, + ) -> None: + self.inputs = inputs if inputs is not None else [] + self.outputs = outputs if outputs is not None else [] + + @classmethod + def get_fields(cls) -> Dict: + return { + 1: ('inputs', BinanceInputOutput, p.FLAG_REPEATED), + 2: ('outputs', BinanceInputOutput, p.FLAG_REPEATED), + } diff --git a/core/src/trezor/messages/BinanceTxRequest.py b/core/src/trezor/messages/BinanceTxRequest.py new file mode 100644 index 000000000..394ae5721 --- /dev/null +++ b/core/src/trezor/messages/BinanceTxRequest.py @@ -0,0 +1,13 @@ +# Automatically generated by pb2py +# fmt: off +import protobuf as p + +if __debug__: + try: + from typing import Dict, List, Optional + except ImportError: + Dict, List, Optional = None, None, None # type: ignore + + +class BinanceTxRequest(p.MessageType): + MESSAGE_WIRE_TYPE = 705 diff --git a/core/tests/test_apps.binance.address.py b/core/tests/test_apps.binance.address.py new file mode 100644 index 000000000..940df6081 --- /dev/null +++ b/core/tests/test_apps.binance.address.py @@ -0,0 +1,49 @@ +from common import * +from apps.common.paths import HARDENED +from apps.binance.helpers import address_from_public_key, validate_full_path + +from trezor.crypto.curve import secp256k1 +from ubinascii import unhexlify + +class TestBinanceAddress(unittest.TestCase): + def test_privkey_to_address(self): + #source of test data - binance javascript SDK + privkey = "90335b9d2153ad1a9799a3ccc070bd64b4164e9642ee1dd48053c33f9a3a05e9" + expected_address = "tbnb1hgm0p7khfk85zpz5v0j8wnej3a90w709zzlffd" + + pubkey = secp256k1.publickey(unhexlify(privkey), True) + address = address_from_public_key(pubkey, "tbnb") + + self.assertEqual(address, expected_address) + + + def test_paths(self): + # 44'/714'/a'/0/0 is correct + incorrect_paths = [ + [44 | HARDENED], + [44 | HARDENED, 714 | HARDENED], + [44 | HARDENED, 714 | HARDENED, 0], + [44 | HARDENED, 714 | HARDENED, 0 | HARDENED, 0 | HARDENED], + [44 | HARDENED, 714 | HARDENED, 0 | HARDENED, 0 | HARDENED, 0 | HARDENED], + [44 | HARDENED, 714 | HARDENED, 0 | HARDENED, 1, 0], + [44 | HARDENED, 714 | HARDENED, 0 | HARDENED, 0, 5], + [44 | HARDENED, 714 | HARDENED, 9999 | HARDENED], + [44 | HARDENED, 714 | HARDENED, 9999000 | HARDENED, 0, 0], + [44 | HARDENED, 60 | HARDENED, 0 | HARDENED, 0, 0], + [1 | HARDENED, 1 | HARDENED, 1 | HARDENED], + ] + correct_paths = [ + [44 | HARDENED, 714 | HARDENED, 0 | HARDENED, 0, 0], + [44 | HARDENED, 714 | HARDENED, 3 | HARDENED, 0, 0], + [44 | HARDENED, 714 | HARDENED, 9 | HARDENED, 0, 0], + ] + + for path in incorrect_paths: + self.assertFalse(validate_full_path(path)) + + for path in correct_paths: + self.assertTrue(validate_full_path(path)) + + +if __name__ == '__main__': + unittest.main() diff --git a/core/tests/test_apps.binance.sign_tx.py b/core/tests/test_apps.binance.sign_tx.py new file mode 100644 index 000000000..c84f3364a --- /dev/null +++ b/core/tests/test_apps.binance.sign_tx.py @@ -0,0 +1,108 @@ +from common import * + +from apps.binance.helpers import produce_json_for_signing +from apps.binance.sign_tx import generate_content_signature, sign_tx + +from trezor.crypto.curve import secp256k1 +from trezor.crypto.hashlib import sha256 +from trezor.messages.BinanceCancelMsg import BinanceCancelMsg +from trezor.messages.BinanceCoin import BinanceCoin +from trezor.messages.BinanceInputOutput import BinanceInputOutput +from trezor.messages.BinanceOrderMsg import BinanceOrderMsg +from trezor.messages.BinanceSignTx import BinanceSignTx +from trezor.messages.BinanceTransferMsg import BinanceTransferMsg + + +class TestBinanceSign(unittest.TestCase): + def test_order_signature(self): + # source of testing data + # https://github.com/binance-chain/javascript-sdk/blob/master/__tests__/fixtures/placeOrder.json + json_hex_msg = "7b226163636f756e745f6e756d626572223a223334222c22636861696e5f6964223a2242696e616e63652d436861696e2d4e696c65222c2264617461223a6e756c6c2c226d656d6f223a22222c226d736773223a5b7b226964223a22424133364630464144373444384634313034353436334534373734463332384634414637373945352d3333222c226f7264657274797065223a322c227072696365223a3130303030303030302c227175616e74697479223a3130303030303030302c2273656e646572223a2274626e623168676d3070376b68666b38357a707a3576306a38776e656a33613930773730397a7a6c666664222c2273696465223a312c2273796d626f6c223a224144412e422d4236335f424e42222c2274696d65696e666f726365223a317d5d2c2273657175656e6365223a223332222c22736f75726365223a2231227d" + expected_signature = "851fc9542342321af63ecbba7d3ece545f2a42bad01ba32cff5535b18e54b6d3106e10b6a4525993d185a1443d9a125186960e028eabfdd8d76cf70a3a7e3100" + public_key = "029729a52e4e3c2b4a4e52aa74033eedaf8ba1df5ab6d1f518fd69e67bbd309b0e" + private_key = "90335b9d2153ad1a9799a3ccc070bd64b4164e9642ee1dd48053c33f9a3a05e9" + + # Testing data for object creation is decoded from json_hex_msg + envelope = BinanceSignTx(msg_count=1, account_number=34, chain_id="Binance-Chain-Nile", memo="", sequence=32, source=1) + msg = BinanceOrderMsg(id="BA36F0FAD74D8F41045463E4774F328F4AF779E5-33", + ordertype=2, + price=100000000, + quantity=100000000, + sender="tbnb1hgm0p7khfk85zpz5v0j8wnej3a90w709zzlffd", + side=1, + symbol="ADA.B-B63_BNB", + timeinforce=1) + + msg_json = produce_json_for_signing(envelope, msg) + + #check if our json string produced for signing is the same as test vector + self.assertEqual(hexlify(msg_json).decode(), json_hex_msg) + + #verify signature against public key + signature = generate_content_signature(msg_json.encode(), unhexlify(private_key)) + self.assertTrue(verify_content_signature(unhexlify(public_key), signature, unhexlify(json_hex_msg))) + + #check if the signed data is the same as test vector + self.assertEqual(signature, unhexlify(expected_signature)) + + def test_cancel_signature(self): + # source of testing data + # https://github.com/binance-chain/javascript-sdk/blob/master/__tests__/fixtures/cancelOrder.json + json_hex_msg = "7b226163636f756e745f6e756d626572223a223334222c22636861696e5f6964223a2242696e616e63652d436861696e2d4e696c65222c2264617461223a6e756c6c2c226d656d6f223a22222c226d736773223a5b7b227265666964223a22424133364630464144373444384634313034353436334534373734463332384634414637373945352d3239222c2273656e646572223a2274626e623168676d3070376b68666b38357a707a3576306a38776e656a33613930773730397a7a6c666664222c2273796d626f6c223a2242434853562e422d3130465f424e42227d5d2c2273657175656e6365223a223333222c22736f75726365223a2231227d" + expected_signature = "d93fb0402b2b30e7ea08e123bb139ad68bf0a1577f38592eb22d11e127f09bbd3380f29b4bf15bdfa973454c5c8ed444f2e256e956fe98cfd21e886a946e21e5" + public_key = "029729a52e4e3c2b4a4e52aa74033eedaf8ba1df5ab6d1f518fd69e67bbd309b0e" + private_key = "90335b9d2153ad1a9799a3ccc070bd64b4164e9642ee1dd48053c33f9a3a05e9" + + # Testing data for object creation is decoded from json_hex_msg + envelope = BinanceSignTx(msg_count=1, account_number=34, chain_id="Binance-Chain-Nile", memo="", sequence=33, source=1) + msg = BinanceCancelMsg(refid="BA36F0FAD74D8F41045463E4774F328F4AF779E5-29", + sender="tbnb1hgm0p7khfk85zpz5v0j8wnej3a90w709zzlffd", + symbol="BCHSV.B-10F_BNB") + + msg_json = produce_json_for_signing(envelope, msg) + + #check if our json string produced for signing is the same as test vector + self.assertEqual(hexlify(msg_json).decode(), json_hex_msg) + + #verify signature against public key + signature = generate_content_signature(msg_json.encode(), unhexlify(private_key)) + self.assertTrue(verify_content_signature(unhexlify(public_key), signature, unhexlify(json_hex_msg))) + + #check if the signed data is the same as test vector + self.assertEqual(signature, unhexlify(expected_signature)) + + def test_transfer_signature(self): + # source of testing data + # https://github.com/binance-chain/javascript-sdk/blob/master/__tests__/fixtures/transfer.json + json_hex_msg = "7b226163636f756e745f6e756d626572223a223334222c22636861696e5f6964223a2242696e616e63652d436861696e2d4e696c65222c2264617461223a6e756c6c2c226d656d6f223a2274657374222c226d736773223a5b7b22696e70757473223a5b7b2261646472657373223a2274626e623168676d3070376b68666b38357a707a3576306a38776e656a33613930773730397a7a6c666664222c22636f696e73223a5b7b22616d6f756e74223a2231303030303030303030222c2264656e6f6d223a22424e42227d5d7d5d2c226f757470757473223a5b7b2261646472657373223a2274626e6231737335376538736137786e77713033306b3263747237373575616339676a7a676c7168767079222c22636f696e73223a5b7b22616d6f756e74223a2231303030303030303030222c2264656e6f6d223a22424e42227d5d7d5d7d5d2c2273657175656e6365223a223331222c22736f75726365223a2231227d" + expected_signature = "97b4c2e41b0d0f61ddcf4020fff0ecb227d6df69b3dd7e657b34be0e32b956e22d0c6be5832d25353ae24af0bb223d4a5337320518c4e7708b84c8e05eb6356b" + public_key = "029729a52e4e3c2b4a4e52aa74033eedaf8ba1df5ab6d1f518fd69e67bbd309b0e" + private_key = "90335b9d2153ad1a9799a3ccc070bd64b4164e9642ee1dd48053c33f9a3a05e9" + + # Testing data for object creation is decoded from json_hex_msg + envelope = BinanceSignTx(msg_count=1, account_number=34, chain_id="Binance-Chain-Nile", memo="test", sequence=31, source=1) + coin = BinanceCoin(denom="BNB", amount=1000000000) + first_input = BinanceInputOutput(address="tbnb1hgm0p7khfk85zpz5v0j8wnej3a90w709zzlffd", coins=[coin]) + first_output = BinanceInputOutput(address="tbnb1ss57e8sa7xnwq030k2ctr775uac9gjzglqhvpy", coins=[coin]) + msg = BinanceTransferMsg(inputs=[first_input], outputs=[first_output]) + + msg_json = produce_json_for_signing(envelope, msg) + + #check if our json string produced for signing is the same as test vector + self.assertEqual(hexlify(msg_json).decode(), json_hex_msg) + + #verify signature against public key + signature = generate_content_signature(msg_json.encode(), unhexlify(private_key)) + self.assertTrue(verify_content_signature(unhexlify(public_key), signature, unhexlify(json_hex_msg))) + + #check if the signed data is the same as test vector + self.assertEqual(signature, unhexlify(expected_signature)) + +def verify_content_signature( + public_key: bytes, signature: bytes, unsigned_data: bytes +) -> bool: + msghash = sha256(unsigned_data).digest() + return secp256k1.verify(public_key, signature, msghash) + +if __name__ == '__main__': + unittest.main() diff --git a/python/trezorctl b/python/trezorctl index fc02de1cc..f1cd4b5b1 100755 --- a/python/trezorctl +++ b/python/trezorctl @@ -31,6 +31,7 @@ import click import requests from trezorlib import ( + binance, btc, cardano, coins, @@ -1841,6 +1842,56 @@ def tezos_sign_tx(connect, address, file): return tezos.sign_tx(client, address_n, msg) +# +# Binance functions +# + + +@cli.command(help="Get Binance address for specified path.") +@click.option( + "-n", "--address", required=True, help="BIP-32 path to key, e.g. m/44'/714'/0'/0/0" +) +@click.option("-d", "--show-display", is_flag=True) +@click.pass_obj +def binance_get_address(connect, address, show_display): + client = connect() + address_n = tools.parse_path(address) + + return binance.get_address(client, address_n, show_display) + + +@cli.command(help="Get Binance public key.") +@click.option( + "-n", "--address", required=True, help="BIP-32 path to key, e.g. m/44'/714'/0'/0/0" +) +@click.option("-d", "--show-display", is_flag=True) +@click.pass_obj +def binance_get_public_key(connect, address, show_display): + client = connect() + address_n = tools.parse_path(address) + + return binance.get_public_key(client, address_n, show_display).hex() + + +@cli.command(help="Sign Binance transaction") +@click.option( + "-n", "--address", required=True, help="BIP-32 path to key, e.g. m/44'/714'/0'/0/0" +) +@click.option( + "-f", + "--file", + type=click.File("r"), + required=True, + help="Transaction in JSON format", +) +@click.pass_obj +def binance_sign_tx(connect, address, file): + client = connect() + address_n = tools.parse_path(address) + + return binance.sign_tx(client, address_n, json.load(file)) + + # # Main # diff --git a/python/trezorlib/binance.py b/python/trezorlib/binance.py new file mode 100644 index 000000000..acb410665 --- /dev/null +++ b/python/trezorlib/binance.py @@ -0,0 +1,52 @@ +from . import messages +from .protobuf import dict_to_proto +from .tools import expect, session + + +@expect(messages.BinanceAddress, field="address") +def get_address(client, address_n, show_display=False): + return client.call( + messages.BinanceGetAddress(address_n=address_n, show_display=show_display) + ) + + +@expect(messages.BinancePublicKey, field="public_key") +def get_public_key(client, address_n, show_display=False): + return client.call( + messages.BinanceGetPublicKey(address_n=address_n, show_display=show_display) + ) + + +@session +def sign_tx(client, address_n, tx_json): + msg = tx_json["msgs"][0] + envelope = dict_to_proto(messages.BinanceSignTx, tx_json) + envelope.msg_count = 1 + envelope.address_n = address_n + + response = client.call(envelope) + + if not isinstance(response, messages.BinanceTxRequest): + raise RuntimeError( + "Invalid response, expected BinanceTxRequest, received " + + type(response).__name__ + ) + + if "refid" in msg: + msg = dict_to_proto(messages.BinanceCancelMsg, msg) + elif "inputs" in msg: + msg = dict_to_proto(messages.BinanceTransferMsg, msg) + elif "ordertype" in msg: + msg = dict_to_proto(messages.BinanceOrderMsg, msg) + else: + raise ValueError("can not determine msg type") + + response = client.call(msg) + + if not isinstance(response, messages.BinanceSignedTx): + raise RuntimeError( + "Invalid response, expected BinanceSignedTx, received " + + type(response).__name__ + ) + + return response diff --git a/python/trezorlib/messages/BinanceSignedTx.py b/python/trezorlib/messages/BinanceSignedTx.py index 9884ce8c0..ef7cc918b 100644 --- a/python/trezorlib/messages/BinanceSignedTx.py +++ b/python/trezorlib/messages/BinanceSignedTx.py @@ -16,16 +16,13 @@ class BinanceSignedTx(p.MessageType): self, signature: bytes = None, public_key: bytes = None, - json: str = None, ) -> None: self.signature = signature self.public_key = public_key - self.json = json @classmethod def get_fields(cls) -> Dict: return { 1: ('signature', p.BytesType, 0), 2: ('public_key', p.BytesType, 0), - 3: ('json', p.UnicodeType, 0), } diff --git a/python/trezorlib/tests/device_tests/REGISTERED_MARKERS b/python/trezorlib/tests/device_tests/REGISTERED_MARKERS index 41ece6798..c61862dbb 100644 --- a/python/trezorlib/tests/device_tests/REGISTERED_MARKERS +++ b/python/trezorlib/tests/device_tests/REGISTERED_MARKERS @@ -1,3 +1,4 @@ +binance capricoin cardano decred diff --git a/python/trezorlib/tests/device_tests/test_msg_binance_get_address.py b/python/trezorlib/tests/device_tests/test_msg_binance_get_address.py new file mode 100644 index 000000000..57ee5cddb --- /dev/null +++ b/python/trezorlib/tests/device_tests/test_msg_binance_get_address.py @@ -0,0 +1,24 @@ +import pytest + +from trezorlib.binance import get_address +from trezorlib.tools import parse_path + +from .conftest import setup_client + +BINANCE_ADDRESS_TEST_VECTORS = [ + ("m/44'/714'/0'/0/0", "bnb1hgm0p7khfk85zpz5v0j8wnej3a90w709vhkdfu"), + ("m/44'/714'/0'/0/1", "bnb1egswqkszzfc2uq78zjslc6u2uky4pw46x4rstd"), +] + + +@pytest.mark.binance +@pytest.mark.skip_t1 # T1 support is not planned +@setup_client( + mnemonic="offer caution gift cross surge pretty orange during eye soldier popular holiday mention east eight office fashion ill parrot vault rent devote earth cousin" +) +@pytest.mark.parametrize("path, expected_address", BINANCE_ADDRESS_TEST_VECTORS) +def test_binance_get_address(client, path, expected_address): + # data from https://github.com/binance-chain/javascript-sdk/blob/master/__tests__/crypto.test.js#L50 + + address = get_address(client, parse_path(path)) + assert address == expected_address diff --git a/python/trezorlib/tests/device_tests/test_msg_binance_get_public_key.py b/python/trezorlib/tests/device_tests/test_msg_binance_get_public_key.py new file mode 100644 index 000000000..a55b96ca3 --- /dev/null +++ b/python/trezorlib/tests/device_tests/test_msg_binance_get_public_key.py @@ -0,0 +1,21 @@ +import pytest + +from trezorlib import binance +from trezorlib.tools import parse_path + +from .conftest import setup_client + +BINANCE_PATH = parse_path("m/44h/714h/0h/0/0") + + +@pytest.mark.binance +@pytest.mark.skip_t1 # T1 support is not planned +@setup_client( + mnemonic="offer caution gift cross surge pretty orange during eye soldier popular holiday mention east eight office fashion ill parrot vault rent devote earth cousin" +) +def test_binance_get_public_key(client): + sig = binance.get_public_key(client, BINANCE_PATH) + assert ( + sig.hex() + == "029729a52e4e3c2b4a4e52aa74033eedaf8ba1df5ab6d1f518fd69e67bbd309b0e" + ) diff --git a/python/trezorlib/tests/device_tests/test_msg_binance_sign_tx.py b/python/trezorlib/tests/device_tests/test_msg_binance_sign_tx.py new file mode 100644 index 000000000..c7650f7e5 --- /dev/null +++ b/python/trezorlib/tests/device_tests/test_msg_binance_sign_tx.py @@ -0,0 +1,100 @@ +import pytest + +from trezorlib import binance +from trezorlib.tools import parse_path + +from .conftest import setup_client + +BINANCE_TEST_VECTORS = [ + ( # CANCEL + { + "account_number": "34", + "chain_id": "Binance-Chain-Nile", + "data": "null", + "memo": "", + "msgs": [ + { + "refid": "BA36F0FAD74D8F41045463E4774F328F4AF779E5-29", + "sender": "tbnb1hgm0p7khfk85zpz5v0j8wnej3a90w709zzlffd", + "symbol": "BCHSV.B-10F_BNB", + } + ], + "sequence": "33", + "source": "1", + }, + { + "public_key": "029729a52e4e3c2b4a4e52aa74033eedaf8ba1df5ab6d1f518fd69e67bbd309b0e", + "signature": "d93fb0402b2b30e7ea08e123bb139ad68bf0a1577f38592eb22d11e127f09bbd3380f29b4bf15bdfa973454c5c8ed444f2e256e956fe98cfd21e886a946e21e5", + }, + ), + ( # ORDER + { + "account_number": "34", + "chain_id": "Binance-Chain-Nile", + "data": "null", + "memo": "", + "msgs": [ + { + "id": "BA36F0FAD74D8F41045463E4774F328F4AF779E5-33", + "ordertype": 2, + "price": 100000000, + "quantity": 100000000, + "sender": "tbnb1hgm0p7khfk85zpz5v0j8wnej3a90w709zzlffd", + "side": 1, + "symbol": "ADA.B-B63_BNB", + "timeinforce": 1, + } + ], + "sequence": "32", + "source": "1", + }, + { + "public_key": "029729a52e4e3c2b4a4e52aa74033eedaf8ba1df5ab6d1f518fd69e67bbd309b0e", + "signature": "851fc9542342321af63ecbba7d3ece545f2a42bad01ba32cff5535b18e54b6d3106e10b6a4525993d185a1443d9a125186960e028eabfdd8d76cf70a3a7e3100", + }, + ), + ( # TRANSFER + { + "account_number": "34", + "chain_id": "Binance-Chain-Nile", + "data": "null", + "memo": "test", + "msgs": [ + { + "inputs": [ + { + "address": "tbnb1hgm0p7khfk85zpz5v0j8wnej3a90w709zzlffd", + "coins": [{"amount": 1000000000, "denom": "BNB"}], + } + ], + "outputs": [ + { + "address": "tbnb1ss57e8sa7xnwq030k2ctr775uac9gjzglqhvpy", + "coins": [{"amount": 1000000000, "denom": "BNB"}], + } + ], + } + ], + "sequence": "31", + "source": "1", + }, + { + "public_key": "029729a52e4e3c2b4a4e52aa74033eedaf8ba1df5ab6d1f518fd69e67bbd309b0e", + "signature": "97b4c2e41b0d0f61ddcf4020fff0ecb227d6df69b3dd7e657b34be0e32b956e22d0c6be5832d25353ae24af0bb223d4a5337320518c4e7708b84c8e05eb6356b", + }, + ), +] + + +@pytest.mark.binance +@pytest.mark.skip_t1 # T1 support is not planned +@setup_client( + mnemonic="offer caution gift cross surge pretty orange during eye soldier popular holiday mention east eight office fashion ill parrot vault rent devote earth cousin" +) +@pytest.mark.parametrize("message, expected_response", BINANCE_TEST_VECTORS) +def test_binance_sign_message(client, message, expected_response): + response = binance.sign_tx(client, parse_path("m/44'/714'/0'/0/0"), message) + + assert response.public_key.hex() == expected_response["public_key"] + + assert response.signature.hex() == expected_response["signature"] diff --git a/tools/build_protobuf b/tools/build_protobuf index f2643b424..af78ce9c6 100755 --- a/tools/build_protobuf +++ b/tools/build_protobuf @@ -5,6 +5,7 @@ PROTOB=common/protob CORE_PROTOBUF_SOURCES="\ $PROTOB/messages.proto \ + $PROTOB/messages-binance.proto \ $PROTOB/messages-bitcoin.proto \ $PROTOB/messages-cardano.proto \ $PROTOB/messages-common.proto \