Skip to content

Commit

Permalink
Implement OP_CHECKSIGFROMSTACK(VERIFY)
Browse files Browse the repository at this point in the history
Some code and ideas from Elements by stevenroose, and sanket1729
Porting help from moonsettler

Tests added to the transaction tests framework.
  • Loading branch information
reardencode committed Jan 10, 2024
1 parent 29c8c7a commit 4c00904
Show file tree
Hide file tree
Showing 11 changed files with 210 additions and 15 deletions.
4 changes: 2 additions & 2 deletions src/pubkey.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -217,12 +217,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 @@ -258,7 +258,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
103 changes: 101 additions & 2 deletions src/script/interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,67 @@ static bool EvalChecksigPreTapscript(const valtype& vchSig, const valtype& vchPu
return 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)
{
assert(sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0 || 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 = !sig.empty();
if (success && sigversion == SigVersion::TAPSCRIPT) {
// 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) return true;
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 if (pubkey_in.size() == 33 && (pubkey_in[0] == 0x02 || pubkey_in[0] == 0x03) && (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0)) {
// Pubkeys of length 33 are only constrained in legacy and segwitv0. In these script types only those beginning
// with 0x02 or 0x03 are constrained. Others are unknown pubkey types.
if (!success) return true;

// Add sighash flag to pass format validation
valtype vchSig{sig.begin(), sig.begin() + sig.size()};
vchSig.emplace_back(SIGHASH_ALL);
if (!CheckSignatureEncoding(vchSig, flags | SCRIPT_VERIFY_LOW_S, serror)) {
// serror is set
return false;
}

if (msg.size() != 32) return set_error(serror, SCRIPT_ERR_INVALID_DATA_LENGTH);

CPubKey pubkey(pubkey_in);
if (!pubkey.IsValid()) return set_error(serror, SCRIPT_ERR_PUBKEYTYPE);

if (!pubkey.Verify(uint256(msg), sig)) return set_error(serror, SCRIPT_ERR_SIG_NULLFAIL);
} 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;
}

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 @@ -626,7 +687,7 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
}
break;

case OP_NOP1: case OP_NOP5:
case OP_NOP1:
case OP_NOP6: case OP_NOP7: case OP_NOP8: case OP_NOP9: case OP_NOP10:
{
if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS)
Expand Down Expand Up @@ -1259,6 +1320,44 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
break;
}

case OP_CHECKSIGFROMSTACK:
case OP_CHECKSIGFROMSTACKVERIFY: {
// if flags not enabled; treat OP_CHECKSIGFROMSTACKVERIFY as a NOP5
if (opcode == OP_CHECKSIGFROMSTACKVERIFY && !(flags & SCRIPT_VERIFY_LNHANCE)) {
break;
}
// OP_CHECKSIGFROMSTACK is only available in Tapscript
if (opcode == OP_CHECKSIGFROMSTACK && (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0)) {
return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
}
if (flags & SCRIPT_VERIFY_DISCOURAGE_LNHANCE) {
if (opcode == OP_CHECKSIGFROMSTACKVERIFY) return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS);
return set_error(serror, SCRIPT_ERR_DISCOURAGE_OP_SUCCESS);
}

// sig message pubkey
if (stack.size() < 3)
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);

const valtype& vchSigIn = stacktop(-3);
const valtype& vchMsg = stacktop(-2);
const valtype& vchPubKey = stacktop(-1);

bool fSuccess = true;
if (!EvalChecksigFromStack(vchSigIn, vchMsg, vchPubKey, execdata, flags, sigversion, serror, fSuccess)) return false;

if (opcode == OP_CHECKSIGFROMSTACKVERIFY) {
if (!fSuccess) return set_error(serror, SCRIPT_ERR_CHECKSIGVERIFY);
break;
}

popstack(stack);
popstack(stack);
popstack(stack);
stack.push_back(fSuccess ? vchTrue : vchFalse);
break;
}

default:
return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
}
Expand Down Expand Up @@ -1933,7 +2032,7 @@ static bool ExecuteWitnessScript(const Span<const valtype>& stack_span, const CS
// Note how this condition would not be reached if an unknown OP_SUCCESSx was found
return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
}
if ((flags & SCRIPT_VERIFY_LNHANCE) && opcode == OP_INTERNALKEY) {
if ((flags & SCRIPT_VERIFY_LNHANCE) && (opcode == OP_CHECKSIGFROMSTACK || opcode == OP_INTERNALKEY)) {
continue;
}
// New opcodes will be listed here. May use a different sigversion to modify existing opcodes.
Expand Down
7 changes: 5 additions & 2 deletions src/script/script.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ std::string GetOpName(opcodetype opcode)
case OP_CHECKLOCKTIMEVERIFY : return "OP_CHECKLOCKTIMEVERIFY";
case OP_CHECKSEQUENCEVERIFY : return "OP_CHECKSEQUENCEVERIFY";
case OP_CHECKTEMPLATEVERIFY : return "OP_CHECKTEMPLATEVERIFY";
case OP_NOP5 : return "OP_NOP5";
case OP_CHECKSIGFROMSTACKVERIFY: return "OP_CHECKSIGFROMSTACKVERIFY";
case OP_NOP6 : return "OP_NOP6";
case OP_NOP7 : return "OP_NOP7";
case OP_NOP8 : return "OP_NOP8";
Expand All @@ -146,7 +146,9 @@ std::string GetOpName(opcodetype opcode)
// Opcode added by BIP 342 (Tapscript)
case OP_CHECKSIGADD : return "OP_CHECKSIGADD";

// Tapscript expansion
case OP_INTERNALKEY : return "OP_INTERNALKEY";
case OP_CHECKSIGFROMSTACK : return "OP_CHECKSIGFROMSTACK";

case OP_INVALIDOPCODE : return "OP_INVALIDOPCODE";

Expand All @@ -165,7 +167,8 @@ unsigned int CScript::GetSigOpCount(bool fAccurate) const
opcodetype opcode;
if (!GetOp(pc, opcode))
break;
if (opcode == OP_CHECKSIG || opcode == OP_CHECKSIGVERIFY)
if (opcode == OP_CHECKSIG || opcode == OP_CHECKSIGVERIFY ||
opcode == OP_CHECKSIGFROMSTACK || opcode == OP_CHECKSIGFROMSTACKVERIFY)
n++;
else if (opcode == OP_CHECKMULTISIG || opcode == OP_CHECKMULTISIGVERIFY)
{
Expand Down
6 changes: 5 additions & 1 deletion src/script/script.h
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,8 @@ enum opcodetype
OP_NOP3 = OP_CHECKSEQUENCEVERIFY,
OP_CHECKTEMPLATEVERIFY = 0xb3,
OP_NOP4 = OP_CHECKTEMPLATEVERIFY,
OP_NOP5 = 0xb4,
OP_CHECKSIGFROMSTACKVERIFY = 0xb4,
OP_NOP5 = OP_CHECKSIGFROMSTACKVERIFY,
OP_NOP6 = 0xb5,
OP_NOP7 = 0xb6,
OP_NOP8 = 0xb7,
Expand All @@ -207,7 +208,10 @@ enum opcodetype

// Opcode added by BIP 342 (Tapscript)
OP_CHECKSIGADD = 0xba,

// Tapscript expansion
OP_INTERNALKEY = 0xbb,
OP_CHECKSIGFROMSTACK = 0xbc,

OP_INVALIDOPCODE = 0xff,
};
Expand Down
2 changes: 2 additions & 0 deletions src/script/script_error.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ std::string ScriptErrorString(const ScriptError serror)
return "OP_RETURN was encountered";
case SCRIPT_ERR_UNBALANCED_CONDITIONAL:
return "Invalid OP_IF construction";
case SCRIPT_ERR_INVALID_DATA_LENGTH:
return "Invalid data length for operation";
case SCRIPT_ERR_NEGATIVE_LOCKTIME:
return "Negative locktime";
case SCRIPT_ERR_UNSATISFIED_LOCKTIME:
Expand Down
1 change: 1 addition & 0 deletions src/script/script_error.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ typedef enum ScriptError_t
SCRIPT_ERR_INVALID_STACK_OPERATION,
SCRIPT_ERR_INVALID_ALTSTACK_OPERATION,
SCRIPT_ERR_UNBALANCED_CONDITIONAL,
SCRIPT_ERR_INVALID_DATA_LENGTH,

/* CHECKLOCKTIMEVERIFY and CHECKSEQUENCEVERIFY */
SCRIPT_ERR_NEGATIVE_LOCKTIME,
Expand Down
11 changes: 5 additions & 6 deletions src/test/data/script_tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,8 @@
["'abcdefghijklmnopqrstuvwxyz'", "HASH256 0x4c 0x20 0xca139bc10c2f660da42666f72e89a225936fc60f193c161124a672050c434671 EQUAL", "P2SH,STRICTENC", "OK"],


["1","NOP1 CHECKLOCKTIMEVERIFY CHECKSEQUENCEVERIFY CHECKTEMPLATEVERIFY NOP5 NOP6 NOP7 NOP8 NOP9 NOP10 1 EQUAL", "P2SH,STRICTENC", "OK"],
["'NOP_1_to_10' NOP1 CHECKLOCKTIMEVERIFY CHECKSEQUENCEVERIFY CHECKTEMPLATEVERIFY NOP5 NOP6 NOP7 NOP8 NOP9 NOP10","'NOP_1_to_10' EQUAL", "P2SH,STRICTENC", "OK"],
["1","NOP1 CHECKLOCKTIMEVERIFY CHECKSEQUENCEVERIFY CHECKTEMPLATEVERIFY CHECKSIGFROMSTACKVERIFY NOP6 NOP7 NOP8 NOP9 NOP10 1 EQUAL", "P2SH,STRICTENC", "OK"],
["'NOP_1_to_10' NOP1 CHECKLOCKTIMEVERIFY CHECKSEQUENCEVERIFY CHECKTEMPLATEVERIFY CHECKSIGFROMSTACKVERIFY NOP6 NOP7 NOP8 NOP9 NOP10","'NOP_1_to_10' EQUAL", "P2SH,STRICTENC", "OK"],

["1", "NOP", "P2SH,STRICTENC,DISCOURAGE_UPGRADABLE_NOPS", "OK", "Discourage NOPx flag allows OP_NOP"],

Expand Down Expand Up @@ -457,7 +457,7 @@
["NOP", "CHECKLOCKTIMEVERIFY 1", "P2SH,STRICTENC", "OK"],
["NOP", "CHECKSEQUENCEVERIFY 1", "P2SH,STRICTENC", "OK"],
["NOP", "CHECKTEMPLATEVERIFY 1", "P2SH,STRICTENC", "OK"],
["NOP", "NOP5 1", "P2SH,STRICTENC", "OK"],
["NOP", "CHECKSIGFROMSTACKVERIFY 1", "P2SH,STRICTENC", "OK"],
["NOP", "NOP6 1", "P2SH,STRICTENC", "OK"],
["NOP", "NOP7 1", "P2SH,STRICTENC", "OK"],
["NOP", "NOP8 1", "P2SH,STRICTENC", "OK"],
Expand Down Expand Up @@ -870,12 +870,11 @@
["2 2 LSHIFT", "8 EQUAL", "P2SH,STRICTENC", "DISABLED_OPCODE", "disabled"],
["2 1 RSHIFT", "1 EQUAL", "P2SH,STRICTENC", "DISABLED_OPCODE", "disabled"],

["1", "NOP1 CHECKLOCKTIMEVERIFY CHECKSEQUENCEVERIFY CHECKTEMPLATEVERIFY NOP5 NOP6 NOP7 NOP8 NOP9 NOP10 2 EQUAL", "P2SH,STRICTENC", "EVAL_FALSE"],
["'NOP_1_to_10' NOP1 CHECKLOCKTIMEVERIFY CHECKSEQUENCEVERIFY CHECKTEMPLATEVERIFY NOP5 NOP6 NOP7 NOP8 NOP9 NOP10","'NOP_1_to_11' EQUAL", "P2SH,STRICTENC", "EVAL_FALSE"],
["1", "NOP1 CHECKLOCKTIMEVERIFY CHECKSEQUENCEVERIFY CHECKTEMPLATEVERIFY CHECKSIGFROMSTACKVERIFY NOP6 NOP7 NOP8 NOP9 NOP10 2 EQUAL", "P2SH,STRICTENC", "EVAL_FALSE"],
["'NOP_1_to_10' NOP1 CHECKLOCKTIMEVERIFY CHECKSEQUENCEVERIFY CHECKTEMPLATEVERIFY CHECKSIGFROMSTACKVERIFY NOP6 NOP7 NOP8 NOP9 NOP10","'NOP_1_to_11' EQUAL", "P2SH,STRICTENC", "EVAL_FALSE"],

["Ensure 100% coverage of discouraged NOPS"],
["1", "NOP1", "P2SH,DISCOURAGE_UPGRADABLE_NOPS", "DISCOURAGE_UPGRADABLE_NOPS"],
["1", "NOP5", "P2SH,DISCOURAGE_UPGRADABLE_NOPS", "DISCOURAGE_UPGRADABLE_NOPS"],
["1", "NOP6", "P2SH,DISCOURAGE_UPGRADABLE_NOPS", "DISCOURAGE_UPGRADABLE_NOPS"],
["1", "NOP7", "P2SH,DISCOURAGE_UPGRADABLE_NOPS", "DISCOURAGE_UPGRADABLE_NOPS"],
["1", "NOP8", "P2SH,DISCOURAGE_UPGRADABLE_NOPS", "DISCOURAGE_UPGRADABLE_NOPS"],
Expand Down
16 changes: 16 additions & 0 deletions src/test/data/tx_invalid.json
Original file line number Diff line number Diff line change
Expand Up @@ -488,5 +488,21 @@
"020000000297e5046ea9335a2ce209f677f0d0a303cd266461ff42316f73c53360a92f52a2000000000151000000000b4c12e6dbe974dadd18ca139e6bce183817ac609f73213aa8aaeae5f123d6b6000000000151000000000ae80300000000000017a9143f163a8747557345ce2e6fe00c1894f2f281795e87d00700000000000017a9144cf13dfda93a7413b7e646611735656e5457657087b80b00000000000017a914868998b49df649c37a88d48c9d4a5b37290e507287a00f00000000000017a914034f9914a77571a6396482e9881745c92c3037c687881300000000000017a914a8238003e1732e2baf4334a8546d72be99af9bae87701700000000000017a91491dbac5d67d5941115a03fc7eaec09f31a5b4dfc87581b00000000000017a914e0c0f19fec3b2993b9c116c798b5429d4515596687401f00000000000017a914d6b40d98d94530f1a1eb57614680813c81a95ccd87282300000000000017a914fb0bfb072bb79611a4323981828108a3cf54b0a687102700000000000017a9149e2d11f06ba667e981b802af10be8dabd08eafff8700000000",
"LNHANCE"],

["Test OP_CHECKSIGFROMSTACK, fails with sig for wrong data"],
[[["a2522fa96033c5736f3142ff616426cd03a3d0f077f609e22c5a33a96e04e597",
0,
"1 0x20 0x2fb0c361166d694c1fa1a9955518170c89d51d5debad1e53a2478dd2207d0e0a",
155000]],
"0200000000010197e5046ea9335a2ce209f677f0d0a303cd266461ff42316f73c53360a92f52a20000000000ffffffff01f0490200000000002251202ca3bc76489a54904ad2507005789afc1e6b362b451be89f69de39ddf9ba8abf034079001cd9669b1a54c67ee61dc2ef980a1d5feeb4c677978b05c9de26d7533f9f9ac4f526c7fdfd65351f0a4756d05d79a91639290fea8669d2bebf86ff351e0d4320feadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef20f4b16c96ad395b47dd9079faf553a0d9e8ce1da8729da811b8a93954a756b883bc21c050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac000000000",
"P2SH,WITNESS,TAPROOT,LNHANCE"],
["Test OP_CHECKSIGFROMSTACKVERIFY, fails immediately with changed sig"],
[[["a2522fa96033c5736f3142ff616426cd03a3d0f077f609e22c5a33a96e04e597",
0,
"1 0x20 0x53af700e9733abe9f4b8518fcec7a1814f723b78329da6a46d14b5bb9a106baf",
155000]],
"0200000000010197e5046ea9335a2ce209f677f0d0a303cd266461ff42316f73c53360a92f52a20000000000ffffffff01f0490200000000002251202ca3bc76489a54904ad2507005789afc1e6b362b451be89f69de39ddf9ba8abf0340f7d74789c5aff9168c2f174d14535a99755aeada9efe1ad130489cdb32d922192aac7564831dabd0e5be832ec102b9a43db2a1f1020660648869ebde3e9f69484520deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef205dba240412eeea155985ac48a7dc1250e5d8d1bbe0368dbf137ab1173f801764bc008721c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac000000000",
"P2SH,WITNESS,TAPROOT,LNHANCE"],


["Make diffs cleaner by leaving a comment here without comma at the end"]
]
Loading

0 comments on commit 4c00904

Please sign in to comment.