mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-01-27 15:51:02 +00:00
wallet/signing: bip143 transactions hashing
This commit is contained in:
parent
81ec2f3c65
commit
bcef961059
50
src/apps/wallet/sign_tx/segwit_bip143.py
Normal file
50
src/apps/wallet/sign_tx/segwit_bip143.py
Normal file
@ -0,0 +1,50 @@
|
||||
from trezor.crypto.hashlib import sha256
|
||||
from trezor.messages.SignTx import SignTx
|
||||
|
||||
|
||||
class Bip143:
|
||||
|
||||
def __init__(self):
|
||||
self.h_prevouts = HashWriter(sha256)
|
||||
self.h_sequence = HashWriter(sha256)
|
||||
self.h_outputs = HashWriter(sha256)
|
||||
|
||||
def add_prevouts(self, txi: TxInputType):
|
||||
write_bytes(self.h_prevouts, txi.prev_hash)
|
||||
write_uint32(self.h_prevouts, txi.prev_index)
|
||||
|
||||
def get_prevouts_hash(self) -> bytes:
|
||||
return get_tx_hash(self.h_prevouts, True)
|
||||
|
||||
def add_sequence(self, txi: TxInputType):
|
||||
write_uint32(self.h_sequence, txi.sequence)
|
||||
|
||||
def get_sequence_hash(self) -> bytes:
|
||||
return get_tx_hash(self.h_sequence, True)
|
||||
|
||||
def add_output(self, txo_bin: TxOutputBinType):
|
||||
write_tx_output(self.h_outputs, txo_bin)
|
||||
|
||||
def get_outputs_hash(self) -> bytes:
|
||||
return get_tx_hash(self.h_outputs, True)
|
||||
|
||||
def preimage(self, tx: SignTx, txi: TxInputType, script_code) -> bytes:
|
||||
h_preimage = HashWriter(sha256)
|
||||
|
||||
write_uint32(h_preimage, tx.version) # nVersion
|
||||
write_bytes(h_preimage, bytearray(self.get_prevouts_hash())) # hashPrevouts
|
||||
write_bytes(h_preimage, bytearray(self.get_sequence_hash())) # hashSequence
|
||||
write_bytes(h_preimage, txi.prev_hash) # outpoint
|
||||
write_uint32(h_preimage, txi.prev_index) # outpoint
|
||||
|
||||
write_varint(h_preimage, len(script_code)) # scriptCode length
|
||||
write_bytes(h_preimage, bytearray(script_code)) # scriptCode
|
||||
|
||||
write_uint64(h_preimage, txi.amount) # amount
|
||||
write_uint32(h_preimage, txi.sequence) # nSequence
|
||||
|
||||
write_bytes(h_preimage, bytearray(self.get_outputs_hash())) # hashOutputs
|
||||
write_uint32(h_preimage, tx.lock_time) # nLockTime
|
||||
write_uint32(h_preimage, 0x00000001) # nHashType todo
|
||||
|
||||
return get_tx_hash(h_preimage, True)
|
@ -15,9 +15,9 @@ from trezor.messages import OutputScriptType, InputScriptType, FailureType
|
||||
|
||||
from apps.common import address_type
|
||||
from apps.common import coins
|
||||
from apps.wallet.sign_tx.segwit_bip143 import *
|
||||
from apps.wallet.sign_tx.writers import *
|
||||
|
||||
|
||||
# Machine instructions
|
||||
# ===
|
||||
|
||||
@ -137,7 +137,10 @@ def sanitize_tx_binoutput(tx: TransactionType) -> TxOutputBinType:
|
||||
# ===
|
||||
|
||||
# Phase 1
|
||||
async def check_tx_fee(tx: SignTx, root, segwit=False):
|
||||
# - check inputs, previous transactions, and outputs
|
||||
# - ask for confirmations
|
||||
# - check fee
|
||||
async def check_tx_fee(tx: SignTx, root, segwit):
|
||||
|
||||
coin = coins.by_name(tx.coin_name)
|
||||
|
||||
@ -145,6 +148,7 @@ async def check_tx_fee(tx: SignTx, root, segwit=False):
|
||||
# are the same as in Phase 2. it is thus not required to fully hash the
|
||||
# tx, as the SignTx info is streamed only once
|
||||
h_first = HashWriter(sha256) # not a real tx hash
|
||||
bip143 = Bip143()
|
||||
|
||||
txo_bin = TxOutputBinType()
|
||||
tx_req = TxRequest()
|
||||
@ -159,6 +163,9 @@ async def check_tx_fee(tx: SignTx, root, segwit=False):
|
||||
txi = await request_tx_input(tx_req, i)
|
||||
write_tx_input_check(h_first, txi)
|
||||
if segwit:
|
||||
# Add I to segwit hash_prevouts, hash_sequence
|
||||
bip143.add_prevouts(txi)
|
||||
bip143.add_sequence(txi)
|
||||
total_in += txi.amount
|
||||
else:
|
||||
total_in += await get_prevtx_output_value(
|
||||
@ -179,6 +186,7 @@ async def check_tx_fee(tx: SignTx, root, segwit=False):
|
||||
txo_bin.amount = txo.amount
|
||||
txo_bin.script_pubkey = output_derive_script(txo, coin, root)
|
||||
write_tx_output(h_first, txo_bin)
|
||||
bip143.add_output(txo_bin)
|
||||
total_out += txo_bin.amount
|
||||
|
||||
fee = total_in - total_out
|
||||
@ -195,19 +203,16 @@ async def check_tx_fee(tx: SignTx, root, segwit=False):
|
||||
raise SigningError(FailureType.ActionCancelled,
|
||||
'Total cancelled')
|
||||
|
||||
return h_first, tx_req, txo_bin
|
||||
return h_first, tx_req, txo_bin, bip143
|
||||
|
||||
|
||||
async def sign_tx(tx: SignTx, root):
|
||||
async def sign_tx(tx: SignTx, root, segwit=False):
|
||||
|
||||
tx = sanitize_sign_tx(tx)
|
||||
|
||||
# Phase 1
|
||||
# - check inputs, previous transactions, and outputs
|
||||
# - ask for confirmations
|
||||
# - check fee
|
||||
|
||||
h_first, tx_req, txo_bin = await check_tx_fee(tx, root)
|
||||
h_first, tx_req, txo_bin, bip143 = await check_tx_fee(tx, root, segwit)
|
||||
|
||||
# Phase 2
|
||||
# - sign inputs
|
||||
@ -230,55 +235,64 @@ async def sign_tx(tx: SignTx, root):
|
||||
|
||||
write_varint(h_sign, tx.inputs_count)
|
||||
|
||||
for i in range(tx.inputs_count):
|
||||
# STAGE_REQUEST_4_INPUT
|
||||
txi = await request_tx_input(tx_req, i)
|
||||
write_tx_input_check(h_second, txi)
|
||||
if i == i_sign:
|
||||
txi_sign = txi
|
||||
key_sign = node_derive(root, txi.address_n)
|
||||
key_sign_pub = key_sign.public_key()
|
||||
txi.script_sig = input_derive_script(txi, key_sign_pub)
|
||||
else:
|
||||
txi.script_sig = bytes()
|
||||
write_tx_input(h_sign, txi)
|
||||
if segwit:
|
||||
txi = await request_tx_input(tx_req, i_sign)
|
||||
# if hashType != ANYONE_CAN_PAY ? todo
|
||||
|
||||
write_varint(h_sign, tx.outputs_count)
|
||||
# todo: what to do with other types?
|
||||
script_code = input_derive_script(txi, coin, root)
|
||||
bip143.preimage(tx, txi, script_code)
|
||||
# Return serialized input chunk ? todo
|
||||
else:
|
||||
for i in range(tx.inputs_count):
|
||||
# STAGE_REQUEST_4_INPUT
|
||||
txi = await request_tx_input(tx_req, i)
|
||||
write_tx_input_check(h_second, txi)
|
||||
if i == i_sign:
|
||||
txi_sign = txi
|
||||
key_sign = node_derive(root, txi.address_n)
|
||||
key_sign_pub = key_sign.public_key()
|
||||
txi.script_sig = input_derive_script(txi, key_sign_pub)
|
||||
else:
|
||||
txi.script_sig = bytes()
|
||||
write_tx_input(h_sign, txi)
|
||||
|
||||
for o in range(tx.outputs_count):
|
||||
# STAGE_REQUEST_4_OUTPUT
|
||||
txo = await request_tx_output(tx_req, o)
|
||||
txo_bin.amount = txo.amount
|
||||
txo_bin.script_pubkey = output_derive_script(txo, coin, root)
|
||||
write_tx_output(h_second, txo_bin)
|
||||
write_tx_output(h_sign, txo_bin)
|
||||
write_varint(h_sign, tx.outputs_count)
|
||||
|
||||
write_uint32(h_sign, tx.lock_time)
|
||||
for o in range(tx.outputs_count):
|
||||
# STAGE_REQUEST_4_OUTPUT
|
||||
txo = await request_tx_output(tx_req, o)
|
||||
txo_bin.amount = txo.amount
|
||||
txo_bin.script_pubkey = output_derive_script(txo, coin, root)
|
||||
write_tx_output(h_second, txo_bin)
|
||||
write_tx_output(h_sign, txo_bin)
|
||||
|
||||
write_uint32(h_sign, 0x00000001) # SIGHASH_ALL hash_type
|
||||
write_uint32(h_sign, tx.lock_time)
|
||||
|
||||
# check the control digests
|
||||
if get_tx_hash(h_first, False) != get_tx_hash(h_second, False):
|
||||
raise SigningError(FailureType.ProcessError,
|
||||
'Transaction has changed during signing')
|
||||
write_uint32(h_sign, 0x00000001) # SIGHASH_ALL hash_type
|
||||
|
||||
# compute the signature from the tx digest
|
||||
signature = ecdsa_sign(key_sign, get_tx_hash(h_sign, True))
|
||||
tx_ser.signature_index = i_sign
|
||||
tx_ser.signature = signature
|
||||
# check the control digests
|
||||
if get_tx_hash(h_first, False) != get_tx_hash(h_second, False):
|
||||
raise SigningError(FailureType.ProcessError,
|
||||
'Transaction has changed during signing')
|
||||
|
||||
# serialize input with correct signature
|
||||
txi_sign.script_sig = input_derive_script(
|
||||
txi_sign, key_sign_pub, signature)
|
||||
w_txi_sign = bytearray_with_cap(
|
||||
len(txi_sign.prev_hash) + 4 + 5 + len(txi_sign.script_sig) + 4)
|
||||
if i_sign == 0: # serializing first input => prepend tx version and inputs count
|
||||
write_uint32(w_txi_sign, tx.version)
|
||||
write_varint(w_txi_sign, tx.inputs_count)
|
||||
write_tx_input(w_txi_sign, txi_sign)
|
||||
tx_ser.serialized_tx = w_txi_sign
|
||||
# compute the signature from the tx digest
|
||||
signature = ecdsa_sign(key_sign, get_tx_hash(h_sign, True))
|
||||
tx_ser.signature_index = i_sign
|
||||
tx_ser.signature = signature
|
||||
|
||||
tx_req.serialized = tx_ser
|
||||
# serialize input with correct signature
|
||||
txi_sign.script_sig = input_derive_script(
|
||||
txi_sign, key_sign_pub, signature)
|
||||
w_txi_sign = bytearray_with_cap(
|
||||
len(txi_sign.prev_hash) + 4 + 5 + len(txi_sign.script_sig) + 4)
|
||||
if i_sign == 0: # serializing first input => prepend tx version and inputs count
|
||||
write_uint32(w_txi_sign, tx.version)
|
||||
write_varint(w_txi_sign, tx.inputs_count)
|
||||
write_tx_input(w_txi_sign, txi_sign)
|
||||
tx_ser.serialized_tx = w_txi_sign
|
||||
|
||||
tx_req.serialized = tx_ser
|
||||
|
||||
for o in range(tx.outputs_count):
|
||||
# STAGE_REQUEST_5_OUTPUT
|
||||
@ -401,6 +415,9 @@ def input_derive_script(i: TxInputType, pubkey: bytes, signature: bytes=None) ->
|
||||
else:
|
||||
return script_spendaddress_new(pubkey, signature)
|
||||
|
||||
if i.script_type == InputScriptType.SPENDP2SHWITNESS: # todo
|
||||
return script_paytoaddress_new(ecdsa_hash_pubkey(pubkey))
|
||||
|
||||
else:
|
||||
raise SigningError(FailureType.SyntaxError,
|
||||
'Unknown input script type')
|
||||
|
84
tests/test_apps.wallet.segwit.bip143.py
Normal file
84
tests/test_apps.wallet.segwit.bip143.py
Normal file
@ -0,0 +1,84 @@
|
||||
from common import *
|
||||
|
||||
from apps.wallet.sign_tx.signing import *
|
||||
from apps.common import coins
|
||||
from trezor.messages.SignTx import SignTx
|
||||
from trezor.messages.TxInputType import TxInputType
|
||||
from trezor.messages.TxOutputType import TxOutputType
|
||||
from trezor.messages import OutputScriptType
|
||||
from trezor.crypto import bip32, bip39
|
||||
|
||||
|
||||
class TestSegwitBip143(unittest.TestCase):
|
||||
# pylint: disable=C0301
|
||||
|
||||
tx = SignTx(coin_name='Bitcoin', version=1, lock_time=0x00000492, inputs_count=1, outputs_count=2)
|
||||
inp1 = TxInputType(address_n=[0],
|
||||
prev_hash=unhexlify('db6b1b20aa0fd7b23880be2ecbd4a98130974cf4748fb66092ac4d3ceb1a5477'),
|
||||
prev_index=1,
|
||||
amount=1000000000, # 10 btc
|
||||
script_type=InputScriptType.SPENDP2SHWITNESS, # todo is this correct?
|
||||
sequence=0xfffffffe)
|
||||
out1 = TxOutputType(address='1Fyxts6r24DpEieygQiNnWxUdb18ANa5p7',
|
||||
amount=0x000000000bebb4b8,
|
||||
script_type=OutputScriptType.PAYTOWITNESS,
|
||||
address_n=None)
|
||||
out2 = TxOutputType(address='1Q5YjKVj5yQWHBBsyEBamkfph3cA6G9KK8',
|
||||
amount=0x000000002faf0800,
|
||||
script_type=OutputScriptType.PAYTOWITNESS,
|
||||
address_n=None)
|
||||
|
||||
def test_bip143_prevouts(self):
|
||||
|
||||
bip143 = Bip143()
|
||||
bip143.add_prevouts(self.inp1)
|
||||
self.assertEqual(hexlify(bip143.get_prevouts_hash()), b'b0287b4a252ac05af83d2dcef00ba313af78a3e9c329afa216eb3aa2a7b4613a')
|
||||
|
||||
def test_bip143_sequence(self):
|
||||
|
||||
bip143 = Bip143()
|
||||
bip143.add_sequence(self.inp1)
|
||||
self.assertEqual(hexlify(bip143.get_sequence_hash()), b'18606b350cd8bf565266bc352f0caddcf01e8fa789dd8a15386327cf8cabe198')
|
||||
|
||||
def test_bip143_outputs(self):
|
||||
|
||||
seed = bip39.seed('alcohol woman abuse must during monitor noble actual mixed trade anger aisle', '')
|
||||
root = bip32.from_seed(seed, 'secp256k1')
|
||||
coin = coins.by_name(self.tx.coin_name)
|
||||
|
||||
bip143 = Bip143()
|
||||
|
||||
for txo in [self.out1, self.out2]:
|
||||
txo_bin = TxOutputBinType()
|
||||
txo_bin.amount = txo.amount
|
||||
txo_bin.script_pubkey = output_derive_script(txo, coin, root)
|
||||
bip143.add_output(txo_bin)
|
||||
|
||||
self.assertEqual(hexlify(bip143.get_outputs_hash()),
|
||||
b'de984f44532e2173ca0d64314fcefe6d30da6f8cf27bafa706da61df8a226c83')
|
||||
|
||||
def test_bip143_preimage_testdata(self):
|
||||
|
||||
seed = bip39.seed('alcohol woman abuse must during monitor noble actual mixed trade anger aisle', '')
|
||||
root = bip32.from_seed(seed, 'secp256k1')
|
||||
coin = coins.by_name(self.tx.coin_name)
|
||||
|
||||
bip143 = Bip143()
|
||||
bip143.add_prevouts(self.inp1)
|
||||
bip143.add_sequence(self.inp1)
|
||||
for txo in [self.out1, self.out2]:
|
||||
txo_bin = TxOutputBinType()
|
||||
txo_bin.amount = txo.amount
|
||||
txo_bin.script_pubkey = output_derive_script(txo, coin, root)
|
||||
bip143.add_output(txo_bin)
|
||||
|
||||
# test data public key
|
||||
script_code = input_derive_script(self.inp1, unhexlify('03ad1d8e89212f0b92c74d23bb710c00662ad1470198ac48c43f7d6f93a2a26873'))
|
||||
self.assertEqual(hexlify(script_code), b'76a91479091972186c449eb1ded22b78e40d009bdf008988ac')
|
||||
result = bip143.preimage(self.tx, self.inp1, script_code)
|
||||
self.assertEqual(hexlify(result), b'64f3b0f4dd2bb3aa1ce8566d220cc74dda9df97d8490cc81d89d735c92e59fb6')
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
Loading…
Reference in New Issue
Block a user