diff --git a/src/binana/checksigfromstack.json b/src/binana/checksigfromstack.json new file mode 100644 index 000000000000..7d0f9292edeb --- /dev/null +++ b/src/binana/checksigfromstack.json @@ -0,0 +1,9 @@ +{ + "binana": [2024, 3, 2], + "deployment": "CHECKSIGFROMSTACK", + "scriptverify": true, + "scriptverify_discourage": true, + "opcodes": { + "CHECKSIGFROMSTACK": "0xcc" + } +} diff --git a/src/pubkey.cpp b/src/pubkey.cpp index fb25ebd4ca79..02b732d77ac2 100644 --- a/src/pubkey.cpp +++ b/src/pubkey.cpp @@ -227,12 +227,12 @@ bool XOnlyPubKey::IsFullyValid() const return secp256k1_xonly_pubkey_parse(secp256k1_context_static, &pubkey, m_keydata.data()); } -bool XOnlyPubKey::VerifySchnorr(const uint256& msg, Span sigbytes) const +bool XOnlyPubKey::VerifySchnorr(const Span msg, Span sigbytes) const { assert(sigbytes.size() == 64); secp256k1_xonly_pubkey pubkey; if (!secp256k1_xonly_pubkey_parse(secp256k1_context_static, &pubkey, m_keydata.data())) return false; - return secp256k1_schnorrsig_verify(secp256k1_context_static, sigbytes.data(), msg.begin(), 32, &pubkey); + return secp256k1_schnorrsig_verify(secp256k1_context_static, sigbytes.data(), msg.data(), msg.size(), &pubkey); } static const HashWriter HASHER_TAPTWEAK{TaggedHash("TapTweak")}; diff --git a/src/pubkey.h b/src/pubkey.h index b4666aad228e..012c98262c28 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -263,7 +263,7 @@ class XOnlyPubKey * * sigbytes must be exactly 64 bytes. */ - bool VerifySchnorr(const uint256& msg, Span sigbytes) const; + bool VerifySchnorr(const Span msg, Span sigbytes) const; /** Compute the Taproot tweak as specified in BIP341, with *this as internal * key: diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 46ea705f89a4..3027cf14c124 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -419,6 +419,55 @@ static bool EvalChecksig(const valtype& sig, const valtype& pubkey, CScript::con assert(false); } +/** + * Returns false when script execution must immediately fail. + */ +static bool EvalChecksigFromStack(const valtype& sig, const valtype& msg, const valtype& pubkey_in, ScriptExecutionData& execdata, unsigned int flags, SigVersion sigversion, ScriptError* serror, bool& success_out) +{ + assert(sigversion == SigVersion::TAPSCRIPT); + + /* + * The following validation sequence is consensus critical. Please note how -- + * upgradable public key versions precede other rules; + * the script execution fails when using empty signature with invalid public key; + * the script execution fails when using non-empty invalid signature. + */ + success_out = !sig.empty(); + if (success_out) { + // Implement the sigops/witnesssize ratio test. + // Passing with an upgradable public key version is also counted. + assert(execdata.m_validation_weight_left_init); + execdata.m_validation_weight_left -= VALIDATION_WEIGHT_PER_SIGOP_PASSED; + if (execdata.m_validation_weight_left < 0) { + return set_error(serror, SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT); + } + } + if (pubkey_in.size() == 0) { + return set_error(serror, SCRIPT_ERR_PUBKEYTYPE); + } else if (pubkey_in.size() == 32) { + if (success_out) { + if (sig.size() != 64) { + return set_error(serror, SCRIPT_ERR_SCHNORR_SIG_SIZE); + } + XOnlyPubKey pubkey{pubkey_in}; + if (!pubkey.VerifySchnorr(msg, sig)) { + return set_error(serror, SCRIPT_ERR_SCHNORR_SIG); + } + } + } else { + /* + * New public key version softforks should be defined before this `else` block. + * Generally, the new code should not do anything but failing the script execution. To avoid + * consensus bugs, it should not modify any existing values (including `success`). + */ + if ((flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE) != 0) { + return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_PUBKEYTYPE); + } + } + + return true; +} + bool EvalScript(std::vector >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* serror) { static const CScriptNum bnZero(0); @@ -1280,6 +1329,40 @@ bool EvalScript(std::vector >& stack, const CScript& } break; + case OP_CHECKSIGFROMSTACK: { + + // DISCOURAGE for OP_CHECKSIGFROMSTACK is handled in OP_SUCCESS handling + // OP_CHECKSIGFROMSTACK is only available in Tapscript + if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) { + return set_error(serror, SCRIPT_ERR_BAD_OPCODE); + } + + // If fewer than 3 elements are on the stack, the script MUST fail and terminate immediately + if (stack.size() < 3) { + return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION); + } + + // The public key (top element) + // message (second to top element), + // and signature (third from top element) are read from the stack. + const valtype& pubkey = stacktop(-1); + const valtype& msg = stacktop(-2); + const valtype& sig = stacktop(-3); + + bool push_success = true; + if (!EvalChecksigFromStack(sig, msg, pubkey, execdata, flags, sigversion, serror, push_success)) { + return false; // serror set by EvalChecksigFromStack + } + + popstack(stack); + popstack(stack); + popstack(stack); + + stack.push_back(push_success ? vchTrue : vchFalse); + + break; + } + default: return set_error(serror, SCRIPT_ERR_BAD_OPCODE); } diff --git a/src/test/data/tx_invalid.json b/src/test/data/tx_invalid.json index e72283d8a5ec..01b832c2fc62 100644 --- a/src/test/data/tx_invalid.json +++ b/src/test/data/tx_invalid.json @@ -393,6 +393,14 @@ ["ceafe58e0f6e7d67c0409fbbf673c84c166e3c5d3c24af58f7175b18df3bb3db", 1, "2 0x48 0x3045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303 0x21 0x0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71 0x21 0x0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71 3 CHECKMULTISIG"]], "0100000002dbb33bdf185b17f758af243c5d3c6e164cc873f6bb9f40c0677d6e0f8ee5afce000000006b4830450221009627444320dc5ef8d7f68f35010b4c050a6ed0d96b67a84db99fda9c9de58b1e02203e4b4aaa019e012e65d69b487fdf8719df72f488fa91506a80c49a33929f1fd50121022b78b756e2258af13779c1a1f37ea6800259716ca4b7f0b87610e0bf3ab52a01ffffffffdbb33bdf185b17f758af243c5d3c6e164cc873f6bb9f40c0677d6e0f8ee5afce010000009300483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303ffffffff01a0860100000000001976a9149bc0bbdd3024da4d0c38ed1aecf5c68dd1d3fa1288ac00000000", "CONST_SCRIPTCODE"], +["Test OP_CHECKSIGFROMSTACK, fails immediately with sig for wrong data"], + [[["a2522fa96033c5736f3142ff616426cd03a3d0f077f609e22c5a33a96e04e597", + 0, + "1 0x20 0x6e929e9354a357e9a1254feac061741a11c66508786c66b3b29edc79b9c46e19", + 155000]], +"0200000000010197e5046ea9335a2ce209f677f0d0a303cd266461ff42316f73c53360a92f52a20000000000ffffffff01f04902000000000022512040104c71081b266fdf4008db8b0a1c3291f2e1cb680753936de9b76dac45a6ef0340b5258eeb9df148d499d14b8e23fe5315a230b7f1dee497a04605426ffe068f2e0920c9b63ba28b1c6cea39c0e659af1658825d23e859c5ae773a0be996f1c4744520feadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef204835c505a762f5e55c2e8eda1c05437d973809a0236178510208a6ac3f7632bfcc008721c050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac000000000", + "P2SH,WITNESS,TAPROOT,CHECKSIGFROMSTACK"], + ["CheckTemplateVerify (CTV) Tests"], ["Modified Segwit OP_CTV Spend Failed if Amount Mutated"], [[["10bf165531fbdfac386f018d92d72dce3ad73af1f183f6fac2c7a2a1050aa51b", diff --git a/src/test/data/tx_valid.json b/src/test/data/tx_valid.json index d5d78af18fb4..5ad400255bbf 100644 --- a/src/test/data/tx_valid.json +++ b/src/test/data/tx_valid.json @@ -520,6 +520,35 @@ [[["1111111111111111111111111111111111111111111111111111111111111111", 0, "0x00 0x14 0x751e76e8199196d454941c45d1b3a323f1433bd6", 5000000]], "0100000000010111111111111111111111111111111111111111111111111111111111111111110000000000ffffffff0130244c0000000000fd02014cdc1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111175210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ac02483045022100c1a4a6581996a7fdfea77d58d537955a5655c1d619b6f3ab6874f28bb2e19708022056402db6fede03caae045a3be616a1a2d0919a475ed4be828dc9ff21f24063aa01210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179800000000", "NONE"], +["Test OP_CHECKSIGFROMSTACK"], + [[["e2f2baee9c59389b34e39742ce05debf64aaa7a00fbdab88614f4d3c133186d5", + 0, + "1 0x20 0xed98cc178a5e3f2537ec8bf5ab9a14e56b8a188d666ba6ce788405e849ba7da8", + 155000]], +"02000000000101d58631133c4d4f6188abbd0fa0a7aa64bfde05ce4297e3349b38599ceebaf2e20000000000ffffffff01f0490200000000002251203408099b8f38a71ab6dfafdf0b266bd0a0f58096b5c453624c752bae6c0f19560340cd3e61f2754dd13e51a6d86d18092f795c626d36deaf0cf076a87648d9f4e4cfceaaa8e8a7eee1ee13dd09ef2c14eedd475f4e9adcf8a2391b910271b2203aa24320deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef208fdb638cf9201fcae809f31b7d5b5ef9ae712cd374c8c89b06d52b9d2c3885bfcc21c050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac000000000", + "DISCOURAGE_CHECKSIGFROMSTACK"], + ["Test OP_CHECKSIGFROMSTACK succeeds with unknown key type"], + [[["e2f2baee9c59389b34e39742ce05debf64aaa7a00fbdab88614f4d3c133186d5", + 0, + "1 0x20 0xde96616e5e3961cbbd7bab3ea0e6b6e1ace088299857136fbb3703454c784afb", + 155000]], +"02000000000101d58631133c4d4f6188abbd0fa0a7aa64bfde05ce4297e3349b38599ceebaf2e20000000000ffffffff01f0490200000000002251203408099b8f38a71ab6dfafdf0b266bd0a0f58096b5c453624c752bae6c0f19560340cd3e61f2754dd13e51a6d86d18092f795c626d36deaf0cf076a87648d9f4e4cfceaaa8e8a7eee1ee13dd09ef2c14eedd475f4e9adcf8a2391b910271b2203aa24420deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef21038fdb638cf9201fcae809f31b7d5b5ef9ae712cd374c8c89b06d52b9d2c3885bfcc21c050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac000000000", + "DISCOURAGE_CHECKSIGFROMSTACK,DISCOURAGE_UPGRADABLE_PUBKEYTYPE"], + ["Test OP_CHECKSIGFROMSTACK yields 0 for 0-sig"], + [[["e2f2baee9c59389b34e39742ce05debf64aaa7a00fbdab88614f4d3c133186d5", + 0, + "1 0x20 0x7f3db202bc0db8c15de91c5da0dd64bd52ae81f5847cda623e1304c524cad314", + 155000]], +"02000000000101d58631133c4d4f6188abbd0fa0a7aa64bfde05ce4297e3349b38599ceebaf2e20000000000ffffffff01f0490200000000002251203408099b8f38a71ab6dfafdf0b266bd0a0f58096b5c453624c752bae6c0f195603004520deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef208fdb638cf9201fcae809f31b7d5b5ef9ae712cd374c8c89b06d52b9d2c3885bfcc008721c050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac000000000", + "DISCOURAGE_CHECKSIGFROMSTACK"], + ["Test OP_CHECKSIGFROMSTACK, shorter message"], + [[["e2f2baee9c59389b34e39742ce05debf64aaa7a00fbdab88614f4d3c133186d5", + 0, + "1 0x20 0x313a784205aef89c9d203c1e4cfacd2e31fa55f42dcd77e5e2db9d0513d50827", + 155000]], +"02000000000101d58631133c4d4f6188abbd0fa0a7aa64bfde05ce4297e3349b38599ceebaf2e20000000000ffffffff01f0490200000000002251203408099b8f38a71ab6dfafdf0b266bd0a0f58096b5c453624c752bae6c0f195603403c5a935ce7a3856bc3e75eae403a21ff2e5a9f919c0f6f4d6bf7f58c834c13484882fc6f98587fe48e6945a49c0ca4fc62fb5f641a216ea62ac2dbc0071976833411636865636b73696766726f6d737461636b208fdb638cf9201fcae809f31b7d5b5ef9ae712cd374c8c89b06d52b9d2c3885bfcc21c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac000000000", + "DISCOURAGE_CHECKSIGFROMSTACK"], + ["Check that CTV is Processed with a Taproot Spend"], [[["f90521604b56c392ffa17a01bcae5914b8cf7728cc6cec00d90838818cc5465f", 0, "1 0x20 0x24f5fe807bcee7774dc515f0b7ee8d6ae39eefd1b590264c52ff867e22c49419", 155000]], "020000000001015f46c58c813808d900ec6ccc2877cfb81459aebc017aa1ff92c3564b602105f90000000000000000000ae80300000000000017a914ce0036ae7d49f06967dd92cc1ffff4a878c457f987d00700000000000017a91406e00c3b362e65e03507a2858d7b6499b668669887b80b00000000000017a9142ee42c65592c59b69bfefbd03781140c67e5232487a00f00000000000017a9146b3df16a1e6651d582ca6598900cb4f2d6c9dfb887881300000000000017a914877d55932d4f38b476d4db27e4efbe159ff0a07187701700000000000017a91441e9dc892e861d252d513d594ba833cd6bc8917087581b00000000000017a914b93075800c693dcc78b0553bf9d1cf879d76a02487401f00000000000017a914e9f0ea3a2cae0ad01114e2ec3502ef08bbc50af487282300000000000017a9149a645b5293bdf8be72cb9d1460bce7d64445cfad87102700000000000017a91451e5d6b2ee24ae128234c92245df3624620ea7d3870222209eb65498bfcd4eb90e61c2c5e323a9c16c8bfd8d53ba649915bcdb572099c12fb321c0b7e0105780185688d998a8f8438aa07637a5799755688ec80175cb26c0406e0200000000", diff --git a/test/functional/feature_taproot.py b/test/functional/feature_taproot.py index 8a5deca61038..056405edc4fb 100755 --- a/test/functional/feature_taproot.py +++ b/test/functional/feature_taproot.py @@ -52,10 +52,12 @@ OP_16, OP_2DROP, OP_2DUP, + OP_3DUP, OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY, OP_CHECKSIG, OP_CHECKSIGADD, + OP_CHECKSIGFROMSTACK, OP_CHECKSIGVERIFY, OP_CODESEPARATOR, OP_DROP, @@ -1076,6 +1078,13 @@ def big_spend_inputs(ctx): # == Test for sigops ratio limit == + # BIP348 CSFS signatures are embedded directly into the tapleaves vs the witness stack + # since they do not introspect directly + CSFS_MSG = b'\x00\x00' + # Signature should pass even if random unknown key is used, just use real privkey + # to pass in case it's the defined pubkey + CSFS_SIG = sign_schnorr(secs[1], CSFS_MSG) + # Given a number n, and a public key pk, functions that produce a (CScript, sigops). Each script takes as # input a valid signature with the passed pk followed by a dummy push of bytes that are to be dropped, and # will execute sigops signature checks. @@ -1092,7 +1101,15 @@ def big_spend_inputs(ctx): 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), # n OP_CHECKSIGADDs and 1 OP_CHECKSIG, but also an OP_CHECKSIGADD with an empty signature. 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), + # n OP_CHECKSIGFROMSTACKs, dropping the signature given, and just validate against embedded sigs + lambda n, pk: (CScript([OP_2DROP, CSFS_SIG, CSFS_MSG, pk] + [OP_3DUP, OP_CHECKSIGFROMSTACK, OP_DROP] * n + [OP_2DROP]), n), + # 1 CHECKSIGVERIFY followed by n OP_CHECKSIGFROMSTACKs, all signatures non-empty and validated + 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), + # 1 empty CHECKSIG followed by 1 empty OP_CHECKSIGFROMSTACKs, then finally n OP_CHECKSIGFROMSTACKs + 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), + ] + for annex in [None, bytes([ANNEX_TAG]) + random.randbytes(random.randrange(1000))]: for hashtype in [SIGHASH_DEFAULT, SIGHASH_ALL]: for pubkey in [pubs[1], random.randbytes(random.choice([x for x in range(2, 81) if x != 32]))]: @@ -1230,6 +1247,11 @@ def predict_sigops_ratio(n, dummy_size): standard = hashtype in VALID_SIGHASHES_ECDSA and (p2sh or witv0) add_spender(spenders, "compat/nocsa", hashtype=hashtype, p2sh=p2sh, witv0=witv0, standard=standard, script=CScript([OP_IF, OP_11, pubkey1, OP_CHECKSIGADD, OP_12, OP_EQUAL, OP_ELSE, pubkey1, OP_CHECKSIG, OP_ENDIF]), key=eckey1, sigops_weight=4-3*witv0, inputs=[getter("sign"), b''], failure={"inputs": [getter("sign"), b'\x01']}, **ERR_UNDECODABLE) + # Fails only in executed branch + # For the standard non-witness p2sh case, we need inputs to be minimal push opcodes (not witness stack elements) + # so we use arb non-0 byte push via valid pubkey + add_spender(spenders, "compat/nocsfs", p2sh=p2sh, witv0=witv0, standard=p2sh or witv0, script=CScript([OP_IF, b'', b'', pubs[0], OP_CHECKSIGFROMSTACK, OP_DROP, OP_ENDIF]), inputs=[pubs[0], b''], failure={"inputs": [pubs[0], pubs[0]]}, **ERR_UNDECODABLE) + return spenders @@ -1254,6 +1276,86 @@ def spenders_taproot_nonstandard(): return spenders +def bip348_csfs_spenders_nonstandard(): + """Spenders for testing that pre-active CHECKSIGFROMSTACK usage is discouraged but valid""" + + spenders = [] + + sec = generate_privkey() + pub, _ = compute_xonly_pubkey(sec) + scripts = [ + ("stilltrue", CScript([b'', b'', b'', OP_CHECKSIGFROMSTACK])), + ("still_opsuccess", CScript([OP_RETURN, OP_CHECKSIGFROMSTACK,])), + ] + tap = taproot_construct(pub, scripts) + + # Valid prior to activation but nonstandard + add_spender(spenders, "discouraged_csfs/stilltrue", tap=tap, leaf="stilltrue", standard=False) + add_spender(spenders, "discouraged_csfs/still_opsuccess", tap=tap, leaf="still_opsuccess", standard=False) + + return spenders + +def bip348_csfs_spenders(): + secs = [generate_privkey() for _ in range(2)] + pubs = [compute_xonly_pubkey(sec)[0] for sec in secs] + + CSFS_MSG = random.randbytes(random.randrange(0, 520)) + + # Grow, shrink the message being signed, and pick random bytes + TRUNC_CSFS_MSG = CSFS_MSG[:] if len(CSFS_MSG) > 0 else None + if TRUNC_CSFS_MSG is not None: + prune_index = random.randrange(len(TRUNC_CSFS_MSG)) + TRUNC_CSFS_MSG = TRUNC_CSFS_MSG[:prune_index] + TRUNC_CSFS_MSG[prune_index+1:] + extendable_length = 520 - len(CSFS_MSG) + EXTEND_CSFS_MSG = None + if extendable_length > 0: + EXTEND_CSFS_MSG = CSFS_MSG + random.randbytes(random.randrange(1, extendable_length + 1)) + OTHER_CSFS_MSG = CSFS_MSG + + while OTHER_CSFS_MSG == CSFS_MSG: + OTHER_CSFS_MSG = random.randbytes(random.randrange(0, 520)) + + UNK_PUBKEY = random.randbytes(random.randrange(1, 520)) + while len(UNK_PUBKEY) == 32: + UNK_PUBKEY = random.randbytes(random.randrange(1, 520)) + + # Sigops ratio test is included elsewhere to mix and match with other sigops + scripts = [ + ("simple_csfs", CScript([CSFS_MSG, pubs[0], OP_CHECKSIGFROMSTACK, OP_1, OP_EQUAL])), + ("simple_fail_csfs", CScript([CSFS_MSG, pubs[0], OP_CHECKSIGFROMSTACK, OP_0, OP_EQUAL])), + ("unk_pubkey_csfs", CScript([CSFS_MSG, UNK_PUBKEY, OP_CHECKSIGFROMSTACK])), + ("onearg_csfs", CScript([pubs[0], OP_CHECKSIGFROMSTACK])), + ("twoargs_csfs", CScript([CSFS_MSG, pubs[0], OP_CHECKSIGFROMSTACK])), + ("empty_pk_csfs", CScript([CSFS_MSG, OP_0, OP_CHECKSIGFROMSTACK, OP_0, OP_EQUAL])), + ] + + tap = taproot_construct(pubs[0], scripts) + + spenders = [] + + # "sighash" is actually the bip340 message being directly verified against + 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_SIG_SCHNORR) + if TRUNC_CSFS_MSG is not None: + 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_SIG_SCHNORR) + if EXTEND_CSFS_MSG is not None: + 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_SIG_SCHNORR) + + # Empty signature pushes zero onto stack and continues, unless the pubkey is empty + 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_UNKNOWN_PUBKEY) + + # Unknown pubkey of non-zero size is unconditionally valid regardless of signature (but signature must exist) + 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_STACK_EMPTY) + + # You need three args for CSFS regardless of what is passed + 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_STACK_EMPTY) + add_spender(spenders, comment="bip348_csfs/twoarg", tap=tap, leaf="twoargs_csfs", key=secs[0], inputs=[getter("sign")], sighash=CSFS_MSG, failure={"inputs": []}, **ERR_STACK_EMPTY) + + # If a known pubkey's signature is not 64 bytes or empty it MUST fail immediately + 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_SIG_SCHNORR) + 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_SIG_SCHNORR) + + return spenders + # Consensus validation flags to use in dumps for tests with "legacy/" or "inactive/" prefix. LEGACY_FLAGS = "P2SH,DERSIG,CHECKLOCKTIMEVERIFY,CHECKSEQUENCEVERIFY,WITNESS,NULLDUMMY" # Consensus validation flags to use in dumps for all other tests. @@ -1311,6 +1413,7 @@ def skip_test_if_missing_module(self): def set_test_params(self): self.num_nodes = 1 + self.extra_args = [["-vbparams=checksigfromstack:0:3999999999"]] self.setup_clean_chain = True def block_submit(self, node, txs, msg, err_msg, cb_pubkey=None, fees=0, sigops_weight=0, witness=False, accept=False): @@ -1782,8 +1885,26 @@ def pr(node): def run_test(self): self.gen_test_vectors() + self.log.info("CSFS Pre-activation tests...") + assert_equal(self.nodes[0].getdeploymentinfo()["deployments"]["checksigfromstack"]["heretical"]["status"],"defined") + self.generate(self.nodes[0], 144) + assert_equal(self.nodes[0].getdeploymentinfo()["deployments"]["checksigfromstack"]["heretical"]["status"],"started") + signal_ver = int(self.nodes[0].getdeploymentinfo()["deployments"]["checksigfromstack"]["heretical"]["signal_activate"], 16) + + self.test_spenders(self.nodes[0], bip348_csfs_spenders_nonstandard(), input_counts=[1, 2]) + + self.log.info("Activating CSFS") + now = self.nodes[0].getblock(self.nodes[0].getbestblockhash())["time"] + coinbase_tx = create_coinbase(self.nodes[0].getblockcount() + 1) + block = create_block(hashprev=int(self.nodes[0].getbestblockhash(), 16), ntime=now, coinbase=coinbase_tx, version=signal_ver) + block.solve() + self.nodes[0].submitblock(block.serialize().hex()) + self.generate(self.nodes[0], 288) + assert_equal(self.nodes[0].getdeploymentinfo()["deployments"]["checksigfromstack"]["heretical"]["status"],"active") + self.log.info("Post-activation tests...") - self.test_spenders(self.nodes[0], spenders_taproot_active(), input_counts=[1, 2, 2, 2, 2, 3]) + consensus_spenders = spenders_taproot_active() + bip348_csfs_spenders() + self.test_spenders(self.nodes[0], consensus_spenders, input_counts=[1, 2, 2, 2, 2, 3]) # Run each test twice; once in isolation, and once combined with others. Testing in isolation # means that the standardness is verified in every test (as combined transactions are only standard # when all their inputs are standard). diff --git a/test/functional/test_framework/key.py b/test/functional/test_framework/key.py index 939c7cbef617..7ce11cbae673 100644 --- a/test/functional/test_framework/key.py +++ b/test/functional/test_framework/key.py @@ -272,7 +272,6 @@ def sign_schnorr(key, msg, aux=None, flip_p=False, flip_r=False): aux = bytes(32) assert len(key) == 32 - assert len(msg) == 32 assert len(aux) == 32 sec = int.from_bytes(key, 'big') diff --git a/test/functional/test_framework/script.py b/test/functional/test_framework/script.py index c13dece3274b..ac80f30a7118 100644 --- a/test/functional/test_framework/script.py +++ b/test/functional/test_framework/script.py @@ -254,6 +254,8 @@ def __new__(cls, n): # BIP 342 opcodes (Tapscript) OP_CHECKSIGADD = CScriptOp(0xba) +OP_CHECKSIGFROMSTACK = CScriptOp(0xcc) + OP_INVALIDOPCODE = CScriptOp(0xff) OPCODE_NAMES.update({ @@ -966,7 +968,7 @@ def taproot_construct(pubkey, scripts=None, *, keyver=None, treat_internal_as_in return TaprootInfo(CScript([OP_1, tweaked]), pubkey, negated + 0, tweak, leaves, h, tweaked, keyver) def is_op_success(o): - if o in [OP_CAT]: + if o in [OP_CAT, OP_CHECKSIGFROMSTACK]: return False return o == 0x50 or o == 0x62 or o == 0x89 or o == 0x8a or o == 0x8d or o == 0x8e or (o >= 0x7e and o <= 0x81) or (o >= 0x83 and o <= 0x86) or (o >= 0x95 and o <= 0x99) or (o >= 0xbb and o <= 0xfe)