diff --git a/src/apps/wallet/sign_tx/segwit_bip143.py b/src/apps/wallet/sign_tx/segwit_bip143.py new file mode 100644 index 0000000000..846d37b25b --- /dev/null +++ b/src/apps/wallet/sign_tx/segwit_bip143.py @@ -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) diff --git a/src/apps/wallet/sign_tx/signing.py b/src/apps/wallet/sign_tx/signing.py index ae5ab8fe6c..24f34a3ba7 100644 --- a/src/apps/wallet/sign_tx/signing.py +++ b/src/apps/wallet/sign_tx/signing.py @@ -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') diff --git a/tests/test_apps.wallet.segwit.bip143.py b/tests/test_apps.wallet.segwit.bip143.py new file mode 100644 index 0000000000..514b4db8e9 --- /dev/null +++ b/tests/test_apps.wallet.segwit.bip143.py @@ -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()