mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-01-22 05:10:56 +00:00
singing: multisig
This commit is contained in:
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
|
||||
|
24
src/apps/wallet/sign_tx/multisig.py
Normal file
24
src/apps/wallet/sign_tx/multisig.py
Normal file
@ -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…
Reference in New Issue
Block a user