mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-02-21 03:52:04 +00:00
- placeOrder, cancelOrder, transfer messages - cli support - unit and device tests
This commit is contained in:
parent
e4a69dcc53
commit
90b91a7fb5
@ -71,7 +71,6 @@ message BinanceTxRequest {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Request: Ask the device to include a Binance transfer msg in the tx.
|
* Request: Ask the device to include a Binance transfer msg in the tx.
|
||||||
* @next BinanceTxRequest
|
|
||||||
* @next BinanceSignedTx
|
* @next BinanceSignedTx
|
||||||
* @next Failure
|
* @next Failure
|
||||||
*/
|
*/
|
||||||
@ -92,7 +91,6 @@ message BinanceTransferMsg {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Request: Ask the device to include a Binance order msg in the tx.
|
* Request: Ask the device to include a Binance order msg in the tx.
|
||||||
* @next BinanceTxRequest
|
|
||||||
* @next BinanceSignedTx
|
* @next BinanceSignedTx
|
||||||
* @next Failure
|
* @next Failure
|
||||||
*/
|
*/
|
||||||
@ -129,7 +127,6 @@ message BinanceOrderMsg {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Request: Ask the device to include a Binance cancel msg in the tx.
|
* Request: Ask the device to include a Binance cancel msg in the tx.
|
||||||
* @next BinanceTxRequest
|
|
||||||
* @next BinanceSignedTx
|
* @next BinanceSignedTx
|
||||||
* @next Failure
|
* @next Failure
|
||||||
*/
|
*/
|
||||||
@ -146,6 +143,5 @@ message BinanceCancelMsg {
|
|||||||
message BinanceSignedTx {
|
message BinanceSignedTx {
|
||||||
optional bytes signature = 1;
|
optional bytes signature = 1;
|
||||||
optional bytes public_key = 2;
|
optional bytes public_key = 2;
|
||||||
optional string json = 3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ algorithm, extended to work on other curves.
|
|||||||
| Ethereum | secp256k1 | `44'/c'/0'/0/a` | yes | [2](#Ethereum) |
|
| Ethereum | secp256k1 | `44'/c'/0'/0/a` | yes | [2](#Ethereum) |
|
||||||
| Ripple | secp256k1 | `44'/144'/a'/0/0` | | [3](#Ripple) |
|
| Ripple | secp256k1 | `44'/144'/a'/0/0` | | [3](#Ripple) |
|
||||||
| EOS | secp256k1 | `44'/194'/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 |
|
| Tron | secp256k1 | TODO | | TODO |
|
||||||
| Ontology | nist256p1 | TODO | | TODO |
|
| Ontology | nist256p1 | TODO | | TODO |
|
||||||
| Cardano | ed25519 | `44'/1815'/a'/y/i` | yes | [4](#Cardano) |
|
| Cardano | ed25519 | `44'/1815'/a'/y/i` | yes | [4](#Cardano) |
|
||||||
|
25
core/src/apps/binance/README.md
Normal file
25
core/src/apps/binance/README.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Binance
|
||||||
|
|
||||||
|
MAINTAINER = Stanislav Marcinko <stanislav.marcinko@satoshilabs.com>
|
||||||
|
|
||||||
|
AUTHOR = Stanislav Marcinko <stanislav.marcinko@satoshilabs.com>
|
||||||
|
|
||||||
|
REVIEWER = Jan Matějek <jan.matejek@satoshilabs.com>
|
||||||
|
|
||||||
|
-----
|
||||||
|
|
||||||
|
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
|
13
core/src/apps/binance/__init__.py
Normal file
13
core/src/apps/binance/__init__.py
Normal file
@ -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)
|
27
core/src/apps/binance/get_address.py
Normal file
27
core/src/apps/binance/get_address.py
Normal file
@ -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)
|
18
core/src/apps/binance/get_public_key.py
Normal file
18
core/src/apps/binance/get_public_key.py
Normal file
@ -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)
|
115
core/src/apps/binance/helpers.py
Normal file
115
core/src/apps/binance/helpers.py
Normal file
@ -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
|
71
core/src/apps/binance/layout.py
Normal file
71
core/src/apps/binance/layout.py
Normal file
@ -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
|
||||||
|
)
|
51
core/src/apps/binance/sign_tx.py
Normal file
51
core/src/apps/binance/sign_tx.py
Normal file
@ -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]
|
@ -41,6 +41,7 @@ def _boot_default():
|
|||||||
import apps.cardano
|
import apps.cardano
|
||||||
import apps.tezos
|
import apps.tezos
|
||||||
import apps.eos
|
import apps.eos
|
||||||
|
import apps.binance
|
||||||
|
|
||||||
if __debug__:
|
if __debug__:
|
||||||
import apps.debug
|
import apps.debug
|
||||||
@ -60,6 +61,7 @@ def _boot_default():
|
|||||||
apps.cardano.boot()
|
apps.cardano.boot()
|
||||||
apps.tezos.boot()
|
apps.tezos.boot()
|
||||||
apps.eos.boot()
|
apps.eos.boot()
|
||||||
|
apps.binance.boot()
|
||||||
if __debug__:
|
if __debug__:
|
||||||
apps.debug.boot()
|
apps.debug.boot()
|
||||||
else:
|
else:
|
||||||
|
25
core/src/trezor/messages/BinanceAddress.py
Normal file
25
core/src/trezor/messages/BinanceAddress.py
Normal file
@ -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),
|
||||||
|
}
|
31
core/src/trezor/messages/BinanceCancelMsg.py
Normal file
31
core/src/trezor/messages/BinanceCancelMsg.py
Normal file
@ -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),
|
||||||
|
}
|
27
core/src/trezor/messages/BinanceCoin.py
Normal file
27
core/src/trezor/messages/BinanceCoin.py
Normal file
@ -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),
|
||||||
|
}
|
28
core/src/trezor/messages/BinanceGetAddress.py
Normal file
28
core/src/trezor/messages/BinanceGetAddress.py
Normal file
@ -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),
|
||||||
|
}
|
28
core/src/trezor/messages/BinanceGetPublicKey.py
Normal file
28
core/src/trezor/messages/BinanceGetPublicKey.py
Normal file
@ -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),
|
||||||
|
}
|
29
core/src/trezor/messages/BinanceInputOutput.py
Normal file
29
core/src/trezor/messages/BinanceInputOutput.py
Normal file
@ -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),
|
||||||
|
}
|
46
core/src/trezor/messages/BinanceOrderMsg.py
Normal file
46
core/src/trezor/messages/BinanceOrderMsg.py
Normal file
@ -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),
|
||||||
|
}
|
5
core/src/trezor/messages/BinanceOrderSide.py
Normal file
5
core/src/trezor/messages/BinanceOrderSide.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Automatically generated by pb2py
|
||||||
|
# fmt: off
|
||||||
|
SIDE_UNKNOWN = 0
|
||||||
|
BUY = 1
|
||||||
|
SELL = 2
|
6
core/src/trezor/messages/BinanceOrderType.py
Normal file
6
core/src/trezor/messages/BinanceOrderType.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# Automatically generated by pb2py
|
||||||
|
# fmt: off
|
||||||
|
OT_UNKNOWN = 0
|
||||||
|
MARKET = 1
|
||||||
|
LIMIT = 2
|
||||||
|
OT_RESERVED = 3
|
25
core/src/trezor/messages/BinancePublicKey.py
Normal file
25
core/src/trezor/messages/BinancePublicKey.py
Normal file
@ -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),
|
||||||
|
}
|
43
core/src/trezor/messages/BinanceSignTx.py
Normal file
43
core/src/trezor/messages/BinanceSignTx.py
Normal file
@ -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),
|
||||||
|
}
|
28
core/src/trezor/messages/BinanceSignedTx.py
Normal file
28
core/src/trezor/messages/BinanceSignedTx.py
Normal file
@ -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),
|
||||||
|
}
|
6
core/src/trezor/messages/BinanceTimeInForce.py
Normal file
6
core/src/trezor/messages/BinanceTimeInForce.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# Automatically generated by pb2py
|
||||||
|
# fmt: off
|
||||||
|
TIF_UNKNOWN = 0
|
||||||
|
GTE = 1
|
||||||
|
TIF_RESERVED = 2
|
||||||
|
IOC = 3
|
30
core/src/trezor/messages/BinanceTransferMsg.py
Normal file
30
core/src/trezor/messages/BinanceTransferMsg.py
Normal file
@ -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),
|
||||||
|
}
|
13
core/src/trezor/messages/BinanceTxRequest.py
Normal file
13
core/src/trezor/messages/BinanceTxRequest.py
Normal file
@ -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
|
49
core/tests/test_apps.binance.address.py
Normal file
49
core/tests/test_apps.binance.address.py
Normal file
@ -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()
|
108
core/tests/test_apps.binance.sign_tx.py
Normal file
108
core/tests/test_apps.binance.sign_tx.py
Normal file
@ -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()
|
@ -31,6 +31,7 @@ import click
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
from trezorlib import (
|
from trezorlib import (
|
||||||
|
binance,
|
||||||
btc,
|
btc,
|
||||||
cardano,
|
cardano,
|
||||||
coins,
|
coins,
|
||||||
@ -1841,6 +1842,56 @@ def tezos_sign_tx(connect, address, file):
|
|||||||
return tezos.sign_tx(client, address_n, msg)
|
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
|
# Main
|
||||||
#
|
#
|
||||||
|
52
python/trezorlib/binance.py
Normal file
52
python/trezorlib/binance.py
Normal file
@ -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
|
@ -16,16 +16,13 @@ class BinanceSignedTx(p.MessageType):
|
|||||||
self,
|
self,
|
||||||
signature: bytes = None,
|
signature: bytes = None,
|
||||||
public_key: bytes = None,
|
public_key: bytes = None,
|
||||||
json: str = None,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
self.signature = signature
|
self.signature = signature
|
||||||
self.public_key = public_key
|
self.public_key = public_key
|
||||||
self.json = json
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_fields(cls) -> Dict:
|
def get_fields(cls) -> Dict:
|
||||||
return {
|
return {
|
||||||
1: ('signature', p.BytesType, 0),
|
1: ('signature', p.BytesType, 0),
|
||||||
2: ('public_key', p.BytesType, 0),
|
2: ('public_key', p.BytesType, 0),
|
||||||
3: ('json', p.UnicodeType, 0),
|
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
binance
|
||||||
capricoin
|
capricoin
|
||||||
cardano
|
cardano
|
||||||
decred
|
decred
|
||||||
|
@ -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
|
@ -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"
|
||||||
|
)
|
100
python/trezorlib/tests/device_tests/test_msg_binance_sign_tx.py
Normal file
100
python/trezorlib/tests/device_tests/test_msg_binance_sign_tx.py
Normal file
@ -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"]
|
@ -5,6 +5,7 @@ PROTOB=common/protob
|
|||||||
|
|
||||||
CORE_PROTOBUF_SOURCES="\
|
CORE_PROTOBUF_SOURCES="\
|
||||||
$PROTOB/messages.proto \
|
$PROTOB/messages.proto \
|
||||||
|
$PROTOB/messages-binance.proto \
|
||||||
$PROTOB/messages-bitcoin.proto \
|
$PROTOB/messages-bitcoin.proto \
|
||||||
$PROTOB/messages-cardano.proto \
|
$PROTOB/messages-cardano.proto \
|
||||||
$PROTOB/messages-common.proto \
|
$PROTOB/messages-common.proto \
|
||||||
|
Loading…
Reference in New Issue
Block a user