wallet/signing: bip143 transactions hashing

pull/25/head
Tomas Susanka 7 years ago
parent 81ec2f3c65
commit bcef961059

@ -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)
write_varint(h_sign, tx.outputs_count)
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, tx.lock_time)
write_uint32(h_sign, 0x00000001) # SIGHASH_ALL hash_type
# 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')
# 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
# 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
if segwit:
txi = await request_tx_input(tx_req, i_sign)
# if hashType != ANYONE_CAN_PAY ? todo
tx_req.serialized = tx_ser
# 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)
write_varint(h_sign, tx.outputs_count)
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, tx.lock_time)
write_uint32(h_sign, 0x00000001) # SIGHASH_ALL hash_type
# 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')
# 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
# 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')

@ -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…
Cancel
Save