From b5e26f1d44d18f8317dece16bba499f2fc901bbf Mon Sep 17 00:00:00 2001 From: Tomas Susanka Date: Mon, 29 Jan 2018 14:41:56 +0100 Subject: [PATCH] singing: multisig --- src/apps/wallet/sign_tx/addresses.py | 22 ++----- src/apps/wallet/sign_tx/multisig.py | 24 ++++++++ src/apps/wallet/sign_tx/scripts.py | 60 +++++++++++++++---- src/apps/wallet/sign_tx/signing.py | 23 ++++--- .../wallet/sign_tx/tx_weight_calculator.py | 2 +- 5 files changed, 95 insertions(+), 36 deletions(-) create mode 100644 src/apps/wallet/sign_tx/multisig.py diff --git a/src/apps/wallet/sign_tx/addresses.py b/src/apps/wallet/sign_tx/addresses.py index ed4816bfc..124d8a342 100644 --- a/src/apps/wallet/sign_tx/addresses.py +++ b/src/apps/wallet/sign_tx/addresses.py @@ -9,6 +9,7 @@ from trezor.messages import FailureType from trezor.messages import InputScriptType from apps.wallet.sign_tx.scripts import * +from apps.wallet.sign_tx.multisig import * # supported witness version for bech32 addresses _BECH32_WITVER = const(0x00) @@ -41,7 +42,7 @@ def get_address(script_type: InputScriptType, coin: CoinType, node) -> str: def address_multisig_p2sh(pubkeys: bytes, m: int, addrtype): - digest = multisig_p2sh_script(pubkeys, m) + digest = output_script_multisig_p2sh(pubkeys, m) if addrtype is None: raise AddressError(FailureType.ProcessError, 'Multisig not enabled on this coin') @@ -49,7 +50,7 @@ def address_multisig_p2sh(pubkeys: bytes, m: int, addrtype): def address_multisig_p2wsh_in_p2sh(pubkeys: bytes, m: int, addrtype): - digest = multisig_p2wsh_script(pubkeys, m) + digest = output_script_multisig_p2wsh(pubkeys, m) if addrtype is None: raise AddressError(FailureType.ProcessError, 'Multisig not enabled on this coin') @@ -57,28 +58,13 @@ def address_multisig_p2wsh_in_p2sh(pubkeys: bytes, m: int, addrtype): def address_multisig_p2wsh(pubkeys: bytes, m: int, addrtype): - digest = multisig_p2wsh_script(pubkeys, m) + digest = output_script_multisig_p2wsh(pubkeys, m) if addrtype is None: raise AddressError(FailureType.ProcessError, 'Multisig not enabled on this coin') return address_p2sh(digest, addrtype) -# Returns ripemd160(sha256(multisig script)) as per P2SH definition -def multisig_p2sh_script(pubkeys, m) -> bytes: - script = multisig_script(pubkeys, m) - return ripemd160(script.get_digest()).digest() - - -# Returns sha256(multisig script) as per P2WSH definition -def multisig_p2wsh_script(pubkeys, m) -> bytes: - for pubkey in pubkeys: - if len(pubkey) != 33: - raise Exception # only compressed public keys are allowed for P2WSH - script = multisig_script(pubkeys, m) - return script.get_digest() - - def address_p2sh(redeem_script_hash: bytes, addrtype: int) -> str: s = bytearray(21) s[0] = addrtype diff --git a/src/apps/wallet/sign_tx/multisig.py b/src/apps/wallet/sign_tx/multisig.py new file mode 100644 index 000000000..7b9661536 --- /dev/null +++ b/src/apps/wallet/sign_tx/multisig.py @@ -0,0 +1,24 @@ +from trezor.crypto import bip32 + +from trezor.messages.MultisigRedeemScriptType import MultisigRedeemScriptType +from trezor.messages.HDNodePathType import HDNodePathType + +from apps.wallet.sign_tx.writers import * + + +def multisig_get_pubkey(hd: HDNodePathType) -> bytes: + p = hd.address_n + n = hd.node + node = bip32.HDNode( + depth=n.depth, + fingerprint=n.fingerprint, + child_num=n.child_num, + chain_code=n.chain_code, + public_key=n.public_key) + for i in p: + node.derive(i, True) + return node.public_key() + + +def multisig_get_pubkeys(multisig: MultisigRedeemScriptType): + return [multisig_get_pubkey(hd) for hd in multisig.pubkeys] diff --git a/src/apps/wallet/sign_tx/scripts.py b/src/apps/wallet/sign_tx/scripts.py index 9c7688908..cb3cb6a73 100644 --- a/src/apps/wallet/sign_tx/scripts.py +++ b/src/apps/wallet/sign_tx/scripts.py @@ -2,6 +2,7 @@ from apps.wallet.sign_tx.multisig import * from apps.wallet.sign_tx.writers import * from apps.common.hash_writer import HashWriter +from trezor.crypto.hashlib import sha256, ripemd160 # TX Scripts # === @@ -16,7 +17,8 @@ from apps.common.hash_writer import HashWriter def input_script_p2pkh_or_p2sh(pubkey: bytes, signature: bytes, sighash: int) -> bytearray: w = bytearray_with_cap(5 + len(signature) + 1 + 5 + len(pubkey)) - append_signature_and_pubkey(w, pubkey, signature, sighash) + append_signature(w, signature, sighash) + append_pubkey(w, pubkey) return w @@ -100,21 +102,55 @@ def input_script_p2wpkh_in_p2sh(pubkeyhash: bytes) -> bytearray: # =============== Multisig =============== -def multisig_script(pubkeys, m) -> HashWriter: +def input_script_multisig(current_signature, other_signatures, pubkeys, m: int): + w = bytearray() + # starts with OP_FALSE because of an old OP_CHECKMULTISIG bug, + # which consumes one additional item on the stack + # see https://bitcoin.org/en/developer-guide#standard-transactions + w.append(0x00) + for s in other_signatures: + if len(s): + append_signature(w, s) + + append_signature(w, current_signature) + + # redeem script + redeem_script = script_multisig(pubkeys, m) + write_op_push(w, len(redeem_script)) + write_bytes(w, script_multisig(pubkeys, m)) + return w + + +# returns a ripedm(sha256()) hash of a multisig script used in P2SH +def output_script_multisig_p2sh(pubkeys, m) -> HashWriter: + script = script_multisig(pubkeys, m) + h = sha256(script).digest() + return ripemd160(h).digest() + + +# returns a sha256() hash of a multisig script used in native P2WSH +def output_script_multisig_p2wsh(pubkeys, m) -> HashWriter: + for pubkey in pubkeys: + if len(pubkey) != 33: + raise Exception # only compressed public keys are allowed for P2WSH + script = script_multisig(pubkeys, m) + return sha256(script).digest() + + +def script_multisig(pubkeys, m) -> bytes: n = len(pubkeys) if n < 1 or n > 15: raise Exception if m < 1 or m > 15: raise Exception - h = HashWriter(sha256) - h.append(0x50 + m) + w = bytearray() + w.append(0x50 + m) # numbers 1 to 16 are pushed as 0x50 + value for p in pubkeys: - h.append(len(p)) # OP_PUSH length (33 for compressed) - write_bytes(h, p) - h.append(0x50 + n) - h.append(0xAE) # OP_CHECKMULTISIG - return h + append_pubkey(w, p) + w.append(0x50 + n) + w.append(0xAE) # OP_CHECKMULTISIG + return w # -------------------------- Others -------------------------- @@ -131,10 +167,14 @@ def output_script_paytoopreturn(data: bytes) -> bytearray: # === helpers -def append_signature_and_pubkey(w: bytearray, pubkey: bytes, signature: bytes, sighash: int) -> bytearray: +def append_signature(w: bytearray, signature: bytes, sighash: int) -> bytearray: write_op_push(w, len(signature) + 1) write_bytes(w, signature) w.append(sighash) + return w + + +def append_pubkey(w: bytearray, pubkey: bytes) -> bytearray: write_op_push(w, len(pubkey)) write_bytes(w, pubkey) return w diff --git a/src/apps/wallet/sign_tx/signing.py b/src/apps/wallet/sign_tx/signing.py index b51c0113c..71c4aa875 100644 --- a/src/apps/wallet/sign_tx/signing.py +++ b/src/apps/wallet/sign_tx/signing.py @@ -45,7 +45,6 @@ class SigningError(ValueError): # - ask for confirmations # - check fee async def check_tx_fee(tx: SignTx, root): - coin = coins.by_name(tx.coin_name) # h_first is used to make sure the inputs and outputs streamed in Phase 1 @@ -97,7 +96,7 @@ async def check_tx_fee(tx: SignTx, root): segwit[i] = True segwit_in += txi.amount total_in += txi.amount - elif txi.script_type == InputScriptType.SPENDADDRESS: + elif txi.script_type in [InputScriptType.SPENDADDRESS, InputScriptType.SPENDMULTISIG]: segwit[i] = False total_in += await get_prevtx_output_value( tx_req, txi.prev_hash, txi.prev_index) @@ -142,7 +141,6 @@ async def check_tx_fee(tx: SignTx, root): async def sign_tx(tx: SignTx, root): - tx = sanitize_sign_tx(tx) # Phase 1 @@ -239,8 +237,16 @@ async def sign_tx(tx: SignTx, root): txi_sign = txi key_sign = node_derive(root, txi.address_n) key_sign_pub = key_sign.public_key() - txi_sign.script_sig = output_script_p2pkh( - ecdsa_hash_pubkey(key_sign_pub)) + # for the signing process the script_sig is equal + # to the scriptPubKey (P2PKH) or a redeem script (P2SH) + if txi_sign.script_type == InputScriptType.SPENDMULTISIG: + txi_sign.script_sig = script_multisig(multisig_get_pubkeys(txi_sign.multisig), txi_sign.multisig.m) + elif txi_sign.script_type == InputScriptType.SPENDADDRESS: + txi_sign.script_sig = output_script_p2pkh( + ecdsa_hash_pubkey(key_sign_pub)) + else: + raise SigningError(FailureType.ProcessError, + 'Unknown transaction type') else: txi.script_sig = bytes() write_tx_input(h_sign, txi) @@ -405,7 +411,8 @@ def get_tx_header(tx: SignTx, segwit=False): def get_p2wpkh_witness(coin: CoinType, signature: bytes, pubkey: bytes): w = bytearray_with_cap(1 + 5 + len(signature) + 1 + 5 + len(pubkey)) write_varint(w, 0x02) # num of segwit items, in P2WPKH it's always 2 - append_signature_and_pubkey(w, pubkey, signature, get_hash_type(coin)) + append_signature(w, signature, get_hash_type(coin)) + append_pubkey(w, pubkey) return w @@ -414,7 +421,6 @@ def get_p2wpkh_witness(coin: CoinType, signature: bytes, pubkey: bytes): def output_derive_script(o: TxOutputType, coin: CoinType, root) -> bytes: - if o.script_type == OutputScriptType.PAYTOOPRETURN: if o.amount != 0: raise SigningError(FailureType.DataError, @@ -486,6 +492,9 @@ def input_derive_script(coin: CoinType, i: TxInputType, pubkey: bytes, signature return input_script_p2wpkh_in_p2sh(ecdsa_hash_pubkey(pubkey)) elif i.script_type == InputScriptType.SPENDWITNESS: # native p2wpkh or p2wsh return input_script_native_p2wpkh_or_p2wsh() + # mutlisig + elif i.script_type == InputScriptType.SPENDMULTISIG: + return input_script_multisig(signature, i.multisig.signatures, multisig_get_pubkeys(i.multisig), i.multisig.m) else: raise SigningError(FailureType.ProcessError, 'Invalid script type') diff --git a/src/apps/wallet/sign_tx/tx_weight_calculator.py b/src/apps/wallet/sign_tx/tx_weight_calculator.py index 88655838f..e36861496 100644 --- a/src/apps/wallet/sign_tx/tx_weight_calculator.py +++ b/src/apps/wallet/sign_tx/tx_weight_calculator.py @@ -55,7 +55,7 @@ class TxWeightCalculator: if i.multisig: multisig_script_size = ( _TXSIZE_MULTISIGSCRIPT + - i.multisig.pubkeys_count * (1 + _TXSIZE_PUBKEY)) + len(i.multisig.pubkeys) * (1 + _TXSIZE_PUBKEY)) input_script_size = ( 1 + # the OP_FALSE bug in multisig i.multisig.m * (1 + _TXSIZE_SIGNATURE) +