From 95f33a77c737e0f1c20cf20c1fd3aebb99f48eb0 Mon Sep 17 00:00:00 2001 From: matejcik Date: Tue, 28 Jul 2020 11:20:18 +0200 Subject: [PATCH] python/trezorctl: support multisig addresses in get-address --- python/CHANGELOG.md | 1 + python/src/trezorlib/cli/btc.py | 88 +++++++++++++++++++++++++++++++-- 2 files changed, 86 insertions(+), 3 deletions(-) diff --git a/python/CHANGELOG.md b/python/CHANGELOG.md index 7db7ff5f3..138d653a6 100644 --- a/python/CHANGELOG.md +++ b/python/CHANGELOG.md @@ -12,6 +12,7 @@ _At the moment, the project does **not** adhere to [Semantic Versioning](https:/ ### Added - `trezorctl set unsafe-prompts` controls the new "unsafe prompts" feature. [#1126] +- `trezorctl btc get-address` can create multisig addresses. ### Changed diff --git a/python/src/trezorlib/cli/btc.py b/python/src/trezorlib/cli/btc.py index 83b1e71ee..54e03828f 100644 --- a/python/src/trezorlib/cli/btc.py +++ b/python/src/trezorlib/cli/btc.py @@ -18,6 +18,7 @@ import base64 import json import click +import construct as c from .. import btc, messages, protobuf, tools from . import ChoiceType, with_client @@ -37,6 +38,34 @@ OUTPUT_SCRIPTS = { DEFAULT_COIN = "Bitcoin" +XpubStruct = c.Struct( + "version" / c.Int32ub, + "depth" / c.Int8ub, + "fingerprint" / c.Int32ub, + "child_num" / c.Int32ub, + "chain_code" / c.Bytes(32), + "key" / c.Bytes(33), + c.Terminated, +) + + +def xpub_deserialize(xpubstr): + xpub_bytes = tools.b58check_decode(xpubstr) + data = XpubStruct.parse(xpub_bytes) + node = messages.HDNodeType( + depth=data.depth, + fingerprint=data.fingerprint, + child_num=data.child_num, + chain_code=data.chain_code, + ) + if data.key[0] == 0: + node.private_key = data.key[1:] + else: + node.public_key = data.key + + return data.version, node + + @click.group(name="btc") def cli(): """Bitcoin and Bitcoin-like coins commands.""" @@ -52,13 +81,66 @@ def cli(): @click.option("-n", "--address", required=True, help="BIP-32 path") @click.option("-t", "--script-type", type=ChoiceType(INPUT_SCRIPTS), default="address") @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") +@click.option( + "-N", + "--multisig-suffix-length", + help="BIP-32 suffix length for multisig", + type=int, + default=2, +) @with_client -def get_address(client, coin, address, script_type, show_display): - """Get address for specified path.""" +def get_address( + client, + coin, + address, + script_type, + show_display, + multisig_xpub, + multisig_threshold, + multisig_suffix_length, +): + """Get address for specified path. + + To obtain a multisig address, provide XPUBs of all signers (including your own) in + the intended order. All XPUBs should be on the same level. By default, it is assumed + that the XPUBs are on the account level, and the last two components of --address + should be derived from all of them. + + For BIP-45 multisig: + + $ trezorctl btc get-public-node -n m/45h/0 + xpub0101 + $ trezorctl btc get-address -n m/45h/0/0/7 -m 3 -x xpub0101 -x xpub0202 -x xpub0303 + + This assumes that the other signers also created xpubs at address "m/45h/i". + For all the signers, the final keys will be derived with the "/0/7" suffix. + + You can specify a different suffix length by using the -N option. For example, to + use final xpubs, specify '-N 0'. + """ coin = coin or DEFAULT_COIN address_n = tools.parse_path(address) + + if multisig_xpub: + if multisig_threshold is None: + raise click.ClickException("Please specify signature threshold") + + multisig_suffix = address_n[-multisig_suffix_length:] + nodes = [xpub_deserialize(x)[1] for x in multisig_xpub] + multisig = messages.MultisigRedeemScriptType( + nodes=nodes, address_n=multisig_suffix, m=multisig_threshold + ) + if script_type == messages.InputScriptType.SPENDADDRESS: + script_type = messages.InputScriptType.SPENDMULTISIG return btc.get_address( - client, coin, address_n, show_display, script_type=script_type + client, + coin, + address_n, + show_display, + script_type=script_type, + multisig=multisig, )