singing: multisig

pull/25/head
Tomas Susanka 6 years ago committed by Jan Pochyla
parent f36b475109
commit b5e26f1d44

@ -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

@ -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]

@ -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

@ -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')

@ -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) +

Loading…
Cancel
Save