Skip to content

Commit e88c9d2

Browse files
reardencodejamesob
authored andcommitted
Implement OP_CHECKSIGFROMSTACK
Some code and ideas from Elements by stevenroose, and sanket1729 Porting help from moonsettler. Tests added to the transaction tests framework. Co-authored-by: James O'Beirne <[email protected]> (cherry picked from commit 15d7aa5)
1 parent 271fd20 commit e88c9d2

File tree

12 files changed

+169
-8
lines changed

12 files changed

+169
-8
lines changed

src/policy/policy.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,8 @@ static constexpr unsigned int STANDARD_SCRIPT_VERIFY_FLAGS{MANDATORY_SCRIPT_VERI
170170
SCRIPT_VERIFY_CONST_SCRIPTCODE |
171171
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION |
172172
SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS |
173-
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE};
173+
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE |
174+
SCRIPT_VERIFY_CHECKSIGFROMSTACK};
174175

175176
/** For convenience, standard but not mandatory verify flags. */
176177
static constexpr unsigned int STANDARD_NOT_MANDATORY_VERIFY_FLAGS{STANDARD_SCRIPT_VERIFY_FLAGS & ~MANDATORY_SCRIPT_VERIFY_FLAGS};

src/pubkey.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,12 +227,13 @@ bool XOnlyPubKey::IsFullyValid() const
227227
return secp256k1_xonly_pubkey_parse(secp256k1_context_static, &pubkey, m_keydata.data());
228228
}
229229

230-
bool XOnlyPubKey::VerifySchnorr(const uint256& msg, Span<const unsigned char> sigbytes) const
230+
bool XOnlyPubKey::VerifySchnorr(
231+
const std::span<const unsigned char> msg, std::span<const unsigned char> sigbytes) const
231232
{
232233
assert(sigbytes.size() == 64);
233234
secp256k1_xonly_pubkey pubkey;
234235
if (!secp256k1_xonly_pubkey_parse(secp256k1_context_static, &pubkey, m_keydata.data())) return false;
235-
return secp256k1_schnorrsig_verify(secp256k1_context_static, sigbytes.data(), msg.begin(), 32, &pubkey);
236+
return secp256k1_schnorrsig_verify(secp256k1_context_static, sigbytes.data(), msg.data(), msg.size(), &pubkey);
236237
}
237238

238239
static const HashWriter HASHER_TAPTWEAK{TaggedHash("TapTweak")};

src/pubkey.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,8 @@ class XOnlyPubKey
263263
*
264264
* sigbytes must be exactly 64 bytes.
265265
*/
266-
bool VerifySchnorr(const uint256& msg, Span<const unsigned char> sigbytes) const;
266+
bool VerifySchnorr(
267+
const std::span<const unsigned char> msg, std::span<const unsigned char> sigbytes) const;
267268

268269
/** Compute the Taproot tweak as specified in BIP341, with *this as internal
269270
* key:

src/script/interpreter.cpp

Lines changed: 92 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,53 @@ static bool EvalChecksigPreTapscript(const valtype& vchSig, const valtype& vchPu
343343
return true;
344344
}
345345

346+
static bool EvalChecksigFromStack(const valtype& sig, const valtype& msg, const valtype& pubkey_in, ScriptExecutionData& execdata, unsigned int flags, SigVersion sigversion, ScriptError* serror, bool& success)
347+
{
348+
/*
349+
* The following validation sequence is consensus critical. Please note how --
350+
* upgradable public key versions precede other rules;
351+
* the script execution fails when using empty signature with invalid public key;
352+
* the script execution fails when using non-empty invalid signature.
353+
*/
354+
success = !sig.empty();
355+
if (success && sigversion == SigVersion::TAPSCRIPT) {
356+
// Implement the sigops/witnesssize ratio test.
357+
// Passing with an upgradable public key version is also counted.
358+
assert(execdata.m_validation_weight_left_init);
359+
execdata.m_validation_weight_left -= VALIDATION_WEIGHT_PER_SIGOP_PASSED;
360+
if (execdata.m_validation_weight_left < 0) {
361+
return set_error(serror, SCRIPT_ERR_TAPSCRIPT_VALIDATION_WEIGHT);
362+
}
363+
}
364+
if (pubkey_in.size() == 0) {
365+
return set_error(serror, SCRIPT_ERR_PUBKEYTYPE);
366+
} else if (pubkey_in.size() == 32) {
367+
if (!success) {
368+
return true;
369+
}
370+
if (sig.size() != 64) {
371+
return set_error(serror, SCRIPT_ERR_SCHNORR_SIG_SIZE);
372+
}
373+
374+
XOnlyPubKey pubkey{pubkey_in};
375+
376+
if (!pubkey.VerifySchnorr(msg, sig)) {
377+
return set_error(serror, SCRIPT_ERR_SCHNORR_SIG);
378+
}
379+
} else {
380+
/*
381+
* New public key version softforks should be defined before this `else` block.
382+
* Generally, the new code should not do anything but failing the script execution. To avoid
383+
* consensus bugs, it should not modify any existing values (including `success`).
384+
*/
385+
if ((flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE) != 0) {
386+
return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_PUBKEYTYPE);
387+
}
388+
}
389+
390+
return true;
391+
}
392+
346393
static bool EvalChecksigTapscript(const valtype& sig, const valtype& pubkey, ScriptExecutionData& execdata, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror, bool& success)
347394
{
348395
assert(sigversion == SigVersion::TAPSCRIPT);
@@ -1213,6 +1260,39 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
12131260
}
12141261
break;
12151262

1263+
case OP_CHECKSIGFROMSTACK:
1264+
{
1265+
// OP_CHECKSIGFROMSTACK is only available in Tapscript
1266+
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) {
1267+
return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
1268+
}
1269+
1270+
// <sig> <msg> <pubkey>
1271+
if (stack.size() < 3) {
1272+
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
1273+
}
1274+
1275+
const valtype& vchSigIn = stacktop(-3);
1276+
const valtype& vchMsg = stacktop(-2);
1277+
const valtype& vchPubKey = stacktop(-1);
1278+
1279+
bool fSuccess = true;
1280+
1281+
// Note that (as with CHECKSIG) if a signature was supplied and its
1282+
// verification fails, we do _not_ push a "false" result to the stack.
1283+
// Rather, we terminate script execution immediately. This might be
1284+
// surprising if you're reading this for the first time.
1285+
if (!EvalChecksigFromStack(vchSigIn, vchMsg, vchPubKey, execdata, flags, sigversion, serror, fSuccess)) {
1286+
return false;
1287+
}
1288+
1289+
popstack(stack);
1290+
popstack(stack);
1291+
popstack(stack);
1292+
stack.push_back(fSuccess ? vchTrue : vchFalse);
1293+
}
1294+
break;
1295+
12161296
default:
12171297
return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
12181298
}
@@ -1807,10 +1887,19 @@ static bool ExecuteWitnessScript(const Span<const valtype>& stack_span, const CS
18071887
}
18081888
// New opcodes will be listed here. May use a different sigversion to modify existing opcodes.
18091889
if (IsOpSuccess(opcode)) {
1810-
if (flags & SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS) {
1811-
return set_error(serror, SCRIPT_ERR_DISCOURAGE_OP_SUCCESS);
1890+
if (opcode == OP_CHECKSIGFROMSTACK) {
1891+
if (flags & SCRIPT_VERIFY_DISCOURAGE_CHECKSIGFROMSTACK) {
1892+
return set_error(serror, SCRIPT_ERR_DISCOURAGE_OP_SUCCESS);
1893+
} else if (!(flags & SCRIPT_VERIFY_CHECKSIGFROMSTACK)) {
1894+
return set_success(serror);
1895+
}
1896+
} else {
1897+
// OP_SUCCESS behaviour
1898+
if (flags & SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS) {
1899+
return set_error(serror, SCRIPT_ERR_DISCOURAGE_OP_SUCCESS);
1900+
}
1901+
return set_success(serror);
18121902
}
1813-
return set_success(serror);
18141903
}
18151904
}
18161905

src/script/interpreter.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,12 @@ enum : uint32_t {
143143
// Making unknown public key versions (in BIP 342 scripts) non-standard
144144
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE = (1U << 20),
145145

146+
// Validating OP_CHECKSIGFROMSTACK(VERIFY)
147+
SCRIPT_VERIFY_CHECKSIGFROMSTACK = (1U << 21),
148+
149+
// Making OP_CHECKSIGFROMSTACK(VERIFY) non-standard
150+
SCRIPT_VERIFY_DISCOURAGE_CHECKSIGFROMSTACK = (1U << 22),
151+
146152
// Constants to point to the highest flag in use. Add new flags above this line.
147153
//
148154
SCRIPT_VERIFY_END_MARKER

src/script/script.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ std::string GetOpName(opcodetype opcode)
150150
// Opcode added by BIP 342 (Tapscript)
151151
case OP_CHECKSIGADD : return "OP_CHECKSIGADD";
152152

153+
// Tapscript expansion
154+
case OP_CHECKSIGFROMSTACK : return "OP_CHECKSIGFROMSTACK";
155+
153156
case OP_INVALIDOPCODE : return "OP_INVALIDOPCODE";
154157

155158
default:

src/script/script.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ enum opcodetype
191191
OP_CHECKSIGVERIFY = 0xad,
192192
OP_CHECKMULTISIG = 0xae,
193193
OP_CHECKMULTISIGVERIFY = 0xaf,
194+
OP_CHECKSIGFROMSTACK = 0xcc,
194195

195196
// expansion
196197
OP_NOP1 = 0xb0,

src/test/data/tx_invalid.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,5 +393,13 @@
393393
["ceafe58e0f6e7d67c0409fbbf673c84c166e3c5d3c24af58f7175b18df3bb3db", 1, "2 0x48 0x3045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303 0x21 0x0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71 0x21 0x0378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71 3 CHECKMULTISIG"]],
394394
"0100000002dbb33bdf185b17f758af243c5d3c6e164cc873f6bb9f40c0677d6e0f8ee5afce000000006b4830450221009627444320dc5ef8d7f68f35010b4c050a6ed0d96b67a84db99fda9c9de58b1e02203e4b4aaa019e012e65d69b487fdf8719df72f488fa91506a80c49a33929f1fd50121022b78b756e2258af13779c1a1f37ea6800259716ca4b7f0b87610e0bf3ab52a01ffffffffdbb33bdf185b17f758af243c5d3c6e164cc873f6bb9f40c0677d6e0f8ee5afce010000009300483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303483045022015bd0139bcccf990a6af6ec5c1c52ed8222e03a0d51c334df139968525d2fcd20221009f9efe325476eb64c3958e4713e9eefe49bf1d820ed58d2112721b134e2a1a5303ffffffff01a0860100000000001976a9149bc0bbdd3024da4d0c38ed1aecf5c68dd1d3fa1288ac00000000", "CONST_SCRIPTCODE"],
395395

396+
["Test OP_CHECKSIGFROMSTACK, fails immediately with sig for wrong data"],
397+
[[["a2522fa96033c5736f3142ff616426cd03a3d0f077f609e22c5a33a96e04e597",
398+
0,
399+
"1 0x20 0x6e929e9354a357e9a1254feac061741a11c66508786c66b3b29edc79b9c46e19",
400+
155000]],
401+
"0200000000010197e5046ea9335a2ce209f677f0d0a303cd266461ff42316f73c53360a92f52a20000000000ffffffff01f04902000000000022512040104c71081b266fdf4008db8b0a1c3291f2e1cb680753936de9b76dac45a6ef0340b5258eeb9df148d499d14b8e23fe5315a230b7f1dee497a04605426ffe068f2e0920c9b63ba28b1c6cea39c0e659af1658825d23e859c5ae773a0be996f1c4744520feadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef204835c505a762f5e55c2e8eda1c05437d973809a0236178510208a6ac3f7632bfcc008721c050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac000000000",
402+
"P2SH,WITNESS,TAPROOT,CHECKSIGFROMSTACK"],
403+
396404
["Make diffs cleaner by leaving a comment here without comma at the end"]
397405
]

src/test/data/tx_valid.json

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,5 +520,34 @@
520520
[[["1111111111111111111111111111111111111111111111111111111111111111", 0, "0x00 0x14 0x751e76e8199196d454941c45d1b3a323f1433bd6", 5000000]],
521521
"0100000000010111111111111111111111111111111111111111111111111111111111111111110000000000ffffffff0130244c0000000000fd02014cdc1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111175210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ac02483045022100c1a4a6581996a7fdfea77d58d537955a5655c1d619b6f3ab6874f28bb2e19708022056402db6fede03caae045a3be616a1a2d0919a475ed4be828dc9ff21f24063aa01210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179800000000", "NONE"],
522522

523+
["Test OP_CHECKSIGFROMSTACK"],
524+
[[["e2f2baee9c59389b34e39742ce05debf64aaa7a00fbdab88614f4d3c133186d5",
525+
0,
526+
"1 0x20 0xed98cc178a5e3f2537ec8bf5ab9a14e56b8a188d666ba6ce788405e849ba7da8",
527+
155000]],
528+
"02000000000101d58631133c4d4f6188abbd0fa0a7aa64bfde05ce4297e3349b38599ceebaf2e20000000000ffffffff01f0490200000000002251203408099b8f38a71ab6dfafdf0b266bd0a0f58096b5c453624c752bae6c0f19560340cd3e61f2754dd13e51a6d86d18092f795c626d36deaf0cf076a87648d9f4e4cfceaaa8e8a7eee1ee13dd09ef2c14eedd475f4e9adcf8a2391b910271b2203aa24320deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef208fdb638cf9201fcae809f31b7d5b5ef9ae712cd374c8c89b06d52b9d2c3885bfcc21c050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac000000000",
529+
"DISCOURAGE_CHECKSIGFROMSTACK"],
530+
["Test OP_CHECKSIGFROMSTACK succeeds with unknown key type"],
531+
[[["e2f2baee9c59389b34e39742ce05debf64aaa7a00fbdab88614f4d3c133186d5",
532+
0,
533+
"1 0x20 0xde96616e5e3961cbbd7bab3ea0e6b6e1ace088299857136fbb3703454c784afb",
534+
155000]],
535+
"02000000000101d58631133c4d4f6188abbd0fa0a7aa64bfde05ce4297e3349b38599ceebaf2e20000000000ffffffff01f0490200000000002251203408099b8f38a71ab6dfafdf0b266bd0a0f58096b5c453624c752bae6c0f19560340cd3e61f2754dd13e51a6d86d18092f795c626d36deaf0cf076a87648d9f4e4cfceaaa8e8a7eee1ee13dd09ef2c14eedd475f4e9adcf8a2391b910271b2203aa24420deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef21038fdb638cf9201fcae809f31b7d5b5ef9ae712cd374c8c89b06d52b9d2c3885bfcc21c050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac000000000",
536+
"DISCOURAGE_CHECKSIGFROMSTACK,DISCOURAGE_UPGRADABLE_PUBKEYTYPE"],
537+
["Test OP_CHECKSIGFROMSTACK yields 0 for 0-sig"],
538+
[[["e2f2baee9c59389b34e39742ce05debf64aaa7a00fbdab88614f4d3c133186d5",
539+
0,
540+
"1 0x20 0x7f3db202bc0db8c15de91c5da0dd64bd52ae81f5847cda623e1304c524cad314",
541+
155000]],
542+
"02000000000101d58631133c4d4f6188abbd0fa0a7aa64bfde05ce4297e3349b38599ceebaf2e20000000000ffffffff01f0490200000000002251203408099b8f38a71ab6dfafdf0b266bd0a0f58096b5c453624c752bae6c0f195603004520deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef208fdb638cf9201fcae809f31b7d5b5ef9ae712cd374c8c89b06d52b9d2c3885bfcc008721c050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac000000000",
543+
"DISCOURAGE_CHECKSIGFROMSTACK"],
544+
["Test OP_CHECKSIGFROMSTACK, shorter message"],
545+
[[["e2f2baee9c59389b34e39742ce05debf64aaa7a00fbdab88614f4d3c133186d5",
546+
0,
547+
"1 0x20 0x313a784205aef89c9d203c1e4cfacd2e31fa55f42dcd77e5e2db9d0513d50827",
548+
155000]],
549+
"02000000000101d58631133c4d4f6188abbd0fa0a7aa64bfde05ce4297e3349b38599ceebaf2e20000000000ffffffff01f0490200000000002251203408099b8f38a71ab6dfafdf0b266bd0a0f58096b5c453624c752bae6c0f195603403c5a935ce7a3856bc3e75eae403a21ff2e5a9f919c0f6f4d6bf7f58c834c13484882fc6f98587fe48e6945a49c0ca4fc62fb5f641a216ea62ac2dbc0071976833411636865636b73696766726f6d737461636b208fdb638cf9201fcae809f31b7d5b5ef9ae712cd374c8c89b06d52b9d2c3885bfcc21c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac000000000",
550+
"DISCOURAGE_CHECKSIGFROMSTACK"],
551+
523552
["Make diffs cleaner by leaving a comment here without comma at the end"]
524553
]

src/test/transaction_tests.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ static std::map<std::string, unsigned int> mapFlagNames = {
7171
{std::string("DISCOURAGE_UPGRADABLE_PUBKEYTYPE"), (unsigned int)SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE},
7272
{std::string("DISCOURAGE_OP_SUCCESS"), (unsigned int)SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS},
7373
{std::string("DISCOURAGE_UPGRADABLE_TAPROOT_VERSION"), (unsigned int)SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION},
74+
{std::string("CHECKSIGFROMSTACK"), (unsigned int)SCRIPT_VERIFY_CHECKSIGFROMSTACK},
75+
{std::string("DISCOURAGE_CHECKSIGFROMSTACK"), (unsigned int)SCRIPT_VERIFY_DISCOURAGE_CHECKSIGFROMSTACK},
7476
};
7577

7678
unsigned int ParseScriptFlags(std::string strFlags)

0 commit comments

Comments
 (0)