diff --git a/core/tests/common.py b/core/tests/common.py index fce79b33c..d9c38ee02 100644 --- a/core/tests/common.py +++ b/core/tests/common.py @@ -7,6 +7,11 @@ from ubinascii import hexlify, unhexlify # noqa: F401 import unittest # noqa: F401 from trezor import utils # noqa: F401 +from apps.common import HARDENED + + +def H_(x: int) -> int: + return x | HARDENED def await_result(task: Awaitable) -> Any: diff --git a/core/tests/test_apps.bitcoin.approver.py b/core/tests/test_apps.bitcoin.approver.py new file mode 100644 index 000000000..7fec4a4bd --- /dev/null +++ b/core/tests/test_apps.bitcoin.approver.py @@ -0,0 +1,146 @@ +from common import unittest, await_result, H_ + +from trezor import wire +from trezor.messages.AuthorizeCoinJoin import AuthorizeCoinJoin +from trezor.messages.TxInputType import TxInputType +from trezor.messages.TxOutputType import TxOutputType +from trezor.messages.SignTx import SignTx +from trezor.messages import InputScriptType, OutputScriptType + +from apps.common import coins +from apps.bitcoin.authorization import CoinJoinAuthorization +from apps.bitcoin.sign_tx.approvers import CoinJoinApprover + + +class TestApprover(unittest.TestCase): + + def setUp(self): + self.coin = coins.by_name('Bitcoin') + self.fee_per_anonymity_percent = 0.003 + + self.msg_auth = AuthorizeCoinJoin( + coordinator="www.example.com", + max_total_fee=40000, + fee_per_anonymity=self.fee_per_anonymity_percent * 10**9, + address_n=[H_(84), H_(0), H_(0)], + coin_name=self.coin.coin_name, + script_type=InputScriptType.SPENDWITNESS, + ) + + def test_coinjoin_lots_of_inputs(self): + denomination = 10000000 + + # Other's inputs. + inputs = [ + TxInputType( + amount=denomination + 1000000 * (i + 1), + script_type=InputScriptType.EXTERNAL, + ) for i in range(99) + ] + + # Our input. + inputs.insert( + 30, + TxInputType( + address_n=[H_(84), H_(0), H_(0), 0, 1], + amount=denomination + 1000000, + script_type=InputScriptType.SPENDWITNESS, + ) + ) + + # Other's CoinJoined outputs. + outputs = [ + TxOutputType( + amount=denomination, + script_type=OutputScriptType.PAYTOWITNESS, + ) for i in range(99) + ] + + # Our CoinJoined output. + outputs.insert( + 40, + TxOutputType( + address_n=[H_(84), H_(0), H_(0), 0, 2], + amount=denomination, + script_type=OutputScriptType.PAYTOWITNESS, + ) + ) + + coordinator_fee = self.fee_per_anonymity_percent / 100 * len(outputs) * denomination + fees = coordinator_fee + 10000 + total_coordinator_fee = coordinator_fee * len(outputs) + + # Other's change-outputs. + outputs.extend( + TxOutputType( + amount=1000000 * (i + 1) - fees, + script_type=OutputScriptType.PAYTOWITNESS, + ) for i in range(99) + ) + + # Our change-output. + outputs.append( + TxOutputType( + address_n=[H_(84), H_(0), H_(0), 1, 1], + amount=1000000 - fees, + script_type=OutputScriptType.PAYTOWITNESS, + ) + ) + + # Coordinator's output. + outputs.append( + TxOutputType( + amount=total_coordinator_fee, + script_type=OutputScriptType.PAYTOWITNESS, + ) + ) + + authorization = CoinJoinAuthorization(self.msg_auth, None, self.coin) + tx = SignTx(outputs_count=len(outputs), inputs_count=len(inputs), coin_name=self.coin.coin_name, lock_time=0) + approver = CoinJoinApprover(tx, self.coin, authorization) + + for txi in inputs: + if txi.script_type == InputScriptType.EXTERNAL: + approver.add_external_input(txi) + else: + await_result(approver.add_internal_input(txi, txi.amount)) + + for txo in outputs: + if txo.address_n: + approver.add_change_output(txo, script_pubkey=bytes(22)) + else: + await_result(approver.add_external_output(txo, script_pubkey=bytes(22))) + + await_result(approver.approve_tx()) + + def test_coinjoin_input_account_depth_mismatch(self): + authorization = CoinJoinAuthorization(self.msg_auth, None, self.coin) + tx = SignTx(outputs_count=201, inputs_count=100, coin_name=self.coin.coin_name, lock_time=0) + approver = CoinJoinApprover(tx, self.coin, authorization) + + txi = TxInputType( + address_n=[H_(49), H_(0), H_(0), 0], + amount=10000000, + script_type=InputScriptType.SPENDWITNESS + ) + + with self.assertRaises(wire.ProcessError): + await_result(approver.add_internal_input(txi, txi.amount)) + + def test_coinjoin_input_account_path_mismatch(self): + authorization = CoinJoinAuthorization(self.msg_auth, None, self.coin) + tx = SignTx(outputs_count=201, inputs_count=100, coin_name=self.coin.coin_name, lock_time=0) + approver = CoinJoinApprover(tx, self.coin, authorization) + + txi = TxInputType( + address_n=[H_(49), H_(0), H_(0), 0, 2], + amount=10000000, + script_type=InputScriptType.SPENDWITNESS + ) + + with self.assertRaises(wire.ProcessError): + await_result(approver.add_internal_input(txi, txi.amount)) + + +if __name__ == '__main__': + unittest.main() diff --git a/core/tests/test_apps.bitcoin.authorization.py b/core/tests/test_apps.bitcoin.authorization.py new file mode 100644 index 000000000..fa2b10819 --- /dev/null +++ b/core/tests/test_apps.bitcoin.authorization.py @@ -0,0 +1,111 @@ +from common import unittest, H_ + +from trezor.messages.AuthorizeCoinJoin import AuthorizeCoinJoin +from trezor.messages.GetOwnershipProof import GetOwnershipProof +from trezor.messages.SignTx import SignTx +from trezor.messages import InputScriptType + +from apps.common import coins +from apps.bitcoin.authorization import CoinJoinAuthorization + +_ROUND_ID_LEN = 32 + + +class TestAuthorization(unittest.TestCase): + + coin = coins.by_name('Bitcoin') + + def setUp(self): + self.msg_auth = AuthorizeCoinJoin( + coordinator="www.example.com", + max_total_fee=40000, + fee_per_anonymity=0.003 * 10**9, + address_n=[H_(84), H_(0), H_(0)], + coin_name=self.coin.coin_name, + script_type=InputScriptType.SPENDWITNESS, + ) + + self.authorization = CoinJoinAuthorization(self.msg_auth, None, self.coin) + + def test_ownership_proof_account_depth_mismatch(self): + # Account depth mismatch. + msg = GetOwnershipProof( + address_n=[H_(84), H_(0), H_(0), 1], + coin_name=self.coin.coin_name, + script_type=InputScriptType.SPENDWITNESS, + user_confirmation=True, + commitment_data=b"www.example.com" + int.to_bytes(1, _ROUND_ID_LEN, "big"), + ) + + self.assertFalse(self.authorization.check_get_ownership_proof(msg)) + + def test_ownership_proof_account_path_mismatch(self): + # Account path mismatch. + msg = GetOwnershipProof( + address_n=[H_(49), H_(0), H_(0), 1, 2], + coin_name=self.coin.coin_name, + script_type=InputScriptType.SPENDWITNESS, + user_confirmation=True, + commitment_data=b"www.example.com" + int.to_bytes(1, _ROUND_ID_LEN, "big"), + ) + + self.assertFalse(self.authorization.check_get_ownership_proof(msg)) + + def test_ownership_proof_coordinator_mismatch(self): + # Coordinator name mismatch. + msg = GetOwnershipProof( + address_n=[H_(84), H_(0), H_(0), 1, 2], + coin_name=self.coin.coin_name, + script_type=InputScriptType.SPENDWITNESS, + user_confirmation=True, + commitment_data=b"www.example.org" + int.to_bytes(1, _ROUND_ID_LEN, "big"), + ) + + self.assertFalse(self.authorization.check_get_ownership_proof(msg)) + + def test_ownership_proof_wrong_round_id(self): + # Wrong round ID length. + msg = GetOwnershipProof( + address_n=[H_(84), H_(0), H_(0), 1, 2], + coin_name=self.coin.coin_name, + script_type=InputScriptType.SPENDWITNESS, + user_confirmation=True, + commitment_data=b"www.example.com" + int.to_bytes(1, _ROUND_ID_LEN - 1, "big"), + ) + + self.assertFalse(self.authorization.check_get_ownership_proof(msg)) + + msg = GetOwnershipProof( + address_n=[H_(84), H_(0), H_(0), 1, 2], + coin_name=self.coin.coin_name, + script_type=InputScriptType.SPENDWITNESS, + user_confirmation=True, + commitment_data=b"www.example.com" + int.to_bytes(1, _ROUND_ID_LEN + 1, "big"), + ) + + self.assertFalse(self.authorization.check_get_ownership_proof(msg)) + + def test_authorize_ownership_proof(self): + + msg = GetOwnershipProof( + address_n=[H_(84), H_(0), H_(0), 1, 2], + coin_name=self.coin.coin_name, + script_type=InputScriptType.SPENDWITNESS, + user_confirmation=True, + commitment_data=b"www.example.com" + int.to_bytes(1, _ROUND_ID_LEN, "big"), + ) + + self.assertTrue(self.authorization.check_get_ownership_proof(msg)) + + def test_approve_sign_tx(self): + + msg = SignTx(outputs_count=10, inputs_count=21, coin_name=self.coin.coin_name, lock_time=0) + + self.assertTrue(self.authorization.approve_sign_tx(msg, 10000)) + self.assertTrue(self.authorization.approve_sign_tx(msg, 20000)) + self.assertFalse(self.authorization.approve_sign_tx(msg, 10001)) + self.assertTrue(self.authorization.approve_sign_tx(msg, 10000)) + + +if __name__ == '__main__': + unittest.main()