Skip to content

BIP348 OP_CHECKSIGFROMSTACK #72

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: 28.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/deploymentinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ const std::map<std::string, uint32_t> g_verify_flag_names{
FLAG_NAME(DISCOURAGE_ANYPREVOUT),
FLAG_NAME(OP_CAT),
FLAG_NAME(DISCOURAGE_OP_CAT),
FLAG_NAME(CHECKSIGFROMSTACK),
FLAG_NAME(DISCOURAGE_CHECKSIGFROMSTACK),
};
#undef FLAG_NAME

Expand Down
1 change: 1 addition & 0 deletions src/policy/policy.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ static constexpr unsigned int STANDARD_SCRIPT_VERIFY_FLAGS{MANDATORY_SCRIPT_VERI
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_CHECK_TEMPLATE_VERIFY_HASH |
SCRIPT_VERIFY_DEFAULT_CHECK_TEMPLATE_VERIFY_HASH |
SCRIPT_VERIFY_ANYPREVOUT |
SCRIPT_VERIFY_CHECKSIGFROMSTACK |
SCRIPT_VERIFY_OP_CAT
};

Expand Down
4 changes: 2 additions & 2 deletions src/pubkey.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,12 +229,12 @@ bool XOnlyPubKey::IsFullyValid() const
return secp256k1_xonly_pubkey_parse(secp256k1_context_static, &pubkey, m_keydata.data());
}

bool XOnlyPubKey::VerifySchnorr(const uint256& msg, Span<const unsigned char> sigbytes) const
bool XOnlyPubKey::VerifySchnorr(const Span<const unsigned char> msg, Span<const unsigned char> 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")};
Expand Down
2 changes: 1 addition & 1 deletion src/pubkey.h
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ class XOnlyPubKey
*
* sigbytes must be exactly 64 bytes.
*/
bool VerifySchnorr(const uint256& msg, Span<const unsigned char> sigbytes) const;
bool VerifySchnorr(const Span<const unsigned char> msg, Span<const unsigned char> sigbytes) const;

/** Compute the Taproot tweak as specified in BIP341, with *this as internal
* key:
Expand Down
106 changes: 106 additions & 0 deletions src/script/interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,72 @@ static bool EvalChecksigPreTapscript(const valtype& vchSig, const valtype& vchPu
return true;
}

/**
* Returns false when script execution must immediately fail.
* `success_out` must be set when returning true.
*/
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);

/*
* Implementation follows the BIP348 spec as closely as possible for correctness.
*/

// If the public key size is zero, the script MUST fail and terminate immediately.
if (pubkey_in.size() == 0) {
return set_error(serror, SCRIPT_ERR_PUBKEYTYPE);
}

// If the public key size is 32 bytes, it is considered to be a public key as described in BIP 340:
// If the signature is not the empty vector, the signature is validated against the public key and message according to BIP 340. Validation failure in this case immediately terminates script execution with failure.
if (pubkey_in.size() == 32) {
if (!sig.empty()) {
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);
}
}
}

// If the public key size is not zero and not 32 bytes; the public key is of an unknown public key type.
// Signature verification for unknown public key types succeeds as if signature verification for a known
// public key type had succeeded.
if (pubkey_in.size() != 0 && pubkey_in.size() != 32) {
/*
* New public key version softforks should be defined before this `if` block.
* Generally, the new code should not do anything but terminating the script execution. To avoid
* consensus bugs, it should not modify any existing values (including `success_out`).
*/
if ((flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE) != 0) {
return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_PUBKEYTYPE);
}
}

// If the script did not fail and terminate before this step, regardless of the public key type:

if (sig.empty()) {
// If the signature is the empty vector: An empty vector is pushed onto the stack, and execution continues with the next opcode.
success_out = false;
} else {
// If the signature is not the empty vector:
// - The opcode is counted towards the sigops budget as described in BIP 342.
// - A 1-byte value 0x01 is pushed onto the stack.
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);
}
success_out = true;
}

return true;
}

static bool EvalChecksigTapscript(const valtype& sig, const valtype& pubkey, ScriptExecutionData& execdata, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror, bool& success)
{
assert(sigversion == SigVersion::TAPSCRIPT);
Expand Down Expand Up @@ -1283,6 +1349,40 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& 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);
}
Expand Down Expand Up @@ -1988,6 +2088,12 @@ std::optional<bool> CheckTapscriptOpSuccess(const CScript& exec_script, unsigned
} else if (!(flags & SCRIPT_VERIFY_OP_CAT)) {
return set_success(serror);
}
} else if (opcode == OP_CHECKSIGFROMSTACK) {
if (flags & SCRIPT_VERIFY_DISCOURAGE_CHECKSIGFROMSTACK) {
return set_error(serror, SCRIPT_ERR_DISCOURAGE_OP_SUCCESS);
} else if (!(flags & SCRIPT_VERIFY_CHECKSIGFROMSTACK)) {
return set_success(serror);
}
} else {
// OP_SUCCESS behaviour
if (flags & SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS) {
Expand Down
6 changes: 6 additions & 0 deletions src/script/interpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,12 @@ enum : uint32_t {
SCRIPT_VERIFY_OP_CAT = (1U << 26),
SCRIPT_VERIFY_DISCOURAGE_OP_CAT = (1U << 27),

// Validating OP_CHECKSIGFROMSTACK
SCRIPT_VERIFY_CHECKSIGFROMSTACK = (1U << 28),

// Making OP_CHECKSIGFROMSTACK non-standard
SCRIPT_VERIFY_DISCOURAGE_CHECKSIGFROMSTACK = (1U << 29),

// Constants to point to the highest flag in use. Add new flags above this line.
//
SCRIPT_VERIFY_END_MARKER
Expand Down
3 changes: 3 additions & 0 deletions src/script/script.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ std::string GetOpName(opcodetype opcode)
// Opcode added by BIP 342 (Tapscript)
case OP_CHECKSIGADD : return "OP_CHECKSIGADD";

// Opcodes added by BIP 348
case OP_CHECKSIGFROMSTACK : return "OP_CHECKSIGFROMSTACK";

case OP_INVALIDOPCODE : return "OP_INVALIDOPCODE";

default:
Expand Down
3 changes: 3 additions & 0 deletions src/script/script.h
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,9 @@ enum opcodetype
// Opcode added by BIP 342 (Tapscript)
OP_CHECKSIGADD = 0xba,

// Opcode added by BIP 348 aka OP_SUCCESS204
OP_CHECKSIGFROMSTACK = 0xcc,

OP_INVALIDOPCODE = 0xff,
};

Expand Down
8 changes: 8 additions & 0 deletions src/test/data/tx_invalid.json
Original file line number Diff line number Diff line change
Expand Up @@ -488,5 +488,13 @@
"020000000297e5046ea9335a2ce209f677f0d0a303cd266461ff42316f73c53360a92f52a2000000000151000000000b4c12e6dbe974dadd18ca139e6bce183817ac609f73213aa8aaeae5f123d6b6000000000151000000000ae80300000000000017a9143f163a8747557345ce2e6fe00c1894f2f281795e87d00700000000000017a9144cf13dfda93a7413b7e646611735656e5457657087b80b00000000000017a914868998b49df649c37a88d48c9d4a5b37290e507287a00f00000000000017a914034f9914a77571a6396482e9881745c92c3037c687881300000000000017a914a8238003e1732e2baf4334a8546d72be99af9bae87701700000000000017a91491dbac5d67d5941115a03fc7eaec09f31a5b4dfc87581b00000000000017a914e0c0f19fec3b2993b9c116c798b5429d4515596687401f00000000000017a914d6b40d98d94530f1a1eb57614680813c81a95ccd87282300000000000017a914fb0bfb072bb79611a4323981828108a3cf54b0a687102700000000000017a9149e2d11f06ba667e981b802af10be8dabd08eafff8700000000",
"DEFAULT_CHECK_TEMPLATE_VERIFY_HASH"],

["Test OP_CHECKSIGFROMSTACK, fails immediately with sig for wrong data"],
[[["a2522fa96033c5736f3142ff616426cd03a3d0f077f609e22c5a33a96e04e597",
0,
"1 0x20 0x6e929e9354a357e9a1254feac061741a11c66508786c66b3b29edc79b9c46e19",
155000]],
"0200000000010197e5046ea9335a2ce209f677f0d0a303cd266461ff42316f73c53360a92f52a20000000000ffffffff01f04902000000000022512040104c71081b266fdf4008db8b0a1c3291f2e1cb680753936de9b76dac45a6ef0340b5258eeb9df148d499d14b8e23fe5315a230b7f1dee497a04605426ffe068f2e0920c9b63ba28b1c6cea39c0e659af1658825d23e859c5ae773a0be996f1c4744520feadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef204835c505a762f5e55c2e8eda1c05437d973809a0236178510208a6ac3f7632bfcc008721c050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac000000000",
"P2SH,WITNESS,TAPROOT,CHECKSIGFROMSTACK"],

["Make diffs cleaner by leaving a comment here without comma at the end"]
]
29 changes: 29 additions & 0 deletions src/test/data/tx_valid.json
Original file line number Diff line number Diff line change
Expand Up @@ -672,5 +672,34 @@
"0200000002d58631133c4d4f6188abbd0fa0a7aa64bfde05ce4297e3349b38599ceebaf2e20000000001510000000082b77fb944a40756a6341ee425c85d5850f2acbe6d51a7bcd211929a764b8ec8000000000100000000000ae80300000000000017a914d63e77972529f4db5b32efaa4e06f66ae0b5dc0987d00700000000000017a91488b6705f8c9568c52b55ed712c257f84f64a49f587b80b00000000000017a9142be57e9a179f8d9ff8f33a788d4b54512ea9e36087a00f00000000000017a91429261b4f65796f618908de9f51669014e2e2e04f87881300000000000017a914e3a1e1d24cbba3ca9369248082988bad3ceafcfb87701700000000000017a91403801b0a9591f3b5a00a5ea60fb34dc12b4a691187581b00000000000017a91465248bc2c732db2d88db0b0d677c1514b101025b87401f00000000000017a91420021e3dc4e80c7192c1a3cd04026d22d0f8d38287282300000000000017a914df27596dbff2028791bd7692846e65d16d8fed0d87102700000000000017a9142ed128e911cab04d3277d3635f79d5e3d7e6f4c48700000000",
"CLEANSTACK,DISCOURAGE_CHECK_TEMPLATE_VERIFY_HASH"],

["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"],

["Make diffs cleaner by leaving a comment here without comma at the end"]
]
4 changes: 2 additions & 2 deletions src/test/script_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1748,8 +1748,8 @@ BOOST_AUTO_TEST_CASE(formatscriptflags)
// quick check that FormatScriptFlags reports any unknown/unexpected bits
BOOST_CHECK_EQUAL(FormatScriptFlags(SCRIPT_VERIFY_P2SH), "P2SH");
BOOST_CHECK_EQUAL(FormatScriptFlags(SCRIPT_VERIFY_P2SH | (1u<<31)), "P2SH,0x80000000");
BOOST_CHECK_EQUAL(FormatScriptFlags(SCRIPT_VERIFY_TAPROOT | (1u<<28)), "TAPROOT,0x10000000");
BOOST_CHECK_EQUAL(FormatScriptFlags(1u<<28), "0x10000000");
BOOST_CHECK_EQUAL(FormatScriptFlags(SCRIPT_VERIFY_TAPROOT | (1u<<30)), "TAPROOT,0x40000000");
BOOST_CHECK_EQUAL(FormatScriptFlags(1u<<30), "0x40000000");
}


Expand Down
2 changes: 2 additions & 0 deletions test/functional/test_framework/script.py
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
Loading