From ef5994d9f3783cb83edcc6373c63f552c6a5e0cf Mon Sep 17 00:00:00 2001 From: Andrew Kozlik Date: Wed, 1 Dec 2021 14:19:04 +0100 Subject: [PATCH] feat(core): Support ownership proofs for Taproot addresses. --- core/.changelog.d/1944.added | 1 + core/src/apps/bitcoin/ownership.py | 2 + core/src/apps/bitcoin/scripts.py | 4 +- .../test_apps.bitcoin.ownership_proof.py | 39 ++++++++++++++++++- 4 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 core/.changelog.d/1944.added diff --git a/core/.changelog.d/1944.added b/core/.changelog.d/1944.added new file mode 100644 index 000000000..4ed3d34ef --- /dev/null +++ b/core/.changelog.d/1944.added @@ -0,0 +1 @@ +Support ownership proofs for Taproot addresses. diff --git a/core/src/apps/bitcoin/ownership.py b/core/src/apps/bitcoin/ownership.py index 83ca4efa7..40bfedb4c 100644 --- a/core/src/apps/bitcoin/ownership.py +++ b/core/src/apps/bitcoin/ownership.py @@ -56,6 +56,8 @@ def generate_proof( InputScriptType.SPENDP2SHWITNESS, ): signature = common.ecdsa_sign(node, sighash.digest()) + elif script_type == InputScriptType.SPENDTAPROOT: + signature = common.bip340_sign(node, sighash.digest()) else: raise wire.DataError("Unsupported script type.") public_key = node.public_key() diff --git a/core/src/apps/bitcoin/scripts.py b/core/src/apps/bitcoin/scripts.py index 89524b8e6..2514caeea 100644 --- a/core/src/apps/bitcoin/scripts.py +++ b/core/src/apps/bitcoin/scripts.py @@ -609,7 +609,9 @@ def write_bip322_signature_proof( w, script_type, multisig, coin, SigHashType.SIGHASH_ALL, public_key, signature ) - if script_type in common.SEGWIT_INPUT_SCRIPT_TYPES: + if script_type == InputScriptType.SPENDTAPROOT: + write_witness_p2tr(w, signature, SigHashType.SIGHASH_ALL_TAPROOT) + elif script_type in common.SEGWIT_INPUT_SCRIPT_TYPES: if multisig: # find the place of our signature based on the public key signature_index = multisig_pubkey_index(multisig, public_key) diff --git a/core/tests/test_apps.bitcoin.ownership_proof.py b/core/tests/test_apps.bitcoin.ownership_proof.py index 811c83bea..856d921f6 100644 --- a/core/tests/test_apps.bitcoin.ownership_proof.py +++ b/core/tests/test_apps.bitcoin.ownership_proof.py @@ -8,7 +8,7 @@ from apps.common import coins from apps.common.keychain import Keychain from apps.common.paths import HARDENED, AlwaysMatchingSchema from apps.bitcoin import ownership, scripts -from apps.bitcoin.addresses import address_p2wpkh, address_p2wpkh_in_p2sh, address_multisig_p2wsh, address_multisig_p2wsh_in_p2sh, address_multisig_p2sh +from apps.bitcoin.addresses import address_p2tr, address_p2wpkh, address_p2wpkh_in_p2sh, address_multisig_p2wsh, address_multisig_p2wsh_in_p2sh, address_multisig_p2sh from apps.bitcoin.multisig import multisig_get_pubkeys @@ -67,6 +67,32 @@ class TestOwnershipProof(unittest.TestCase): self.assertEqual(proof, unhexlify("534c0019000192caf0b8daf78f1d388dbbceaec34bd2dabc31b217e32343663667f6694a3f4617160014e0cffbee1925a411844f44c3b8d81365ab51d03602483045022100a37330dca699725db613dd1b30059843d1248340642162a0adef114509c9849402201126c9044b998065d40b44fd2399b52c409794bbc3bfdd358cd5fb450c94316d012103a961687895a78da9aef98eed8e1f2a3e91cfb69d2f3cf11cbd0bb1773d951928")) self.assertFalse(ownership.verify_nonownership(proof, script_pubkey, commitment_data, keychain, coin)) + def test_p2tr_gen_proof(self): + coin = coins.by_name('Bitcoin') + seed = bip39.seed(' '.join(['all'] * 12), '') + keychain = Keychain(seed, coin.curve_name, [AlwaysMatchingSchema], slip21_namespaces=[[b"SLIP-0019"]]) + commitment_data = b"" + + node = keychain.derive([86 | HARDENED, 0 | HARDENED, 0 | HARDENED, 1, 0]) + address = address_p2tr(node.public_key(), coin) + script_pubkey = scripts.output_derive_script(address, coin) + ownership_id = ownership.get_identifier(script_pubkey, keychain) + self.assertEqual(ownership_id, unhexlify("dc18066224b9e30e306303436dc18ab881c7266c13790350a3fe415e438135ec")) + + proof, signature = ownership.generate_proof( + node=node, + script_type=InputScriptType.SPENDTAPROOT, + multisig=None, + coin=coin, + user_confirmed=False, + ownership_ids=[ownership_id], + script_pubkey=script_pubkey, + commitment_data=commitment_data, + ) + self.assertEqual(signature, unhexlify("6cd08474ea019c9ab4b9b7b76ec03c4dd4db76abc3a460434a91cfc1b190174949eb7111c8e762407730a215421a0da0b5e01f48de62d7ccea0abea046e2a496")) + self.assertEqual(proof, unhexlify("534c00190001dc18066224b9e30e306303436dc18ab881c7266c13790350a3fe415e438135ec0001406cd08474ea019c9ab4b9b7b76ec03c4dd4db76abc3a460434a91cfc1b190174949eb7111c8e762407730a215421a0da0b5e01f48de62d7ccea0abea046e2a496")) + self.assertFalse(ownership.verify_nonownership(proof, script_pubkey, commitment_data, keychain, coin)) + def test_p2pkh_gen_proof(self): coin = coins.by_name('Bitcoin') seed = bip39.seed(' '.join(['all'] * 12), 'TREZOR') @@ -104,6 +130,17 @@ class TestOwnershipProof(unittest.TestCase): proof = unhexlify("534c00190001a122407efc198211c81af4450f40b235d54775efd934d16b9e31c6ce9bad57070002483045022100e5eaf2cb0a473b4545115c7b85323809e75cb106175ace38129fd62323d73df30220363dbc7acb7afcda022b1f8d97acb8f47c42043cfe0595583aa26e30bc8b3bb50121032ef68318c8f6aaa0adec0199c69901f0db7d3485eb38d9ad235221dc3d61154b") self.assertTrue(ownership.verify_nonownership(proof, script_pubkey, commitment_data, keychain, coin)) + def test_p2tr_verify_proof(self): + coin = coins.by_name('Bitcoin') + seed = bip39.seed(' '.join(['all'] * 12), 'TREZOR') + keychain = Keychain(seed, coin.curve_name, [AlwaysMatchingSchema], slip21_namespaces=[[b"SLIP-0019"]]) + commitment_data = b"" + + # Proof for "all all ... all" seed without passphrase. + script_pubkey = unhexlify("51204102897557de0cafea0a8401ea5b59668eccb753e4b100aebe6a19609f3cc79f") + proof = unhexlify("534c00190001dc18066224b9e30e306303436dc18ab881c7266c13790350a3fe415e438135ec0001406cd08474ea019c9ab4b9b7b76ec03c4dd4db76abc3a460434a91cfc1b190174949eb7111c8e762407730a215421a0da0b5e01f48de62d7ccea0abea046e2a496") + self.assertTrue(ownership.verify_nonownership(proof, script_pubkey, commitment_data, keychain, coin)) + def test_p2wsh_gen_proof(self): coin = coins.by_name('Bitcoin') seed = bip39.seed(' '.join(['all'] * 12), '')