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 trezor.messages import InputScriptType
|
||||||
|
|
||||||
from apps.wallet.sign_tx.scripts import *
|
from apps.wallet.sign_tx.scripts import *
|
||||||
|
from apps.wallet.sign_tx.multisig import *
|
||||||
|
|
||||||
# supported witness version for bech32 addresses
|
# supported witness version for bech32 addresses
|
||||||
_BECH32_WITVER = const(0x00)
|
_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):
|
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:
|
if addrtype is None:
|
||||||
raise AddressError(FailureType.ProcessError,
|
raise AddressError(FailureType.ProcessError,
|
||||||
'Multisig not enabled on this coin')
|
'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):
|
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:
|
if addrtype is None:
|
||||||
raise AddressError(FailureType.ProcessError,
|
raise AddressError(FailureType.ProcessError,
|
||||||
'Multisig not enabled on this coin')
|
'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):
|
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:
|
if addrtype is None:
|
||||||
raise AddressError(FailureType.ProcessError,
|
raise AddressError(FailureType.ProcessError,
|
||||||
'Multisig not enabled on this coin')
|
'Multisig not enabled on this coin')
|
||||||
return address_p2sh(digest, addrtype)
|
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:
|
def address_p2sh(redeem_script_hash: bytes, addrtype: int) -> str:
|
||||||
s = bytearray(21)
|
s = bytearray(21)
|
||||||
s[0] = addrtype
|
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.wallet.sign_tx.writers import *
|
||||||
from apps.common.hash_writer import HashWriter
|
from apps.common.hash_writer import HashWriter
|
||||||
|
|
||||||
|
from trezor.crypto.hashlib import sha256, ripemd160
|
||||||
|
|
||||||
# TX Scripts
|
# 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:
|
def input_script_p2pkh_or_p2sh(pubkey: bytes, signature: bytes, sighash: int) -> bytearray:
|
||||||
w = bytearray_with_cap(5 + len(signature) + 1 + 5 + len(pubkey))
|
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
|
return w
|
||||||
|
|
||||||
|
|
||||||
@ -100,21 +102,55 @@ def input_script_p2wpkh_in_p2sh(pubkeyhash: bytes) -> bytearray:
|
|||||||
|
|
||||||
# =============== Multisig ===============
|
# =============== 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)
|
n = len(pubkeys)
|
||||||
if n < 1 or n > 15:
|
if n < 1 or n > 15:
|
||||||
raise Exception
|
raise Exception
|
||||||
if m < 1 or m > 15:
|
if m < 1 or m > 15:
|
||||||
raise Exception
|
raise Exception
|
||||||
|
|
||||||
h = HashWriter(sha256)
|
w = bytearray()
|
||||||
h.append(0x50 + m)
|
w.append(0x50 + m) # numbers 1 to 16 are pushed as 0x50 + value
|
||||||
for p in pubkeys:
|
for p in pubkeys:
|
||||||
h.append(len(p)) # OP_PUSH length (33 for compressed)
|
append_pubkey(w, p)
|
||||||
write_bytes(h, p)
|
w.append(0x50 + n)
|
||||||
h.append(0x50 + n)
|
w.append(0xAE) # OP_CHECKMULTISIG
|
||||||
h.append(0xAE) # OP_CHECKMULTISIG
|
return w
|
||||||
return h
|
|
||||||
|
|
||||||
|
|
||||||
# -------------------------- Others --------------------------
|
# -------------------------- Others --------------------------
|
||||||
@ -131,10 +167,14 @@ def output_script_paytoopreturn(data: bytes) -> bytearray:
|
|||||||
|
|
||||||
# === helpers
|
# === 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_op_push(w, len(signature) + 1)
|
||||||
write_bytes(w, signature)
|
write_bytes(w, signature)
|
||||||
w.append(sighash)
|
w.append(sighash)
|
||||||
|
return w
|
||||||
|
|
||||||
|
|
||||||
|
def append_pubkey(w: bytearray, pubkey: bytes) -> bytearray:
|
||||||
write_op_push(w, len(pubkey))
|
write_op_push(w, len(pubkey))
|
||||||
write_bytes(w, pubkey)
|
write_bytes(w, pubkey)
|
||||||
return w
|
return w
|
||||||
|
@ -45,7 +45,6 @@ class SigningError(ValueError):
|
|||||||
# - ask for confirmations
|
# - ask for confirmations
|
||||||
# - check fee
|
# - check fee
|
||||||
async def check_tx_fee(tx: SignTx, root):
|
async def check_tx_fee(tx: SignTx, root):
|
||||||
|
|
||||||
coin = coins.by_name(tx.coin_name)
|
coin = coins.by_name(tx.coin_name)
|
||||||
|
|
||||||
# h_first is used to make sure the inputs and outputs streamed in Phase 1
|
# 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[i] = True
|
||||||
segwit_in += txi.amount
|
segwit_in += txi.amount
|
||||||
total_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
|
segwit[i] = False
|
||||||
total_in += await get_prevtx_output_value(
|
total_in += await get_prevtx_output_value(
|
||||||
tx_req, txi.prev_hash, txi.prev_index)
|
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):
|
async def sign_tx(tx: SignTx, root):
|
||||||
|
|
||||||
tx = sanitize_sign_tx(tx)
|
tx = sanitize_sign_tx(tx)
|
||||||
|
|
||||||
# Phase 1
|
# Phase 1
|
||||||
@ -239,8 +237,16 @@ async def sign_tx(tx: SignTx, root):
|
|||||||
txi_sign = txi
|
txi_sign = txi
|
||||||
key_sign = node_derive(root, txi.address_n)
|
key_sign = node_derive(root, txi.address_n)
|
||||||
key_sign_pub = key_sign.public_key()
|
key_sign_pub = key_sign.public_key()
|
||||||
txi_sign.script_sig = output_script_p2pkh(
|
# for the signing process the script_sig is equal
|
||||||
ecdsa_hash_pubkey(key_sign_pub))
|
# 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:
|
else:
|
||||||
txi.script_sig = bytes()
|
txi.script_sig = bytes()
|
||||||
write_tx_input(h_sign, txi)
|
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):
|
def get_p2wpkh_witness(coin: CoinType, signature: bytes, pubkey: bytes):
|
||||||
w = bytearray_with_cap(1 + 5 + len(signature) + 1 + 5 + len(pubkey))
|
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
|
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
|
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:
|
def output_derive_script(o: TxOutputType, coin: CoinType, root) -> bytes:
|
||||||
|
|
||||||
if o.script_type == OutputScriptType.PAYTOOPRETURN:
|
if o.script_type == OutputScriptType.PAYTOOPRETURN:
|
||||||
if o.amount != 0:
|
if o.amount != 0:
|
||||||
raise SigningError(FailureType.DataError,
|
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))
|
return input_script_p2wpkh_in_p2sh(ecdsa_hash_pubkey(pubkey))
|
||||||
elif i.script_type == InputScriptType.SPENDWITNESS: # native p2wpkh or p2wsh
|
elif i.script_type == InputScriptType.SPENDWITNESS: # native p2wpkh or p2wsh
|
||||||
return input_script_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:
|
else:
|
||||||
raise SigningError(FailureType.ProcessError, 'Invalid script type')
|
raise SigningError(FailureType.ProcessError, 'Invalid script type')
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ class TxWeightCalculator:
|
|||||||
if i.multisig:
|
if i.multisig:
|
||||||
multisig_script_size = (
|
multisig_script_size = (
|
||||||
_TXSIZE_MULTISIGSCRIPT +
|
_TXSIZE_MULTISIGSCRIPT +
|
||||||
i.multisig.pubkeys_count * (1 + _TXSIZE_PUBKEY))
|
len(i.multisig.pubkeys) * (1 + _TXSIZE_PUBKEY))
|
||||||
input_script_size = (
|
input_script_size = (
|
||||||
1 + # the OP_FALSE bug in multisig
|
1 + # the OP_FALSE bug in multisig
|
||||||
i.multisig.m * (1 + _TXSIZE_SIGNATURE) +
|
i.multisig.m * (1 + _TXSIZE_SIGNATURE) +
|
||||||
|
Loading…
Reference in New Issue
Block a user