def sign_interactive(): coin = prompt("Coin name", default="Bitcoin") blockbook_host = prompt("Blockbook server", default="btc1.trezor.io") if not requests.get(f"https://{blockbook_host}/api").ok: raise click.ClickException("Could not connect to blockbook") blockbook_url = f"https://{blockbook_host}/api/tx-specific/" inputs, txes = _get_inputs_interactive(blockbook_url) outputs = _get_outputs_interactive() version = prompt("Transaction version", type=int, default=2) lock_time = prompt("Transaction locktime", type=int, default=0) result = { "coin_name": coin, "inputs": [to_dict(i, hexlify_bytes=True) for i in inputs], "outputs": [to_dict(o, hexlify_bytes=True) for o in outputs], "details": { "version": version, "lock_time": lock_time, }, "prev_txes": { txhash: to_dict(txdata, hexlify_bytes=True) for txhash, txdata in txes.items() }, } print(json.dumps(result, sort_keys=True, indent=2))
def to_dict(self): return { "coin_name": self.coin_name, "details": protobuf.to_dict(self.details), "inputs": [protobuf.to_dict(i) for i in self.inputs], "outputs": [protobuf.to_dict(o) for o in self.outputs], "prev_txes": { key.hex(): protobuf.to_dict(value) for key, value in self.prev_txes.items() }, }
def test_hexlify(): msg = SimpleMessage(bytes=b"\xca\xfe\x00\x12\x34", unicode="žluťoučký kůň") converted_nohex = protobuf.to_dict(msg, hexlify_bytes=False) converted_hex = protobuf.to_dict(msg, hexlify_bytes=True) assert converted_nohex["bytes"] == b"\xca\xfe\x00\x12\x34" assert converted_nohex["unicode"] == "žluťoučký kůň" assert converted_hex["bytes"] == "cafe001234" assert converted_hex["unicode"] == "žluťoučký kůň" recovered_nohex = protobuf.dict_to_proto(SimpleMessage, converted_nohex) recovered_hex = protobuf.dict_to_proto(SimpleMessage, converted_hex) assert recovered_nohex.bytes == msg.bytes assert recovered_hex.bytes == msg.bytes
def test_to_dict(): msg = SimpleMessage( uvarint=5, svarint=-13, bool=False, bytes=b"\xca\xfe\x00\xfe", unicode="žluťoučký kůň", enum=SimpleEnum.BAR, rep_int=[1, 2, 3], rep_str=["a", "b", "c"], rep_enum=[SimpleEnum.FOO, SimpleEnum.BAR, SimpleEnum.QUUX], ) converted = protobuf.to_dict(msg) fields = [field.name for field in msg.FIELDS.values()] assert list(sorted(converted.keys())) == list(sorted(fields)) assert converted["uvarint"] == 5 assert converted["svarint"] == -13 assert converted["bool"] is False assert converted["bytes"] == "cafe00fe" assert converted["unicode"] == "žluťoučký kůň" assert converted["enum"] == "BAR" assert converted["rep_int"] == [1, 2, 3] assert converted["rep_str"] == ["a", "b", "c"] assert converted["rep_enum"] == ["FOO", "BAR", "QUUX"]
def sign_interactive(): coin = prompt("Coin name", default="Bitcoin") if coin in coins.tx_api: coin_data = coins.by_name[coin] txapi = coins.tx_api[coin] else: echo('Coin "%s" is not recognized.' % coin, err=True) echo("Supported coin types: %s" % ", ".join(coins.tx_api.keys()), err=True) sys.exit(1) inputs, txes = _get_inputs_interactive(coin_data, txapi) outputs = _get_outputs_interactive() if coin_data["bip115"]: current_block_height = txapi.current_height() # Zencash recommendation for the better protection block_height = current_block_height - 300 block_hash = txapi.get_block_hash(block_height) # Blockhash passed in reverse order block_hash = block_hash[::-1] for output in outputs: output.block_hash_bip115 = block_hash output.block_height_bip115 = block_height signtx = messages.SignTx() signtx.version = prompt("Transaction version", type=int, default=2) signtx.lock_time = prompt("Transaction locktime", type=int, default=0) if coin == "Capricoin": signtx.timestamp = prompt("Transaction timestamp", type=int) result = { "coin_name": coin, "inputs": [to_dict(i, hexlify_bytes=True) for i in inputs], "outputs": [to_dict(o, hexlify_bytes=True) for o in outputs], "details": to_dict(signtx, hexlify_bytes=True), "prev_txes": { txhash.hex(): to_dict(txdata, hexlify_bytes=True) for txhash, txdata in txes.items() }, } print(json.dumps(result, sort_keys=True, indent=2))
def test_nested_round_trip(): msg = NestedMessage( scalar=9, nested=SimpleMessage(uvarint=4, enum=SimpleEnum.FOO), repeated=[ SimpleMessage(), SimpleMessage(rep_enum=[SimpleEnum.BAR, SimpleEnum.BAR]), SimpleMessage(bytes=b"\xca\xfe"), ], ) converted = protobuf.to_dict(msg) recovered = protobuf.dict_to_proto(NestedMessage, converted) assert msg == recovered
def test_dict_roundtrip(): msg = SimpleMessage( uvarint=5, svarint=-13, bool=False, bytes=b"\xca\xfe\x00\xfe", unicode="žluťoučký kůň", enum=SimpleEnum.BAR, rep_int=[1, 2, 3], rep_str=["a", "b", "c"], rep_enum=[SimpleEnum.FOO, SimpleEnum.BAR, SimpleEnum.QUUX], ) converted = protobuf.to_dict(msg) recovered = protobuf.dict_to_proto(SimpleMessage, converted) assert recovered == msg
def cli(tx, coin_name): """Add a transaction to the cache. \b Without URL, default blockbook server will be used: ./tests/tx_cache.py bcash bc37c28dfb467d2ecb50261387bf752a3977d7e5337915071bb4151e6b711a78 It is also possible to specify URL explicitly: ./tests/tx_cache.py bcash https://bch1.trezor.io/api/tx-specific/bc37c28dfb467d2ecb50261387bf752a3977d7e5337915071bb4151e6b711a78 The transaction will be parsed into Trezor format and saved in tests/txcache/<COIN_NAME>/<TXHASH>.json. Note that only Bitcoin-compatible fields will be filled out. If you are adding a coin with special fields (Dash, Zcash...), it is your responsibility to fill out the missing fields properly. """ if tx.startswith("http"): tx_url = tx tx_hash = tx.split("/")[-1].lower() elif coin_name not in BLOCKBOOKS: raise click.ClickException( f"Could not find blockbook for {coin_name}. Please specify a full URL." ) else: tx_hash = tx.lower() tx_url = BLOCKBOOKS[coin_name].format(tx) click.echo(f"Fetching from {tx_url}...") try: # Get transaction from Blockbook server. The servers refuse requests with an empty user agent. tx_src = requests.get(tx_url, headers={ "user-agent": "tx_cache" }).json(parse_float=Decimal) tx_proto = btc.from_json(tx_src) tx_dict = protobuf.to_dict(tx_proto) tx_json = json.dumps(tx_dict, sort_keys=True, indent=2) + "\n" except Exception as e: raise click.ClickException(str(e)) from e cache_dir = CACHE_PATH / coin_name if not cache_dir.exists(): cache_dir.mkdir() (cache_dir / f"{tx_hash}.json").write_text(tx_json) click.echo(tx_json)
def test_nested_to_dict(): msg = NestedMessage( scalar=9, nested=SimpleMessage(uvarint=4, enum=SimpleEnum.FOO), repeated=[ SimpleMessage(), SimpleMessage(rep_enum=[SimpleEnum.BAR, SimpleEnum.BAR]), SimpleMessage(bytes=b"\xca\xfe"), ], ) converted = protobuf.to_dict(msg) assert converted["scalar"] == 9 assert isinstance(converted["nested"], dict) assert isinstance(converted["repeated"], list) rep = converted["repeated"] assert rep[0] == {} assert rep[1] == {"rep_enum": ["BAR", "BAR"]} assert rep[2] == {"bytes": "cafe"}
def test_unknown_enum_to_dict(): simple = SimpleMessage(enum=6000) converted = protobuf.to_dict(simple) assert converted["enum"] == 6000
def to_json(psbt_base64, psbt_file, coin_name): """Convert PSBT to Trezor-compatible JSON transaction. You can use `trezorctl btc sign-tx <file>` to sign it.""" psbt_bytes = get_psbt_bytes(psbt_base64, psbt_file) header, inputs, outputs = psbt.read_psbt(psbt_bytes) coin = coins.by_name[coin_name] fingerprints = set() for inout in inputs + outputs: for path in inout.bip32_path.values(): fingerprints.add(path.fingerprint) if not fingerprints: raise click.ClickException("Nothing to sign!") if len(fingerprints) > 1: fingerprint_str = ", ".join(f.hex() for f in fingerprints) raise click.ClickException( f"More than one signer fingerprint found: {fingerprint_str}") # TODO allow specifying or allow using all master_fingerprint = fingerprints.pop() details_dict = { "version": header.transaction.version, "lock_time": header.transaction.lock_time, } trezor_details = trezor.make_signing_details(header.transaction) trezor_inputs = [] trezor_outputs = [] prev_txes = {} for i, (tx_in, psbt_in) in enumerate(zip(header.transaction.inputs, inputs), 1): try: trezor_in = trezor.make_input(tx_in, psbt_in, master_fingerprint) trezor_inputs.append(trezor_in) prev_txes[tx_in.tx] = trezor.make_transaction( psbt_in.non_witness_utxo) except Exception as e: import traceback traceback.print_exc() raise click.ClickException(f"In input #{i}: {e}") from e for i, (tx_out, psbt_out) in enumerate(zip(header.transaction.outputs, outputs), 1): try: trezor_out = trezor.make_output(tx_out, psbt_out, master_fingerprint, coin) trezor_outputs.append(trezor_out) except Exception as e: raise click.ClickException(f"In output #{i}: {e}") from e tx = dict( coin_name=coin_name, details=to_dict(trezor_details), inputs=[to_dict(txi) for txi in trezor_inputs], outputs=[to_dict(txo) for txo in trezor_outputs], prev_txes={key.hex(): to_dict(val) for key, val in prev_txes.items()}, ) click.echo(json.dumps(tx, sort_keys=True, indent=2))