aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/pubkey.cpp7
-rw-r--r--src/pubkey.h2
-rw-r--r--src/script/interpreter.cpp68
-rw-r--r--src/script/interpreter.h14
-rw-r--r--src/script/script_error.cpp4
-rw-r--r--src/script/script_error.h2
6 files changed, 90 insertions, 7 deletions
diff --git a/src/pubkey.cpp b/src/pubkey.cpp
index 69e3d91392..4d734fc891 100644
--- a/src/pubkey.cpp
+++ b/src/pubkey.cpp
@@ -181,6 +181,13 @@ bool XOnlyPubKey::VerifySchnorr(const uint256& msg, Span<const unsigned char> si
return secp256k1_schnorrsig_verify(secp256k1_context_verify, sigbytes.data(), msg.begin(), &pubkey);
}
+bool XOnlyPubKey::CheckPayToContract(const XOnlyPubKey& base, const uint256& hash, bool parity) const
+{
+ secp256k1_xonly_pubkey base_point;
+ if (!secp256k1_xonly_pubkey_parse(secp256k1_context_verify, &base_point, base.data())) return false;
+ return secp256k1_xonly_pubkey_tweak_add_check(secp256k1_context_verify, m_keydata.begin(), parity, &base_point, hash.begin());
+}
+
bool CPubKey::Verify(const uint256 &hash, const std::vector<unsigned char>& vchSig) const {
if (!IsValid())
return false;
diff --git a/src/pubkey.h b/src/pubkey.h
index 1a818037d1..0f784b86e4 100644
--- a/src/pubkey.h
+++ b/src/pubkey.h
@@ -221,8 +221,10 @@ public:
* sigbytes must be exactly 64 bytes.
*/
bool VerifySchnorr(const uint256& msg, Span<const unsigned char> sigbytes) const;
+ bool CheckPayToContract(const XOnlyPubKey& base, const uint256& hash, bool parity) const;
const unsigned char& operator[](int pos) const { return *(m_keydata.begin() + pos); }
+ const unsigned char* data() const { return m_keydata.begin(); }
size_t size() const { return m_keydata.size(); }
};
diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp
index aef6e28118..07cc525f50 100644
--- a/src/script/interpreter.cpp
+++ b/src/script/interpreter.cpp
@@ -1379,6 +1379,9 @@ template PrecomputedTransactionData::PrecomputedTransactionData(const CTransacti
template PrecomputedTransactionData::PrecomputedTransactionData(const CMutableTransaction& txTo);
static const CHashWriter HASHER_TAPSIGHASH = TaggedHash("TapSighash");
+static const CHashWriter HASHER_TAPLEAF = TaggedHash("TapLeaf");
+static const CHashWriter HASHER_TAPBRANCH = TaggedHash("TapBranch");
+static const CHashWriter HASHER_TAPTWEAK = TaggedHash("TapTweak");
template<typename T>
bool SignatureHashSchnorr(uint256& hash_out, const T& tx_to, uint32_t in_pos, uint8_t hash_type, SigVersion sigversion, const PrecomputedTransactionData& cache)
@@ -1679,14 +1682,35 @@ static bool ExecuteWitnessScript(const Span<const valtype>& stack_span, const CS
return true;
}
-static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, const std::vector<unsigned char>& program, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror)
+static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, const std::vector<unsigned char>& program, const CScript& script)
{
- CScript exec_script; //!< Actually executed script (last stack item in P2WSH; implied P2PKH script in P2WPKH)
+ const int path_len = (control.size() - TAPROOT_CONTROL_BASE_SIZE) / TAPROOT_CONTROL_NODE_SIZE;
+ const XOnlyPubKey p{uint256(std::vector<unsigned char>(control.begin() + 1, control.begin() + TAPROOT_CONTROL_BASE_SIZE))};
+ const XOnlyPubKey q{uint256(program)};
+ uint256 tapleaf_hash = (CHashWriter(HASHER_TAPLEAF) << uint8_t(control[0] & TAPROOT_LEAF_MASK) << script).GetSHA256();
+ uint256 k = tapleaf_hash;
+ for (int i = 0; i < path_len; ++i) {
+ CHashWriter ss_branch{HASHER_TAPBRANCH};
+ Span<const unsigned char> node(control.data() + TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * i, TAPROOT_CONTROL_NODE_SIZE);
+ if (std::lexicographical_compare(k.begin(), k.end(), node.begin(), node.end())) {
+ ss_branch << k << node;
+ } else {
+ ss_branch << node << k;
+ }
+ k = ss_branch.GetSHA256();
+ }
+ k = (CHashWriter(HASHER_TAPTWEAK) << MakeSpan(p) << k).GetSHA256();
+ return q.CheckPayToContract(p, k, control[0] & 1);
+}
+
+static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, const std::vector<unsigned char>& program, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror, bool is_p2sh)
+{
+ CScript exec_script; //!< Actually executed script (last stack item in P2WSH; implied P2PKH script in P2WPKH; leaf script in P2TR)
Span<const valtype> stack{witness.stack};
if (witversion == 0) {
if (program.size() == WITNESS_V0_SCRIPTHASH_SIZE) {
- // Version 0 segregated witness program: SHA256(CScript) inside the program, CScript + inputs in witness
+ // BIP141 P2WSH: 32-byte witness v0 program (which encodes SHA256(script))
if (stack.size() == 0) {
return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY);
}
@@ -1699,7 +1723,7 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion,
}
return ExecuteWitnessScript(stack, exec_script, flags, SigVersion::WITNESS_V0, checker, serror);
} else if (program.size() == WITNESS_V0_KEYHASH_SIZE) {
- // Special case for pay-to-pubkeyhash; signature + pubkey in witness
+ // BIP141 P2WPKH: 20-byte witness v0 program (which encodes Hash160(pubkey))
if (stack.size() != 2) {
return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH); // 2 items in witness
}
@@ -1708,11 +1732,41 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion,
} else {
return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH);
}
+ } else if (witversion == 1 && program.size() == WITNESS_V1_TAPROOT_SIZE && !is_p2sh) {
+ // BIP341 Taproot: 32-byte non-P2SH witness v1 program (which encodes a P2C-tweaked pubkey)
+ if (!(flags & SCRIPT_VERIFY_TAPROOT)) return set_success(serror);
+ if (stack.size() == 0) return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY);
+ if (stack.size() >= 2 && !stack.back().empty() && stack.back()[0] == ANNEX_TAG) {
+ // Drop annex (this is non-standard; see IsWitnessStandard)
+ SpanPopBack(stack);
+ }
+ if (stack.size() == 1) {
+ // Key path spending (stack size is 1 after removing optional annex)
+ if (!checker.CheckSchnorrSignature(stack.front(), program, SigVersion::TAPROOT, serror)) {
+ return false; // serror is set
+ }
+ return set_success(serror);
+ } else {
+ // Script path spending (stack size is >1 after removing optional annex)
+ const valtype& control = SpanPopBack(stack);
+ const valtype& script_bytes = SpanPopBack(stack);
+ exec_script = CScript(script_bytes.begin(), script_bytes.end());
+ if (control.size() < TAPROOT_CONTROL_BASE_SIZE || control.size() > TAPROOT_CONTROL_MAX_SIZE || ((control.size() - TAPROOT_CONTROL_BASE_SIZE) % TAPROOT_CONTROL_NODE_SIZE) != 0) {
+ return set_error(serror, SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE);
+ }
+ if (!VerifyTaprootCommitment(control, program, exec_script)) {
+ return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH);
+ }
+ if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION) {
+ return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION);
+ }
+ return set_success(serror);
+ }
} else {
if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM) {
return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM);
}
- // Higher version witness scripts return true for future softfork compatibility
+ // Other version/size/p2sh combinations return true for future softfork compatibility
return true;
}
// There is intentionally no return statement here, to be able to use "control reaches end of non-void function" warnings to detect gaps in the logic above.
@@ -1758,7 +1812,7 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C
// The scriptSig must be _exactly_ CScript(), otherwise we reintroduce malleability.
return set_error(serror, SCRIPT_ERR_WITNESS_MALLEATED);
}
- if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror)) {
+ if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror, /* is_p2sh */ false)) {
return false;
}
// Bypass the cleanstack check at the end. The actual stack is obviously not clean
@@ -1803,7 +1857,7 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C
// reintroduce malleability.
return set_error(serror, SCRIPT_ERR_WITNESS_MALLEATED_P2SH);
}
- if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror)) {
+ if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror, /* is_p2sh */ true)) {
return false;
}
// Bypass the cleanstack check at the end. The actual stack is obviously not clean
diff --git a/src/script/interpreter.h b/src/script/interpreter.h
index e54243c8f0..2ffe18dd92 100644
--- a/src/script/interpreter.h
+++ b/src/script/interpreter.h
@@ -121,6 +121,14 @@ enum
// Making OP_CODESEPARATOR and FindAndDelete fail any non-segwit scripts
//
SCRIPT_VERIFY_CONST_SCRIPTCODE = (1U << 16),
+
+ // Taproot validation (BIP 341)
+ //
+ SCRIPT_VERIFY_TAPROOT = (1U << 17),
+
+ // Making unknown Taproot leaf versions non-standard
+ //
+ SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION = (1U << 18),
};
bool CheckSignatureEncoding(const std::vector<unsigned char> &vchSig, unsigned int flags, ScriptError* serror);
@@ -167,6 +175,12 @@ static constexpr size_t WITNESS_V0_SCRIPTHASH_SIZE = 32;
static constexpr size_t WITNESS_V0_KEYHASH_SIZE = 20;
static constexpr size_t WITNESS_V1_TAPROOT_SIZE = 32;
+static constexpr uint8_t TAPROOT_LEAF_MASK = 0xfe;
+static constexpr size_t TAPROOT_CONTROL_BASE_SIZE = 33;
+static constexpr size_t TAPROOT_CONTROL_NODE_SIZE = 32;
+static constexpr size_t TAPROOT_CONTROL_MAX_NODE_COUNT = 128;
+static constexpr size_t TAPROOT_CONTROL_MAX_SIZE = TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * TAPROOT_CONTROL_MAX_NODE_COUNT;
+
template <class T>
uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int nHashType, const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* cache = nullptr);
diff --git a/src/script/script_error.cpp b/src/script/script_error.cpp
index 9c49ced3ed..77add5194c 100644
--- a/src/script/script_error.cpp
+++ b/src/script/script_error.cpp
@@ -73,6 +73,8 @@ std::string ScriptErrorString(const ScriptError serror)
return "NOPx reserved for soft-fork upgrades";
case SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM:
return "Witness version reserved for soft-fork upgrades";
+ case SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION:
+ return "Taproot version reserved for soft-fork upgrades";
case SCRIPT_ERR_PUBKEYTYPE:
return "Public key is neither compressed or uncompressed";
case SCRIPT_ERR_CLEANSTACK:
@@ -97,6 +99,8 @@ std::string ScriptErrorString(const ScriptError serror)
return "Invalid Schnorr signature hash type";
case SCRIPT_ERR_SCHNORR_SIG:
return "Invalid Schnorr signature";
+ case SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE:
+ return "Invalid Taproot control block size";
case SCRIPT_ERR_OP_CODESEPARATOR:
return "Using OP_CODESEPARATOR in non-witness script";
case SCRIPT_ERR_SIG_FINDANDDELETE:
diff --git a/src/script/script_error.h b/src/script/script_error.h
index c79a11a94b..59d3e565a5 100644
--- a/src/script/script_error.h
+++ b/src/script/script_error.h
@@ -56,6 +56,7 @@ typedef enum ScriptError_t
/* softfork safeness */
SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS,
SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM,
+ SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION,
/* segregated witness */
SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH,
@@ -70,6 +71,7 @@ typedef enum ScriptError_t
SCRIPT_ERR_SCHNORR_SIG_SIZE,
SCRIPT_ERR_SCHNORR_SIG_HASHTYPE,
SCRIPT_ERR_SCHNORR_SIG,
+ SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE,
/* Constant scriptCode */
SCRIPT_ERR_OP_CODESEPARATOR,