aboutsummaryrefslogtreecommitdiff
path: root/test/functional/feature_taproot.py
diff options
context:
space:
mode:
authorPieter Wuille <pieter@wuille.net>2022-10-24 14:26:29 -0400
committerPieter Wuille <pieter@wuille.net>2022-11-21 14:00:43 -0500
commit5d413c8e793a439540d064d24fddfc868e1817d0 (patch)
tree0be2e576c9c292b2d547bc26a8d2e2e1a2dbe39a /test/functional/feature_taproot.py
parent1c5c951713a97511274299856f1d7c09f86bf2f0 (diff)
downloadbitcoin-5d413c8e793a439540d064d24fddfc868e1817d0.tar.xz
Add feature_taproot case involved invalid internal pubkey
Diffstat (limited to 'test/functional/feature_taproot.py')
-rwxr-xr-xtest/functional/feature_taproot.py47
1 files changed, 46 insertions, 1 deletions
diff --git a/test/functional/feature_taproot.py b/test/functional/feature_taproot.py
index cbb2e0338b..31a6b31225 100755
--- a/test/functional/feature_taproot.py
+++ b/test/functional/feature_taproot.py
@@ -96,7 +96,14 @@ from test_framework.util import (
assert_equal,
random_bytes,
)
-from test_framework.key import generate_privkey, compute_xonly_pubkey, sign_schnorr, tweak_add_privkey, ECKey
+from test_framework.key import (
+ generate_privkey,
+ compute_xonly_pubkey,
+ sign_schnorr,
+ tweak_add_privkey,
+ ECKey,
+ SECP256K1
+)
from test_framework.address import (
hash160,
program_to_witness,
@@ -661,6 +668,44 @@ def spenders_taproot_active():
# Test with signature with bit flipped.
add_spender(spenders, "sig/bitflip", tap=tap, key=secs[0], failure={"signature": bitflipper(default_signature)}, **ERR_SIG_SCHNORR)
+ # == Test involving an internal public key not on the curve ==
+
+ # X-only public keys are 32 bytes, but not every 32-byte array is a valid public key; only
+ # around 50% of them are. This does not affect users using correct software; these "keys" have
+ # no corresponding private key, and thus will never appear as output of key
+ # generation/derivation/tweaking.
+ #
+ # Using an invalid public key as P2TR output key makes the UTXO unspendable. Revealing an
+ # invalid public key as internal key in a P2TR script path spend also makes the spend invalid.
+ # These conditions are explicitly spelled out in BIP341.
+ #
+ # It is however hard to create test vectors for this, because it involves "guessing" how a
+ # hypothetical incorrect implementation deals with an obviously-invalid condition, and making
+ # sure that guessed behavior (accepting it in certain condition) doesn't occur.
+ #
+ # The test case added here tries to detect a very specific bug a verifier could have: if they
+ # don't verify whether or not a revealed internal public key in a script path spend is valid,
+ # and (correctly) implement output_key == tweak(internal_key, tweakval) but (incorrectly) treat
+ # tweak(invalid_key, tweakval) as equal the public key corresponding to private key tweakval.
+ # This may seem like a far-fetched edge condition to test for, but in fact, the BIP341 wallet
+ # pseudocode did exactly that (but obviously only triggerable by someone invoking the tweaking
+ # function with an invalid public key, which shouldn't happen).
+
+ # Generate an invalid public key
+ while True:
+ invalid_pub = random_bytes(32)
+ if not SECP256K1.is_x_coord(int.from_bytes(invalid_pub, 'big')):
+ break
+
+ # Implement a test case that detects validation logic which maps invalid public keys to the
+ # point at infinity in the tweaking logic.
+ tap = taproot_construct(invalid_pub, [("true", CScript([OP_1]))], treat_internal_as_infinity=True)
+ add_spender(spenders, "output/invalid_x", tap=tap, key_tweaked=tap.tweak, failure={"leaf": "true", "inputs": []}, **ERR_WITNESS_PROGRAM_MISMATCH)
+
+ # Do the same thing without invalid point, to make sure there is no mistake in the test logic.
+ tap = taproot_construct(pubs[0], [("true", CScript([OP_1]))])
+ add_spender(spenders, "output/invalid_x_mock", tap=tap, key=secs[0], leaf="true", inputs=[])
+
# == Tests for signature hashing ==
# Run all tests once with no annex, and once with a valid random annex.