mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-22 23:07:59 +01:00
Merge bitcoin/bitcoin#26383: test: Add feature_taproot case involving invalid internal pubkey
5d413c8e79
Add feature_taproot case involved invalid internal pubkey (Pieter Wuille) Pull request description: Add a test case to feature_taproot which involves an output that is (incorrectly) constructed, using an invalid internal public key and valid script tree. It is designed to detect cases where the script path spending validation logic does not detect this case, and instead treats the internal public key as the point at infinity. Equivalent unit test case added in https://github.com/bitcoin-core/qa-assets/pull/98. ACKs for top commit: instagibbs: ACK5d413c8e79
aureleoules: reACK5d413c8e79
Tree-SHA512: dfa014e383cd2743f3c9a996e1f2a2fceb9e244edf4b05dc0c110c4ba32a87684482222907805a4ca998aebcb42a197bb3e7967bfb5f0554fe9f1e5aa5463603
This commit is contained in:
commit
38d06e1561
2 changed files with 52 additions and 4 deletions
|
@ -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.
|
||||
|
|
|
@ -12,7 +12,7 @@ import struct
|
|||
import unittest
|
||||
from typing import List, Dict
|
||||
|
||||
from .key import TaggedHash, tweak_add_pubkey
|
||||
from .key import TaggedHash, tweak_add_pubkey, compute_xonly_pubkey
|
||||
|
||||
from .messages import (
|
||||
CTransaction,
|
||||
|
@ -872,7 +872,7 @@ TaprootInfo = namedtuple("TaprootInfo", "scriptPubKey,internal_pubkey,negflag,tw
|
|||
# - merklebranch: the merkle branch to use for this leaf (32*N bytes)
|
||||
TaprootLeafInfo = namedtuple("TaprootLeafInfo", "script,version,merklebranch,leaf_hash")
|
||||
|
||||
def taproot_construct(pubkey, scripts=None):
|
||||
def taproot_construct(pubkey, scripts=None, treat_internal_as_infinity=False):
|
||||
"""Construct a tree of Taproot spending conditions
|
||||
|
||||
pubkey: a 32-byte xonly pubkey for the internal pubkey (bytes)
|
||||
|
@ -891,7 +891,10 @@ def taproot_construct(pubkey, scripts=None):
|
|||
|
||||
ret, h = taproot_tree_helper(scripts)
|
||||
tweak = TaggedHash("TapTweak", pubkey + h)
|
||||
tweaked, negated = tweak_add_pubkey(pubkey, tweak)
|
||||
if treat_internal_as_infinity:
|
||||
tweaked, negated = compute_xonly_pubkey(tweak)
|
||||
else:
|
||||
tweaked, negated = tweak_add_pubkey(pubkey, tweak)
|
||||
leaves = dict((name, TaprootLeafInfo(script, version, merklebranch, leaf)) for name, version, script, merklebranch, leaf in ret)
|
||||
return TaprootInfo(CScript([OP_1, tweaked]), pubkey, negated + 0, tweak, leaves, h, tweaked)
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue