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.
12411356LEGACY_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).
0 commit comments