You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
trezor-firmware/tools/snippets/sign_tx.py

126 lines
3.9 KiB

"""
Script for quick (and repeatable) creation and signing of
specific inputs and outputs inspired by BTC device tests.
INPUTS and OUTPUTS lists are the only things needed to be modified.
Similar to tools/build_tx.py, but more suitable for bigger/autogenerated transactions.
The serialized transaction can then be announced to network at https://tbtc1.trezor.io/sendtx
It could be useful to inspect the transaction details at https://live.blockcypher.com/btc/decodetx/
Usage:
- modify INPUTS and OUTPUTS lists to suit the needs
- call the script with possible flags - see `python sign_tx.py --help`
"""
import argparse
from decimal import Decimal
from typing import Dict, List
import requests
from trezorlib import btc, messages
from trezorlib.client import get_default_client
from trezorlib.debuglink import TrezorClientDebugLink
from trezorlib.tools import parse_path
from trezorlib.transport import enumerate_devices
parser = argparse.ArgumentParser()
parser.add_argument(
"--autoconfirm",
action="store_true",
help="Automatically confirm everything on the device.",
)
parser.add_argument(
"--testnet",
action="store_true",
help="Use BTC testnet instead of mainnet.",
)
args = parser.parse_args()
# Can choose autoconfirm everything on the device (in the device-tests-style)
# (Suitable for long/repetitive transactions)
if args.autoconfirm:
print("Autoconfirming everything on the device.")
for device in enumerate_devices():
try:
CLIENT = TrezorClientDebugLink(device, auto_interact=True)
break
except Exception:
pass
else:
raise RuntimeError("Could not find device")
else:
CLIENT = get_default_client()
# Choosing between Mainnet and Testnet
if args.testnet:
COIN = "Testnet"
URL = "https://tbtc1.trezor.io/api/tx-specific"
else:
COIN = "Bitcoin"
URL = "https://btc1.trezor.io/api/tx-specific"
print(f"Operating on {COIN} at {URL}")
# Specific example of generating and signing a transaction with 255 outputs
# (Could be tried on `all all all...` seed on testnet)
# (--autoconfirm really helps here)
INPUTS = [
messages.TxInputType(
address_n=parse_path("44h/1h/0h/0/0"), # mvbu1Gdy8SUjTenqerxUaZyYjmveZvt33q
amount=1_827_955,
prev_hash=bytes.fromhex(
"58d56a5d1325cf83543ee4c87fd73a784e4ba1499ced574be359fa2bdcb9ac8e"
),
prev_index=1,
),
]
count = 255
OUTPUTS = [
messages.TxOutputType(
address="momtnzR3XqXgDSsFmd8gkGxUiHZLde3RmA", # "44h/1h/0h/0/3"
amount=(1_827_955 - 10_000) // count,
script_type=messages.OutputScriptType.PAYTOADDRESS,
)
for _ in range(count)
]
def get_tx_info(tx_id: str) -> messages.TransactionType:
"""Fetch basic transaction info for the signing."""
tx_url = f"{URL}/{tx_id}"
tx_src = requests.get(tx_url, headers={"user-agent": "tx_cache"}).json(
parse_float=Decimal
)
if "error" in tx_src:
raise RuntimeError(tx_src["error"])
return btc.from_json(tx_src)
def get_prev_txes(
inputs: List[messages.TxInputType],
) -> Dict[bytes, messages.TransactionType]:
"""Get info for all the previous transactions inputs are depending on."""
prev_txes = {}
for input in inputs:
tx_id = input.prev_hash
if tx_id not in prev_txes:
prev_txes[tx_id] = get_tx_info(tx_id.hex())
return prev_txes
if __name__ == "__main__":
assert len(INPUTS) > 0, "there are no inputs"
assert len(OUTPUTS) > 0, "there are no outputs"
if not all(isinstance(inp, messages.TxInputType) for inp in INPUTS):
raise RuntimeError("all inputs must be TxInputType")
if not all(isinstance(out, messages.TxOutputType) for out in OUTPUTS):
raise RuntimeError("all outputs must be TxOutputType")
_, serialized_tx = btc.sign_tx(
CLIENT, COIN, INPUTS, OUTPUTS, prev_txes=get_prev_txes(INPUTS)
)
print(80 * "-")
print(serialized_tx.hex())