mirror of
https://github.com/Retropex/bitcoin.git
synced 2025-05-17 13:40:43 +02:00
Add feature_taproot case involved invalid internal pubkey
This commit is contained in:
parent
1c5c951713
commit
5d413c8e79
@ -96,7 +96,14 @@ from test_framework.util import (
|
|||||||
assert_equal,
|
assert_equal,
|
||||||
random_bytes,
|
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 (
|
from test_framework.address import (
|
||||||
hash160,
|
hash160,
|
||||||
program_to_witness,
|
program_to_witness,
|
||||||
@ -661,6 +668,44 @@ def spenders_taproot_active():
|
|||||||
# Test with signature with bit flipped.
|
# Test with signature with bit flipped.
|
||||||
add_spender(spenders, "sig/bitflip", tap=tap, key=secs[0], failure={"signature": bitflipper(default_signature)}, **ERR_SIG_SCHNORR)
|
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 ==
|
# == Tests for signature hashing ==
|
||||||
|
|
||||||
# Run all tests once with no annex, and once with a valid random annex.
|
# Run all tests once with no annex, and once with a valid random annex.
|
||||||
|
@ -12,7 +12,7 @@ import struct
|
|||||||
import unittest
|
import unittest
|
||||||
from typing import List, Dict
|
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 (
|
from .messages import (
|
||||||
CTransaction,
|
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)
|
# - merklebranch: the merkle branch to use for this leaf (32*N bytes)
|
||||||
TaprootLeafInfo = namedtuple("TaprootLeafInfo", "script,version,merklebranch,leaf_hash")
|
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
|
"""Construct a tree of Taproot spending conditions
|
||||||
|
|
||||||
pubkey: a 32-byte xonly pubkey for the internal pubkey (bytes)
|
pubkey: a 32-byte xonly pubkey for the internal pubkey (bytes)
|
||||||
@ -891,6 +891,9 @@ def taproot_construct(pubkey, scripts=None):
|
|||||||
|
|
||||||
ret, h = taproot_tree_helper(scripts)
|
ret, h = taproot_tree_helper(scripts)
|
||||||
tweak = TaggedHash("TapTweak", pubkey + h)
|
tweak = TaggedHash("TapTweak", pubkey + h)
|
||||||
|
if treat_internal_as_infinity:
|
||||||
|
tweaked, negated = compute_xonly_pubkey(tweak)
|
||||||
|
else:
|
||||||
tweaked, negated = tweak_add_pubkey(pubkey, tweak)
|
tweaked, negated = tweak_add_pubkey(pubkey, tweak)
|
||||||
leaves = dict((name, TaprootLeafInfo(script, version, merklebranch, leaf)) for name, version, script, merklebranch, leaf in ret)
|
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)
|
return TaprootInfo(CScript([OP_1, tweaked]), pubkey, negated + 0, tweak, leaves, h, tweaked)
|
||||||
|
Loading…
Reference in New Issue
Block a user