mirror of
https://github.com/trezor/trezor-firmware.git
synced 2025-07-06 06:42:33 +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 address_type
|
||||||
from apps.common import coins
|
from apps.common import coins
|
||||||
|
from apps.wallet.sign_tx.segwit_bip143 import *
|
||||||
from apps.wallet.sign_tx.writers import *
|
from apps.wallet.sign_tx.writers import *
|
||||||
|
|
||||||
|
|
||||||
# Machine instructions
|
# Machine instructions
|
||||||
# ===
|
# ===
|
||||||
|
|
||||||
@ -137,7 +137,10 @@ def sanitize_tx_binoutput(tx: TransactionType) -> TxOutputBinType:
|
|||||||
# ===
|
# ===
|
||||||
|
|
||||||
# Phase 1
|
# 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)
|
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
|
# 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
|
# tx, as the SignTx info is streamed only once
|
||||||
h_first = HashWriter(sha256) # not a real tx hash
|
h_first = HashWriter(sha256) # not a real tx hash
|
||||||
|
bip143 = Bip143()
|
||||||
|
|
||||||
txo_bin = TxOutputBinType()
|
txo_bin = TxOutputBinType()
|
||||||
tx_req = TxRequest()
|
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)
|
txi = await request_tx_input(tx_req, i)
|
||||||
write_tx_input_check(h_first, txi)
|
write_tx_input_check(h_first, txi)
|
||||||
if segwit:
|
if segwit:
|
||||||
|
# Add I to segwit hash_prevouts, hash_sequence
|
||||||
|
bip143.add_prevouts(txi)
|
||||||
|
bip143.add_sequence(txi)
|
||||||
total_in += txi.amount
|
total_in += txi.amount
|
||||||
else:
|
else:
|
||||||
total_in += await get_prevtx_output_value(
|
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.amount = txo.amount
|
||||||
txo_bin.script_pubkey = output_derive_script(txo, coin, root)
|
txo_bin.script_pubkey = output_derive_script(txo, coin, root)
|
||||||
write_tx_output(h_first, txo_bin)
|
write_tx_output(h_first, txo_bin)
|
||||||
|
bip143.add_output(txo_bin)
|
||||||
total_out += txo_bin.amount
|
total_out += txo_bin.amount
|
||||||
|
|
||||||
fee = total_in - total_out
|
fee = total_in - total_out
|
||||||
@ -195,19 +203,16 @@ async def check_tx_fee(tx: SignTx, root, segwit=False):
|
|||||||
raise SigningError(FailureType.ActionCancelled,
|
raise SigningError(FailureType.ActionCancelled,
|
||||||
'Total cancelled')
|
'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)
|
tx = sanitize_sign_tx(tx)
|
||||||
|
|
||||||
# Phase 1
|
# 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
|
# Phase 2
|
||||||
# - sign inputs
|
# - sign inputs
|
||||||
@ -230,55 +235,64 @@ async def sign_tx(tx: SignTx, root):
|
|||||||
|
|
||||||
write_varint(h_sign, tx.inputs_count)
|
write_varint(h_sign, tx.inputs_count)
|
||||||
|
|
||||||
for i in range(tx.inputs_count):
|
if segwit:
|
||||||
# STAGE_REQUEST_4_INPUT
|
txi = await request_tx_input(tx_req, i_sign)
|
||||||
txi = await request_tx_input(tx_req, i)
|
# if hashType != ANYONE_CAN_PAY ? todo
|
||||||
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)
|
|
||||||
|
|
||||||
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):
|
write_varint(h_sign, 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, 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
|
write_uint32(h_sign, 0x00000001) # SIGHASH_ALL hash_type
|
||||||
if get_tx_hash(h_first, False) != get_tx_hash(h_second, False):
|
|
||||||
raise SigningError(FailureType.ProcessError,
|
|
||||||
'Transaction has changed during signing')
|
|
||||||
|
|
||||||
# compute the signature from the tx digest
|
# check the control digests
|
||||||
signature = ecdsa_sign(key_sign, get_tx_hash(h_sign, True))
|
if get_tx_hash(h_first, False) != get_tx_hash(h_second, False):
|
||||||
tx_ser.signature_index = i_sign
|
raise SigningError(FailureType.ProcessError,
|
||||||
tx_ser.signature = signature
|
'Transaction has changed during signing')
|
||||||
|
|
||||||
# serialize input with correct signature
|
# compute the signature from the tx digest
|
||||||
txi_sign.script_sig = input_derive_script(
|
signature = ecdsa_sign(key_sign, get_tx_hash(h_sign, True))
|
||||||
txi_sign, key_sign_pub, signature)
|
tx_ser.signature_index = i_sign
|
||||||
w_txi_sign = bytearray_with_cap(
|
tx_ser.signature = signature
|
||||||
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
|
# 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):
|
for o in range(tx.outputs_count):
|
||||||
# STAGE_REQUEST_5_OUTPUT
|
# STAGE_REQUEST_5_OUTPUT
|
||||||
@ -401,6 +415,9 @@ def input_derive_script(i: TxInputType, pubkey: bytes, signature: bytes=None) ->
|
|||||||
else:
|
else:
|
||||||
return script_spendaddress_new(pubkey, signature)
|
return script_spendaddress_new(pubkey, signature)
|
||||||
|
|
||||||
|
if i.script_type == InputScriptType.SPENDP2SHWITNESS: # todo
|
||||||
|
return script_paytoaddress_new(ecdsa_hash_pubkey(pubkey))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise SigningError(FailureType.SyntaxError,
|
raise SigningError(FailureType.SyntaxError,
|
||||||
'Unknown input script type')
|
'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