From aaa224a4f7393ac7ce89fea03953389346ff7d29 Mon Sep 17 00:00:00 2001 From: matejcik Date: Wed, 22 Jun 2022 15:19:39 +0200 Subject: [PATCH] feat(python/trezorctl): detect script type from path (fixes #2159) --- python/.changelog.d/2159.added | 1 + python/src/trezorlib/cli/btc.py | 47 ++++++++++++++++++++++++++++----- 2 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 python/.changelog.d/2159.added diff --git a/python/.changelog.d/2159.added b/python/.changelog.d/2159.added new file mode 100644 index 000000000..1db07480a --- /dev/null +++ b/python/.changelog.d/2159.added @@ -0,0 +1 @@ +trezorctl: Bitcoin commands can detect script type from derivation path. diff --git a/python/src/trezorlib/cli/btc.py b/python/src/trezorlib/cli/btc.py index a099d6e53..898a4940f 100644 --- a/python/src/trezorlib/cli/btc.py +++ b/python/src/trezorlib/cli/btc.py @@ -49,6 +49,19 @@ OUTPUT_SCRIPTS = { "tr": messages.OutputScriptType.PAYTOTAPROOT, } +BIP_PURPOSE_TO_SCRIPT_TYPE = { + tools.H_(44): messages.InputScriptType.SPENDADDRESS, + tools.H_(49): messages.InputScriptType.SPENDP2SHWITNESS, + tools.H_(84): messages.InputScriptType.SPENDWITNESS, + tools.H_(86): messages.InputScriptType.SPENDTAPROOT, +} + +BIP48_SCRIPT_TYPES = { + tools.H_(0): messages.InputScriptType.SPENDMULTISIG, + tools.H_(1): messages.InputScriptType.SPENDP2SHWITNESS, + tools.H_(2): messages.InputScriptType.SPENDWITNESS, +} + DEFAULT_COIN = "Bitcoin" @@ -85,6 +98,22 @@ def xpub_deserialize(xpubstr: str) -> Tuple[str, messages.HDNodeType]: return data.version, node +def guess_script_type_from_path(address_n: List[int]) -> messages.InputScriptType: + if len(address_n) < 1: + return messages.InputScriptType.SPENDADDRESS + + purpose = address_n[0] + if purpose in BIP_PURPOSE_TO_SCRIPT_TYPE: + return BIP_PURPOSE_TO_SCRIPT_TYPE[purpose] + + if purpose == tools.H_(48) and len(address_n) >= 4: + script_type_field = address_n[3] + if script_type_field in BIP48_SCRIPT_TYPES: + return BIP48_SCRIPT_TYPES[script_type_field] + + return messages.InputScriptType.SPENDADDRESS + + @click.group(name="btc") def cli() -> None: """Bitcoin and Bitcoin-like coins commands.""" @@ -98,7 +127,7 @@ def cli() -> None: @cli.command() @click.option("-c", "--coin", default=DEFAULT_COIN) @click.option("-n", "--address", required=True, help="BIP-32 path") -@click.option("-t", "--script-type", type=ChoiceType(INPUT_SCRIPTS), default="address") +@click.option("-t", "--script-type", type=ChoiceType(INPUT_SCRIPTS)) @click.option("-d", "--show-display", is_flag=True) @click.option("-x", "--multisig-xpub", multiple=True, help="XPUBs of multisig owners") @click.option("-m", "--multisig-threshold", type=int, help="Number of signatures") @@ -114,7 +143,7 @@ def get_address( client: "TrezorClient", coin: str, address: str, - script_type: messages.InputScriptType, + script_type: Optional[messages.InputScriptType], show_display: bool, multisig_xpub: List[str], multisig_threshold: Optional[int], @@ -141,6 +170,8 @@ def get_address( use final xpubs, specify '-N 0'. """ address_n = tools.parse_path(address) + if script_type is None: + script_type = guess_script_type_from_path(address_n) multisig: Optional[messages.MultisigRedeemScriptType] if multisig_xpub: @@ -171,7 +202,7 @@ def get_address( @click.option("-c", "--coin", default=DEFAULT_COIN) @click.option("-n", "--address", required=True, help="BIP-32 path, e.g. m/44'/0'/0'") @click.option("-e", "--curve") -@click.option("-t", "--script-type", type=ChoiceType(INPUT_SCRIPTS), default="address") +@click.option("-t", "--script-type", type=ChoiceType(INPUT_SCRIPTS)) @click.option("-d", "--show-display", is_flag=True) @with_client def get_public_node( @@ -179,11 +210,13 @@ def get_public_node( coin: str, address: str, curve: Optional[str], - script_type: messages.InputScriptType, + script_type: Optional[messages.InputScriptType], show_display: bool, ) -> dict: """Get public node of given path.""" address_n = tools.parse_path(address) + if script_type is None: + script_type = guess_script_type_from_path(address_n) result = btc.get_public_node( client, address_n, @@ -332,7 +365,7 @@ def sign_tx(client: "TrezorClient", json_file: TextIO) -> None: @cli.command() @click.option("-c", "--coin", default=DEFAULT_COIN) @click.option("-n", "--address", required=True, help="BIP-32 path") -@click.option("-t", "--script-type", type=ChoiceType(INPUT_SCRIPTS), default="address") +@click.option("-t", "--script-type", type=ChoiceType(INPUT_SCRIPTS)) @click.option( "-e", "--electrum-compat", @@ -346,11 +379,13 @@ def sign_message( coin: str, address: str, message: str, - script_type: messages.InputScriptType, + script_type: Optional[messages.InputScriptType], electrum_compat: bool, ) -> Dict[str, str]: """Sign message using address of given path.""" address_n = tools.parse_path(address) + if script_type is None: + script_type = guess_script_type_from_path(address_n) res = btc.sign_message( client, coin, address_n, message, script_type, electrum_compat )