def sign_transaction(tx: Serializable, sender_path: str = DEFAULT_PATH_STRING, dongle: Any = None) -> SignedTransaction: """ Sign a transaction object (rlp.Serializable) """ dongle = init_dongle(dongle) retval = None assert isinstance( tx, Transaction), "Only Transaction objects are currently supported" print('tx: ', tx) encoded_tx = encode(tx, Transaction) if not is_bip32_path(sender_path): raise ValueError('Invalid sender BIP32 path given to sign_transaction') path = parse_bip32_path(sender_path) payload = (len(path) // 4).to_bytes(1, 'big') + path + encoded_tx chunk_count = 0 for chunk in chunks(payload): chunk_size = len(chunk) if chunk_count == 0: retval = dongle_send_data(dongle, 'SIGN_TX_FIRST_DATA', chunk, Lc=chunk_size.to_bytes(1, 'big')) else: retval = dongle_send_data(dongle, 'SIGN_TX_SECONDARY_DATA', chunk, Lc=chunk_size.to_bytes(1, 'big')) chunk_count += 1 if retval is None or len(retval) < 64: raise Exception('Invalid response from Ledger') if (CHAIN_ID * 2 + 35) + 1 > 255: ecc_parity = retval[0] - ((CHAIN_ID * 2 + 35) % 256) v = (CHAIN_ID * 2 + 35) + ecc_parity else: v = retval[0] r = int(binascii.hexlify(retval[1:33]), 16) s = int(binascii.hexlify(retval[33:65]), 16) return SignedTransaction(nonce=tx.nonce, gasprice=tx.gasprice, startgas=tx.startgas, to=tx.to, value=tx.value, data=tx.data, v=v, r=r, s=s)
def get_account_by_path(path_string: str, dongle: Any = None) -> LedgerAccount: """ Return an account for a specific BIP32 derivation path """ dongle = init_dongle(dongle) path = parse_bip32_path(path_string) lc = len(path).to_bytes(1, 'big') data = (len(path) // 4).to_bytes(1, 'big') + path response = dongle_send_data(dongle, 'GET_ADDRESS_NO_CONFIRM', data, Lc=lc) return LedgerAccount(path_string, decode_response_address(response))
def test_comms_sign_small_tx(yield_dongle): CHAIN_ID = 0 # eh? with yield_dongle() as dongle: tx = Transaction( to=decode_hex('0xf0155486a14539f784739be1c02e93f28eb8e960'), value=int(1e17), startgas=int(1e6), gasprice=int(1e9), data=b"", nonce=0, ) encoded_tx = rlp.encode(tx, Transaction) # TODO: Never did figure out what the path count prefix was about payload = ( len(DEFAULT_PATH_ENCODED) // 4 ).to_bytes(1, 'big') + DEFAULT_PATH_ENCODED + encoded_tx vrsbytes = dongle_send_data( dongle, SIGN_TX_FIRST_DATA, payload, Lc=int(len(payload)).to_bytes(1, 'big') ) assert type(vrsbytes) == bytearray if (CHAIN_ID*2 + 35) + 1 > 255: ecc_parity = vrsbytes[0] - ((CHAIN_ID*2 + 35) % 256) v = (CHAIN_ID*2 + 35) + ecc_parity else: v = vrsbytes[0] r = binascii.hexlify(vrsbytes[1:33]) s = binascii.hexlify(vrsbytes[33:65]) assert v in (27, 28) # 0, 1 okay? assert r # TODO: What's an invalid value here? assert s # TODO: What's an invalid value here?
def test_comms_multiple_accounts(yield_dongle): addresses = [] with yield_dongle() as dongle: for i in range(5): path = parse_bip32_path("44'/60'/0'/0/{}".format(i)) lc = len(path).to_bytes(1, 'big') data = (len(path) // 4).to_bytes(1, 'big') + path resp = dongle_send_data( dongle, GET_ADDRESS_NO_CONFIRM, data, Lc=lc ) assert type(resp) == bytearray address = decode_response_address(resp) assert type(address) == str assert len(address) == 42 assert address.startswith('0x') assert address not in addresses addresses.append(address)
def test_comms_sign_large_tx(yield_dongle): CHAIN_ID = 0 # eh? chunk_count = 0 retval = None txdata = '0x29589f61000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee00000000000000000000000000000000000000000000000001628c8b11e853c40000000000000000000000000f5d2fb29fb7d3cfee444a200298f468908cc9420000000000000000000000009283099a29556fcf8fff5b2cea2d4f67cb7a7a8b8000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000107345af81329fe1a05000000000000000000000000440bbd6a888a36de6e2f6a25f65bc4e16874faa9000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000045045524d00000000000000000000000000000000000000000000000000000000' with yield_dongle() as dongle: tx = Transaction( to=decode_hex('0xf0155486a14539f784739be1c02e93f28eb8e960'), value=int(1e17), startgas=int(1e6), gasprice=int(1e9), data=decode_hex(txdata), nonce=1, ) encoded_tx = rlp.encode(tx, Transaction) payload = ( len(DEFAULT_PATH_ENCODED) // 4 ).to_bytes(1, 'big') + DEFAULT_PATH_ENCODED + encoded_tx for chunk in chunks(payload): chunk_size = len(chunk) if chunk_count == 0: retval = dongle_send_data( dongle, 'SIGN_TX_FIRST_DATA', chunk, Lc=chunk_size.to_bytes(1, 'big') ) else: retval = dongle_send_data( dongle, 'SIGN_TX_SECONDARY_DATA', chunk, Lc=chunk_size.to_bytes(1, 'big') ) chunk_count += 1 if (CHAIN_ID*2 + 35) + 1 > 255: ecc_parity = retval[0] - ((CHAIN_ID*2 + 35) % 256) v = (CHAIN_ID*2 + 35) + ecc_parity else: v = retval[0] r = binascii.hexlify(retval[1:33]) s = binascii.hexlify(retval[33:65]) assert v in (27, 28) # 0, 1 okay? assert r # TODO: What's an invalid value here? assert s # TODO: What's an invalid value here? signed_tx_obj = SignedTransaction( to=decode_hex('0x818e6fecd516ecc3849daf6845e3ec868087b755'), value=int(1e17), startgas=int(4e6), gasprice=int(3e9), data=decode_hex(txdata), nonce=1, v=v, r=int(r, 16), s=int(s, 16) ) raw_tx = encode_hex(rlp.encode(signed_tx_obj)) assert raw_tx.startswith('0x') assert len(raw_tx) > 32 # TODO: Shrug