Skip to content

Commit 823b583

Browse files
instagibbsRob1Ham
authored andcommitted
test: add BIP-348 coverage to feature_taproot
(cherry picked from commit ae64dce)
1 parent e57f648 commit 823b583

File tree

3 files changed

+133
-9
lines changed

3 files changed

+133
-9
lines changed

test/functional/feature_taproot.py

Lines changed: 129 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,12 @@
5252
OP_16,
5353
OP_2DROP,
5454
OP_2DUP,
55+
OP_3DUP,
5556
OP_CHECKMULTISIG,
5657
OP_CHECKMULTISIGVERIFY,
5758
OP_CHECKSIG,
5859
OP_CHECKSIGADD,
60+
OP_CHECKSIGFROMSTACK,
5961
OP_CHECKSIGVERIFY,
6062
OP_CODESEPARATOR,
6163
OP_DROP,
@@ -409,7 +411,7 @@ def default_scriptsig(ctx):
409411
# The annex (only when mode=="taproot").
410412
"annex": None,
411413
# The codeseparator position (only when mode=="taproot").
412-
"codeseppos": -1,
414+
"codeseppos": 0xffffffff,
413415
# The redeemscript to add to the scriptSig (if P2SH; None implies not P2SH).
414416
"script_p2sh": None,
415417
# The script to add to the witness in (if P2WSH; None implies P2WPKH)
@@ -751,10 +753,12 @@ def spenders_taproot_active():
751753
]
752754
random.shuffle(scripts)
753755
tap = taproot_construct(pubs[0], scripts)
754-
add_spender(spenders, "sighash/pk_codesep", tap=tap, leaf="pk_codesep", key=secs[1], **common, **SINGLE_SIG, **SIGHASH_BITFLIP, **ERR_SIG_SCHNORR)
755-
add_spender(spenders, "sighash/codesep_pk", tap=tap, leaf="codesep_pk", key=secs[1], codeseppos=0, **common, **SINGLE_SIG, **SIGHASH_BITFLIP, **ERR_SIG_SCHNORR)
756-
add_spender(spenders, "sighash/branched_codesep/left", tap=tap, leaf="branched_codesep", key=secs[0], codeseppos=3, **common, inputs=[getter("sign"), b'\x01'], **SIGHASH_BITFLIP, **ERR_SIG_SCHNORR)
757-
add_spender(spenders, "sighash/branched_codesep/right", tap=tap, leaf="branched_codesep", key=secs[1], codeseppos=6, **common, inputs=[getter("sign"), b''], **SIGHASH_BITFLIP, **ERR_SIG_SCHNORR)
756+
add_spender(spenders, "sighash/pk_codesep", tap=tap, leaf="pk_codesep", key=secs[1], **common, **SINGLE_SIG, **SIGHASH_BITFLIP, **ERR_SCHNORR_SIG)
757+
add_spender(spenders, "sighash/codesep_pk", tap=tap, leaf="codesep_pk", key=secs[1], codeseppos=0, **common, **SINGLE_SIG, **SIGHASH_BITFLIP, **ERR_SCHNORR_SIG)
758+
add_spender(spenders, "sighash/branched_codesep/left", tap=tap, leaf="branched_codesep", key=secs[0], codeseppos=3, **common, inputs=[getter("sign"), b'\x01'], **SIGHASH_BITFLIP, **ERR_SCHNORR_SIG)
759+
add_spender(spenders, "sighash/branched_codesep/right", tap=tap, leaf="branched_codesep", key=secs[1], codeseppos=6, **common, inputs=[getter("sign"), b''], **SIGHASH_BITFLIP, **ERR_SCHNORR_SIG)
760+
add_spender(spenders, "sighash/codesep_pk_wrongpos1", tap=tap, leaf="codesep_pk", key=secs[1], codeseppos=0, **common, **SINGLE_SIG, failure={"codeseppos": 1}, **ERR_SCHNORR_SIG)
761+
add_spender(spenders, "sighash/codesep_pk_wrongpos2", tap=tap, leaf="codesep_pk", key=secs[1], codeseppos=0, **common, **SINGLE_SIG, failure={"codeseppos": 0xfffffffe}, **ERR_SCHNORR_SIG)
758762

759763
# Reusing the scripts above, test that various features affect the sighash.
760764
add_spender(spenders, "sighash/annex", tap=tap, leaf="pk_codesep", key=secs[1], hashtype=hashtype, standard=False, **SINGLE_SIG, annex=bytes([ANNEX_TAG]), failure={"sighash": override(default_sighash, annex=None)}, **ERR_SIG_SCHNORR)
@@ -1057,6 +1061,13 @@ def big_spend_inputs(ctx):
10571061

10581062
# == Test for sigops ratio limit ==
10591063

1064+
# BIP348 CSFS signatures are embedded directly into the tapleaves vs the witness stack
1065+
# since they do not introspect directly
1066+
CSFS_MSG = b'\x00\x00'
1067+
# Signature should pass even if random unknown key is used, just use real privkey
1068+
# to pass in case it's the defined pubkey
1069+
CSFS_SIG = sign_schnorr(secs[1], CSFS_MSG)
1070+
10601071
# Given a number n, and a public key pk, functions that produce a (CScript, sigops). Each script takes as
10611072
# input a valid signature with the passed pk followed by a dummy push of bytes that are to be dropped, and
10621073
# will execute sigops signature checks.
@@ -1073,7 +1084,15 @@ def big_spend_inputs(ctx):
10731084
lambda n, pk: (CScript([OP_DROP, OP_0, pk, OP_CHECKSIG, OP_NOT, OP_VERIFY, pk] + [OP_2DUP, OP_CHECKSIG, OP_VERIFY] * n + [OP_CHECKSIG]), n + 1),
10741085
# n OP_CHECKSIGADDs and 1 OP_CHECKSIG, but also an OP_CHECKSIGADD with an empty signature.
10751086
lambda n, pk: (CScript([OP_DROP, OP_0, OP_10, pk, OP_CHECKSIGADD, OP_10, OP_EQUALVERIFY, pk] + [OP_2DUP, OP_16, OP_SWAP, OP_CHECKSIGADD, b'\x11', OP_EQUALVERIFY] * n + [OP_CHECKSIG]), n + 1),
1087+
# n OP_CHECKSIGFROMSTACKs, dropping the signature given, and just validate against embedded sigs
1088+
lambda n, pk: (CScript([OP_2DROP, CSFS_SIG, CSFS_MSG, pk] + [OP_3DUP, OP_CHECKSIGFROMSTACK, OP_DROP] * n + [OP_2DROP]), n),
1089+
# 1 CHECKSIGVERIFY followed by n OP_CHECKSIGFROMSTACKs, all signatures non-empty and validated
1090+
lambda n, pk: (CScript([OP_DROP, pk, OP_CHECKSIGVERIFY, CSFS_SIG, CSFS_MSG, pk] + [OP_3DUP, OP_CHECKSIGFROMSTACK, OP_DROP] * n + [OP_2DROP]), n+1),
1091+
# 1 empty CHECKSIG followed by 1 empty OP_CHECKSIGFROMSTACKs, then finally n OP_CHECKSIGFROMSTACKs
1092+
lambda n, pk: (CScript([OP_2DROP, OP_0, pk, OP_CHECKSIG, OP_DROP, OP_0, CSFS_MSG, pk, OP_CHECKSIGFROMSTACK, OP_DROP, CSFS_SIG, CSFS_MSG, pk] + [OP_3DUP, OP_CHECKSIGFROMSTACK, OP_DROP] * n + [OP_2DROP]), n),
1093+
10761094
]
1095+
10771096
for annex in [None, bytes([ANNEX_TAG]) + random.randbytes(random.randrange(1000))]:
10781097
for hashtype in [SIGHASH_DEFAULT, SIGHASH_ALL]:
10791098
for pubkey in [pubs[1], random.randbytes(random.choice([x for x in range(2, 81) if x != 32]))]:
@@ -1237,6 +1256,102 @@ def spenders_taproot_nonstandard():
12371256

12381257
return spenders
12391258

1259+
def bip348_csfs_spenders():
1260+
secs = [generate_privkey() for _ in range(2)]
1261+
pubs = [compute_xonly_pubkey(sec)[0] for sec in secs]
1262+
1263+
CSFS_MSG = random.randbytes(random.randrange(0, 520))
1264+
1265+
# Grow, shrink the message being signed, and pick random bytes
1266+
TRUNC_CSFS_MSG = CSFS_MSG[:] if len(CSFS_MSG) > 0 else None
1267+
if TRUNC_CSFS_MSG is not None:
1268+
prune_index = random.randrange(len(TRUNC_CSFS_MSG))
1269+
TRUNC_CSFS_MSG = TRUNC_CSFS_MSG[:prune_index] + TRUNC_CSFS_MSG[prune_index+1:]
1270+
extendable_length = 520 - len(CSFS_MSG)
1271+
EXTEND_CSFS_MSG = None
1272+
if extendable_length > 0:
1273+
EXTEND_CSFS_MSG = CSFS_MSG + random.randbytes(random.randrange(1, extendable_length))
1274+
OTHER_CSFS_MSG = CSFS_MSG
1275+
1276+
while OTHER_CSFS_MSG == CSFS_MSG:
1277+
OTHER_CSFS_MSG = random.randbytes(random.randrange(0, 520))
1278+
1279+
UNK_PUBKEY = random.randbytes(random.randrange(1, 520))
1280+
while len(UNK_PUBKEY) == 32:
1281+
UNK_PUBKEY = random.randbytes(random.randrange(1, 520))
1282+
1283+
# Sigops ratio test is included elsewhere to mix and match with other sigops
1284+
scripts = [
1285+
("simple_csfs", CScript([CSFS_MSG, pubs[0], OP_CHECKSIGFROMSTACK, OP_1, OP_EQUAL])),
1286+
("simple_fail_csfs", CScript([CSFS_MSG, pubs[0], OP_CHECKSIGFROMSTACK, OP_0, OP_EQUAL])),
1287+
("unk_pubkey_csfs", CScript([CSFS_MSG, UNK_PUBKEY, OP_CHECKSIGFROMSTACK])),
1288+
("onearg_csfs", CScript([pubs[0], OP_CHECKSIGFROMSTACK])),
1289+
("twoargs_csfs", CScript([CSFS_MSG, pubs[0], OP_CHECKSIGFROMSTACK])),
1290+
("empty_pk_csfs", CScript([CSFS_MSG, OP_0, OP_CHECKSIGFROMSTACK, OP_0, OP_EQUAL])),
1291+
]
1292+
1293+
tap = taproot_construct(pubs[0], scripts)
1294+
1295+
spenders = []
1296+
1297+
# "sighash" is actually the bip340 message being directly verified against
1298+
add_spender(spenders, comment="bip348_csfs/simple", tap=tap, leaf="simple_csfs", key=secs[0], inputs=[getter("sign")], sighash=CSFS_MSG, failure={"sighash": OTHER_CSFS_MSG}, **ERR_SCHNORR_SIG)
1299+
if TRUNC_CSFS_MSG is not None:
1300+
add_spender(spenders, comment="bip348_csfs/trunc_msg", tap=tap, leaf="onearg_csfs", key=secs[0], inputs=[getter("sign"), CSFS_MSG], standard=len(CSFS_MSG)<=80, sighash=CSFS_MSG, failure={"inputs": [getter("sign"), TRUNC_CSFS_MSG]}, **ERR_SCHNORR_SIG)
1301+
if EXTEND_CSFS_MSG is not None:
1302+
add_spender(spenders, comment="bip348_csfs/extend_msg", tap=tap, leaf="onearg_csfs", key=secs[0], inputs=[getter("sign"), CSFS_MSG], standard=len(CSFS_MSG)<=80, sighash=CSFS_MSG, failure={"inputs": [getter("sign"), EXTEND_CSFS_MSG]}, **ERR_SCHNORR_SIG)
1303+
1304+
# Empty signature pushes zero onto stack and continues, unless the pubkey is empty
1305+
add_spender(spenders, comment="bip348_csfs/simple_fail", tap=tap, leaf="simple_fail_csfs", inputs=[b''], failure={"leaf": "empty_pk_csfs", "inputs": [OTHER_CSFS_MSG]}, **ERR_PUBKEYTYPE)
1306+
1307+
# Unknown pubkey of non-zero size is unconditionally valid regardless of signature (but signature must exist)
1308+
add_spender(spenders, comment="bip348_csfs/unk_pubkey", tap=tap, leaf="unk_pubkey_csfs", standard=False, key=secs[0], inputs=[getter("sign")], sighash=CSFS_MSG, failure={"inputs": []}, **ERR_INVALID_STACK_OPERATION)
1309+
1310+
# You need three args for CSFS regardless of what is passed
1311+
add_spender(spenders, comment="bip348_csfs/onearg", tap=tap, leaf="onearg_csfs", key=secs[0], inputs=[getter("sign"), CSFS_MSG], standard=len(CSFS_MSG)<=80, sighash=CSFS_MSG, failure={"inputs": []}, **ERR_INVALID_STACK_OPERATION)
1312+
add_spender(spenders, comment="bip348_csfs/twoarg", tap=tap, leaf="twoargs_csfs", key=secs[0], inputs=[getter("sign")], sighash=CSFS_MSG, failure={"inputs": []}, **ERR_INVALID_STACK_OPERATION)
1313+
1314+
# If a known pubkey's signature is not 64 bytes or empty it MUST fail immediately
1315+
add_spender(spenders, comment="bip348_csfs/simple_65_sig", tap=tap, leaf="simple_csfs", key=secs[0], inputs=[getter("sign")], sighash=CSFS_MSG, failure={"leaf": "simple_fail_csfs", "inputs": [zero_appender(getter("sign"))]}, **ERR_SCHNORR_SIG)
1316+
add_spender(spenders, comment="bip348_csfs/simple_63_sig", tap=tap, leaf="simple_csfs", key=secs[0], inputs=[getter("sign")], sighash=CSFS_MSG, failure={"leaf": "simple_fail_csfs", "inputs": [byte_popper(getter("sign"))]}, **ERR_SCHNORR_SIG)
1317+
1318+
return spenders
1319+
1320+
def sample_spenders():
1321+
1322+
# Create key(s) for output creation, as well as key and script-spends
1323+
secs = [generate_privkey() for _ in range(2)]
1324+
pubs = [compute_xonly_pubkey(sec)[0] for sec in secs]
1325+
1326+
# Create a list of scripts which will be built into a taptree
1327+
scripts = [
1328+
# leaf label, followed by CScript
1329+
("encodeable_pushdata1", CScript([OP_DROP, OP_PUSHDATA1, b'aa' * 75])),
1330+
("nonstd_encodeable_pushdata1", CScript([OP_PUSHDATA1, b'aa'])),
1331+
("dummyleaf", CScript([])),
1332+
]
1333+
1334+
# Build TaprootInfo using scripts and appropriate pubkey for output creation
1335+
tap = taproot_construct(pubs[0], scripts)
1336+
1337+
# Finally, add spender(s).
1338+
# Each spender embodies a test with an optional failure condition.
1339+
# These failure conditions allow for fine-grained success/failure
1340+
# conditions that are tested randomly.
1341+
spenders = []
1342+
1343+
# Named comment, using first leaf from scripts, with empty string as witness data, no optional fail condition
1344+
add_spender(spenders, comment="tutorial/pushdata1", tap=tap, leaf="encodeable_pushdata1", inputs=[b'\x00'], no_fail=True)
1345+
1346+
# Spender with alternative failure tapscript via over-riding "failure" dictionary, along with the failure's expected err_msg / ERR_*
1347+
add_spender(spenders, comment="tutorial/pushdata1redux", tap=tap, leaf="encodeable_pushdata1", inputs=[b'\x00'], failure={"leaf": "dummyleaf"}, **ERR_EVAL_FALSE)
1348+
1349+
# Spender that is non-standard but otherwise valid, with extraneous signature data from inner key for optional failure condition
1350+
add_spender(spenders, comment="tutorial/nonminpushdata1", tap=tap, leaf="nonstd_encodeable_pushdata1", key=secs[0], standard=False, failure={"inputs": [getter("sign")]}, **ERR_CLEANSTACK)
1351+
1352+
# New scripts=[] can be defined, and rinse-repeated as necessary until the spenders list is returned for execution
1353+
return spenders
1354+
12401355
# Consensus validation flags to use in dumps for tests with "legacy/" or "inactive/" prefix.
12411356
LEGACY_FLAGS = "P2SH,DERSIG,CHECKLOCKTIMEVERIFY,CHECKSEQUENCEVERIFY,WITNESS,NULLDUMMY"
12421357
# Consensus validation flags to use in dumps for all other tests.
@@ -1765,7 +1880,15 @@ def run_test(self):
17651880
self.gen_test_vectors()
17661881

17671882
self.log.info("Post-activation tests...")
1768-
self.test_spenders(self.nodes[0], spenders_taproot_active(), input_counts=[1, 2, 2, 2, 2, 3])
1883+
1884+
# New sub-tests not checking standardness can be added to consensus_spenders
1885+
# to allow for increased coverage across input types.
1886+
# See sample_spenders for a minimal example
1887+
consensus_spenders = sample_spenders()
1888+
consensus_spenders += bip348_csfs_spenders()
1889+
consensus_spenders += spenders_taproot_active()
1890+
self.test_spenders(self.nodes[0], consensus_spenders, input_counts=[1, 2, 2, 2, 2, 3])
1891+
17691892
# Run each test twice; once in isolation, and once combined with others. Testing in isolation
17701893
# means that the standardness is verified in every test (as combined transactions are only standard
17711894
# when all their inputs are standard).

test/functional/test_framework/key.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,6 @@ def sign_schnorr(key, msg, aux=None, flip_p=False, flip_r=False):
272272
aux = bytes(32)
273273

274274
assert len(key) == 32
275-
assert len(msg) == 32
276275
assert len(aux) == 32
277276

278277
sec = int.from_bytes(key, 'big')

test/functional/test_framework/script.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,6 @@ def __new__(cls, n):
235235
OP_CHECKSIGVERIFY = CScriptOp(0xad)
236236
OP_CHECKMULTISIG = CScriptOp(0xae)
237237
OP_CHECKMULTISIGVERIFY = CScriptOp(0xaf)
238-
OP_CHECKSIGFROMSTACK = CScriptOp(0xcc)
239238

240239
# expansion
241240
OP_NOP1 = CScriptOp(0xb0)
@@ -252,6 +251,9 @@ def __new__(cls, n):
252251
# BIP 342 opcodes (Tapscript)
253252
OP_CHECKSIGADD = CScriptOp(0xba)
254253

254+
# BIP 348 (OP_SUCCESS204)
255+
OP_CHECKSIGFROMSTACK = CScriptOp(0xcc)
256+
255257
OP_INVALIDOPCODE = CScriptOp(0xff)
256258

257259
OPCODE_NAMES.update({
@@ -851,7 +853,7 @@ def TaprootSignatureMsg(txTo, spent_utxos, hash_type, input_index=0, *, scriptpa
851853
if scriptpath:
852854
ss += TaggedHash("TapLeaf", bytes([leaf_ver]) + ser_string(leaf_script))
853855
ss += bytes([0])
854-
ss += codeseparator_pos.to_bytes(4, "little", signed=True)
856+
ss += codeseparator_pos.to_bytes(4, "little", signed=False)
855857
assert len(ss) == 175 - (in_type == SIGHASH_ANYONECANPAY) * 49 - (out_type != SIGHASH_ALL and out_type != SIGHASH_SINGLE) * 32 + (annex is not None) * 32 + scriptpath * 37
856858
return ss
857859

0 commit comments

Comments
 (0)