Skip to content

Commit d150181

Browse files
committed
Add OP_CHECKCONTRACTVERIFY
1 parent 32c9b12 commit d150181

File tree

9 files changed

+318
-7
lines changed

9 files changed

+318
-7
lines changed

src/pubkey.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,50 @@ std::optional<std::pair<XOnlyPubKey, bool>> XOnlyPubKey::CreateTapTweak(const ui
275275
return ret;
276276
}
277277

278+
bool XOnlyPubKey::CheckDoubleTweak(const XOnlyPubKey& naked_key, const std::vector<unsigned char>& data, const uint256* merkle_root) const
279+
{
280+
int parity;
281+
282+
secp256k1_xonly_pubkey internal_xonly;
283+
284+
if (data.empty()) {
285+
// No data tweak; the internal key is the naked key
286+
if (!secp256k1_xonly_pubkey_parse(secp256k1_context_static, &internal_xonly, naked_key.data())) return false;
287+
} else {
288+
// Compute the sha256 of naked_key || data
289+
uint256 data_tweak = (HashWriter{} << naked_key << MakeUCharSpan(data)).GetSHA256();
290+
291+
secp256k1_xonly_pubkey naked_key_parsed;
292+
if (!secp256k1_xonly_pubkey_parse(secp256k1_context_static, &naked_key_parsed, naked_key.data())) return false;
293+
secp256k1_pubkey internal;
294+
if (!secp256k1_xonly_pubkey_tweak_add(secp256k1_context_static, &internal, &naked_key_parsed, data_tweak.data())) return false;
295+
if (!secp256k1_xonly_pubkey_from_pubkey(secp256k1_context_static, &internal_xonly, &parity, &internal)) return false;
296+
}
297+
298+
secp256k1_xonly_pubkey expected_xonly;
299+
if (!secp256k1_xonly_pubkey_parse(secp256k1_context_static, &expected_xonly, m_keydata.data())) return false;
300+
301+
if (merkle_root != nullptr) {
302+
// Compute the taptweak based on merkle_root
303+
unsigned char pubkey_bytes[32];
304+
secp256k1_xonly_pubkey_serialize(secp256k1_context_static, pubkey_bytes, &internal_xonly);
305+
XOnlyPubKey internal_key = XOnlyPubKey(pubkey_bytes);
306+
307+
uint256 tweak = internal_key.ComputeTapTweakHash(merkle_root);
308+
309+
secp256k1_pubkey result;
310+
if (!secp256k1_xonly_pubkey_tweak_add(secp256k1_context_static, &result, &internal_xonly, tweak.begin())) return false;
311+
312+
secp256k1_xonly_pubkey result_xonly;
313+
if (!secp256k1_xonly_pubkey_from_pubkey(secp256k1_context_static, &result_xonly, &parity, &result)) return false;
314+
315+
return secp256k1_xonly_pubkey_cmp(secp256k1_context_static, &result_xonly, &expected_xonly) == 0;
316+
} else {
317+
// If merkle_root is nullptr, compare internal_xonly with expected_xonly
318+
return secp256k1_xonly_pubkey_cmp(secp256k1_context_static, &internal_xonly, &expected_xonly) == 0;
319+
}
320+
}
321+
278322

279323
bool CPubKey::Verify(const uint256 &hash, const std::vector<unsigned char>& vchSig) const {
280324
if (!IsValid())

src/pubkey.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,12 @@ class XOnlyPubKey
282282
/** Construct a Taproot tweaked output point with this point as internal key. */
283283
std::optional<std::pair<XOnlyPubKey, bool>> CreateTapTweak(const uint256* merkle_root) const;
284284

285+
/** Verify that this key is obtained from the x-only pubkey `naked` after applying in sequence:
286+
* - the tweak with `data` (this tweak is skipped if `data` is empty);
287+
* - the taptweak with `merkle_root` (unless it's null).
288+
*/
289+
bool CheckDoubleTweak(const XOnlyPubKey& naked_key, const std::vector<unsigned char>& data, const uint256* merkle_root) const;
290+
285291
/** Returns a list of CKeyIDs for the CPubKeys that could have been used to create this XOnlyPubKey.
286292
* This is needed for key lookups since keys are indexed by CKeyID.
287293
*/

src/script/interpreter.cpp

Lines changed: 151 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,19 @@ static bool EvalChecksig(const valtype& sig, const valtype& pubkey, CScript::con
419419
assert(false);
420420
}
421421

422+
//! Unspendable internal pubkey suggested in BIP-341.
423+
const std::vector<unsigned char> BIP341_NUMS_POINT{
424+
0x50, 0x92, 0x9b, 0x74, 0xc1, 0xa0, 0x49, 0x54, 0xb7, 0x8b, 0x4b, 0x60, 0x35, 0xe9, 0x7a, 0x5e,
425+
0x07, 0x8a, 0x5a, 0x0f, 0x28, 0xec, 0x96, 0xd5, 0x47, 0xbf, 0xee, 0x9a, 0xce, 0x80, 0x3a, 0xc0,
426+
};
427+
428+
//! Flag to mark an OP_CHECKCONTRACVERIFY as referring to an input.
429+
const int CCV_FLAG_CHECK_INPUT = -1;
430+
//! Flag to specify that an OP_CHECKCONTRACVERIFY which refers to an output does not check the output amount.
431+
const int CCV_FLAG_IGNORE_OUTPUT_AMOUNT = 1;
432+
//! Flag to specify that an OP_CHECKCONTRACVERIFY referring to an output deducts the amount of its output from the current input amount for future calls.
433+
const int CCV_FLAG_DEDUCT_OUTPUT_AMOUNT = 2;
434+
422435
bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* serror)
423436
{
424437
static const CScriptNum bnZero(0);
@@ -1171,6 +1184,51 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
11711184
}
11721185
break;
11731186

1187+
case OP_CHECKCONTRACTVERIFY:
1188+
{
1189+
// OP_CHECKCONTRACTVERIFY is only available in Tapscript
1190+
if (sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0) return set_error(serror, SCRIPT_ERR_BAD_OPCODE);
1191+
1192+
// we expect at least the flag to be on the stack
1193+
if (stack.empty())
1194+
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
1195+
1196+
// initially, read only a single parameter at the top of stack
1197+
int flags = CScriptNum(stacktop(-1), fRequireMinimal).getint();
1198+
if (flags < -1 || flags > CCV_FLAG_DEDUCT_OUTPUT_AMOUNT) {
1199+
// undefined values of the flags; keep OP_SUCCESS behavior
1200+
// in order to enable future upgrades via soft-fork
1201+
stack = { {1} };
1202+
return set_success(serror);
1203+
}
1204+
1205+
// all currently defined versions require exactly 5 stack elements
1206+
1207+
// (data index pk taptree flags -- )
1208+
if (stack.size() < 5)
1209+
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
1210+
1211+
valtype& data = stacktop(-5);
1212+
int index = CScriptNum(stacktop(-4), fRequireMinimal).getint();
1213+
valtype& pk = stacktop(-3);
1214+
valtype& taptree = stacktop(-2);
1215+
1216+
if (!pk.empty() && pk != std::vector<unsigned char>{0x81} && pk.size() != 32) {
1217+
return set_error(serror, SCRIPT_ERR_CHECKCONTRACTVERIFY_WRONG_ARGS);
1218+
}
1219+
1220+
if (!checker.CheckContract(flags, index, pk, data, taptree, execdata, serror)) {
1221+
return false; // serror is set
1222+
}
1223+
1224+
popstack(stack);
1225+
popstack(stack);
1226+
popstack(stack);
1227+
popstack(stack);
1228+
popstack(stack);
1229+
}
1230+
break;
1231+
11741232
case OP_CHECKMULTISIG:
11751233
case OP_CHECKMULTISIGVERIFY:
11761234
{
@@ -1965,6 +2023,94 @@ bool GenericTransactionSignatureChecker<T>::CheckDefaultCheckTemplateVerifyHash(
19652023
return HandleMissingData(m_mdb);
19662024
}
19672025
}
2026+
2027+
template <class T>
2028+
bool GenericTransactionSignatureChecker<T>::CheckContract(int flags, int index, const std::vector<unsigned char>& pubkey, const std::vector<unsigned char>& data, const std::vector<unsigned char>& taptree, ScriptExecutionData& ScriptExecutionData, ScriptError* serror) const
2029+
{
2030+
assert(ScriptExecutionData.m_internal_key.has_value());
2031+
2032+
if (!(txdata->m_bip341_taproot_ready && txdata->m_spent_outputs_ready)) {
2033+
return HandleMissingData(m_mdb);
2034+
}
2035+
2036+
bool use_current_taptree = taptree.size() == 1 && taptree.data()[0] == 0x81;
2037+
bool use_current_pubkey = pubkey.size() == 1 && pubkey.data()[0] == 0x81;
2038+
2039+
uint256 merkle_tree;
2040+
const uint256 *merkle_tree_ptr = nullptr;
2041+
if (taptree.empty()) {
2042+
// no taptweak, leave nullptr
2043+
} else if (use_current_taptree) {
2044+
merkle_tree_ptr = &ScriptExecutionData.m_taproot_merkle_root;
2045+
} else if (taptree.size() == 32) {
2046+
merkle_tree = uint256(taptree);
2047+
merkle_tree_ptr = &merkle_tree;
2048+
} else {
2049+
return set_error(serror, SCRIPT_ERR_CHECKCONTRACTVERIFY_WRONG_ARGS);
2050+
}
2051+
2052+
XOnlyPubKey initialXOnlyKey;
2053+
if (use_current_pubkey) {
2054+
initialXOnlyKey = ScriptExecutionData.m_internal_key.value();
2055+
} else {
2056+
const std::vector<unsigned char> initial_pubkey(pubkey.empty() ? BIP341_NUMS_POINT : pubkey);
2057+
initialXOnlyKey = XOnlyPubKey{Span<const unsigned char>{initial_pubkey.data(), initial_pubkey.data() + 32}};
2058+
}
2059+
2060+
if (index == -1) {
2061+
index = nIn;
2062+
}
2063+
2064+
auto indexLimit = (flags == CCV_FLAG_CHECK_INPUT ? txTo->vin.size() : txTo->vout.size());
2065+
if (index < 0 || index >= static_cast<int>(indexLimit)) {
2066+
return set_error(serror, SCRIPT_ERR_CHECKCONTRACTVERIFY_OUT_OF_BOUNDS);
2067+
}
2068+
2069+
CScript scriptPubKey = (flags == CCV_FLAG_CHECK_INPUT) ? txdata->m_spent_outputs[index].scriptPubKey : txTo->vout.at(index).scriptPubKey;
2070+
2071+
if (scriptPubKey.size() != 1 + 1 + 32 || scriptPubKey[0] != OP_1 || scriptPubKey[1] != 32) {
2072+
return set_error(serror, SCRIPT_ERR_CHECKCONTRACTVERIFY_WRONG_ARGS);
2073+
}
2074+
2075+
const XOnlyPubKey finalXOnlyKey{Span<const unsigned char>{scriptPubKey.data() + 2, scriptPubKey.data() + 34}};
2076+
2077+
if (!finalXOnlyKey.CheckDoubleTweak(initialXOnlyKey, data, merkle_tree_ptr)) {
2078+
return set_error(serror, SCRIPT_ERR_CHECKCONTRACTVERIFY_MISMATCH);
2079+
}
2080+
2081+
2082+
if (!ScriptExecutionData.m_ccv_amount_init) {
2083+
ScriptExecutionData.m_ccv_amount = amount;
2084+
ScriptExecutionData.m_ccv_amount_init = true;
2085+
}
2086+
2087+
switch (flags) {
2088+
case 0:
2089+
// default behavior for outputs: add amount for the cumulative deferred check
2090+
ScriptExecutionData.AddDeferredAggregateOutputAmountCheck(index, ScriptExecutionData.m_ccv_amount);
2091+
break;
2092+
case CCV_FLAG_IGNORE_OUTPUT_AMOUNT:
2093+
// amount checking is disabled
2094+
break;
2095+
case CCV_FLAG_DEDUCT_OUTPUT_AMOUNT:
2096+
// subtract amount from input
2097+
if (txTo->vout[index].nValue > ScriptExecutionData.m_ccv_amount) {
2098+
return set_error(serror, SCRIPT_ERR_CHECKCONTRACTVERIFY_WRONG_AMOUNT);
2099+
}
2100+
ScriptExecutionData.m_ccv_amount -= txTo->vout[index].nValue;
2101+
2102+
// This output must not be used by another output check,
2103+
// unless it is with the CCV_FLAG_IGNORE_OUTPUT_AMOUNT flag
2104+
ScriptExecutionData.AddDeferredExclusiveOutputAmountCheck(index);
2105+
2106+
break;
2107+
default:
2108+
break;
2109+
}
2110+
2111+
return true;
2112+
}
2113+
19682114
// explicit instantiation
19692115
template class GenericTransactionSignatureChecker<CTransaction>;
19702116
template class GenericTransactionSignatureChecker<CMutableTransaction>;
@@ -2060,7 +2206,7 @@ uint256 ComputeTaprootMerkleRoot(Span<const unsigned char> control, const uint25
20602206
return k;
20612207
}
20622208

2063-
static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, const std::vector<unsigned char>& program, const uint256& tapleaf_hash, std::optional<XOnlyPubKey>& internal_key)
2209+
static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, const std::vector<unsigned char>& program, const uint256& tapleaf_hash, uint256& merkle_root, std::optional<XOnlyPubKey>& internal_key)
20642210
{
20652211
assert(control.size() >= TAPROOT_CONTROL_BASE_SIZE);
20662212
assert(program.size() >= uint256::size());
@@ -2070,7 +2216,7 @@ static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, c
20702216
//! The output pubkey (taken from the scriptPubKey).
20712217
const XOnlyPubKey q{program};
20722218
// Compute the Merkle root from the leaf and the provided path.
2073-
const uint256 merkle_root = ComputeTaprootMerkleRoot(control, tapleaf_hash);
2219+
merkle_root = ComputeTaprootMerkleRoot(control, tapleaf_hash);
20742220
// Verify that the output pubkey matches the tweaked internal pubkey, after correcting for parity.
20752221
return q.CheckTapTweak(p, merkle_root, control[0] & 1);
20762222
}
@@ -2133,10 +2279,12 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion,
21332279
return set_error(serror, SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE);
21342280
}
21352281
execdata.m_tapleaf_hash = ComputeTapleafHash(control[0] & TAPROOT_LEAF_MASK, script);
2136-
if (!VerifyTaprootCommitment(control, program, execdata.m_tapleaf_hash, execdata.m_internal_key)) {
2282+
if (!VerifyTaprootCommitment(control, program, execdata.m_tapleaf_hash, execdata.m_taproot_merkle_root, execdata.m_internal_key)) {
21372283
return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH);
21382284
}
21392285
execdata.m_tapleaf_hash_init = true;
2286+
execdata.m_taproot_merkle_root_init = true;
2287+
21402288
if ((control[0] & TAPROOT_LEAF_MASK) == TAPROOT_LEAF_TAPSCRIPT) {
21412289
// Tapscript (leaf version 0xc0)
21422290
exec_script = CScript(script.begin(), script.end());

src/script/interpreter.h

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,32 @@ enum class KeyVersion
240240
ANYPREVOUT = 1, //!< 1 or 33 byte public key, first byte is 0x01
241241
};
242242

243+
struct DeferredAggregateOutputAmountCheck
244+
{
245+
//! The index of the output that should be covering the value of a particular input.
246+
unsigned int vout_idx;
247+
248+
//! An expected constituent amount of the output - note that this isn't the *total*
249+
//! expected, but the value required for a specific input.
250+
CAmount amount;
251+
252+
DeferredAggregateOutputAmountCheck(unsigned int vout_idx, CAmount amount) noexcept
253+
: vout_idx(vout_idx), amount(amount) {}
254+
255+
};
256+
257+
struct DeferredExclusiveOutputAmountCheck
258+
{
259+
//! The index of an output whose amount was deducted from an input.
260+
//! No other call to OP_CHECKCONTRACTVERIFY must target the same output,
261+
//! except possibly with CCV_FLAG_IGNORE_OUTPUT_AMOUNT.
262+
unsigned int vout_idx;
263+
264+
DeferredExclusiveOutputAmountCheck(unsigned int vout_idx) noexcept
265+
: vout_idx(vout_idx) {}
266+
267+
};
268+
243269
//! Data that is accumulated during the script verification of a single input and then
244270
//! used to perform aggregate checks after all inputs have been run through
245271
//! `VerifyScript()`.
@@ -253,6 +279,10 @@ struct DeferredCheck
253279
//! script executions are performed in batch.
254280
const CTransaction* m_tx_to;
255281

282+
std::optional<DeferredAggregateOutputAmountCheck> m_aggr_output_amount_check{std::nullopt};
283+
284+
std::optional<DeferredExclusiveOutputAmountCheck> m_excl_output_amount_check{std::nullopt};
285+
256286
//! Future specific deferred check structs will be added here as pointers.
257287
//!
258288
//! e.g.
@@ -269,6 +299,11 @@ struct ScriptExecutionData
269299
//! The tapleaf hash.
270300
uint256 m_tapleaf_hash;
271301

302+
//! Whether m_taproot_merkle_root is initialized.
303+
bool m_taproot_merkle_root_init = false;
304+
//! The merkle root of the taproot tree.
305+
uint256 m_taproot_merkle_root;
306+
272307
//! Whether m_codeseparator_pos is initialized.
273308
bool m_codeseparator_pos_init = false;
274309
//! Opcode position of the last executed OP_CODESEPARATOR (or 0xFFFFFFFF if none executed).
@@ -300,9 +335,27 @@ struct ScriptExecutionData
300335
//! that has created this `ScriptExecutionData` instance.
301336
std::vector<DeferredCheck>* m_deferred_checks;
302337

303-
void AppendDeferredCheck(DeferredCheck dc)
338+
//! Whether m_ccv_amount is initialized.
339+
bool m_ccv_amount_init = false;
340+
//! Residual amount of the current input according to CHECKCONTRACTVERIFY semantics.
341+
CAmount m_ccv_amount;
342+
343+
DeferredCheck& NewDeferredCheck()
344+
{
345+
Assert(m_deferred_checks)->emplace_back();
346+
return m_deferred_checks->back();
347+
}
348+
349+
void AddDeferredAggregateOutputAmountCheck(unsigned int vout_idx, CAmount amount)
350+
{
351+
auto& dc = this->NewDeferredCheck();
352+
dc.m_aggr_output_amount_check = {vout_idx, amount};
353+
}
354+
355+
void AddDeferredExclusiveOutputAmountCheck(unsigned int vout_idx)
304356
{
305-
Assert(m_deferred_checks)->push_back(dc);
357+
auto& dc = this->NewDeferredCheck();
358+
dc.m_excl_output_amount_check = {vout_idx};
306359
}
307360
};
308361

@@ -355,6 +408,11 @@ class BaseSignatureChecker
355408
return false;
356409
}
357410

411+
virtual bool CheckContract(int flags, int index, const std::vector<unsigned char>& pubkey, const std::vector<unsigned char>& data, const std::vector<unsigned char>& taptree, ScriptExecutionData& ScriptExecutionData, ScriptError* serror) const
412+
{
413+
return false;
414+
}
415+
358416
virtual ~BaseSignatureChecker() = default;
359417
};
360418

@@ -392,6 +450,7 @@ class GenericTransactionSignatureChecker : public BaseSignatureChecker
392450
bool CheckLockTime(const CScriptNum& nLockTime) const override;
393451
bool CheckSequence(const CScriptNum& nSequence) const override;
394452
bool CheckDefaultCheckTemplateVerifyHash(const Span<const unsigned char>& hash) const override;
453+
bool CheckContract(int flags, int index, const std::vector<unsigned char>& pubkey, const std::vector<unsigned char>& data, const std::vector<unsigned char>& taptree, ScriptExecutionData& ScriptExecutionData, ScriptError* serror) const override;
395454
};
396455

397456
using TransactionSignatureChecker = GenericTransactionSignatureChecker<CTransaction>;

src/script/script.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ bool IsOpSuccess(const opcodetype& opcode)
368368
return opcode == 80 || opcode == 98 || (opcode >= 126 && opcode <= 129) ||
369369
(opcode >= 131 && opcode <= 134) || (opcode >= 137 && opcode <= 138) ||
370370
(opcode >= 141 && opcode <= 142) || (opcode >= 149 && opcode <= 153) ||
371-
(opcode >= 187 && opcode <= 254);
371+
(opcode >= 188 && opcode <= 254);
372372
}
373373

374374
bool CheckMinimalPush(const std::vector<unsigned char>& data, opcodetype opcode) {

src/script/script.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,9 @@ enum opcodetype
209209
// Opcode added by BIP 342 (Tapscript)
210210
OP_CHECKSIGADD = 0xba,
211211

212+
// Opcodes for contracts
213+
OP_CHECKCONTRACTVERIFY = 0xbb,
214+
212215
OP_INVALIDOPCODE = 0xff,
213216
};
214217

0 commit comments

Comments
 (0)