Skip to content

Commit 8bbed4b

Browse files
committed
Implement Taproot validation (BIP 341)
This includes key path spending and script path spending, but not the Tapscript execution implementation (leaf 0xc0 remains unemcumbered in this commit). Includes constants for various aspects of the consensus rules suggested by Jeremy Rubin.
1 parent 0664f5f commit 8bbed4b

File tree

6 files changed

+90
-7
lines changed

6 files changed

+90
-7
lines changed

src/pubkey.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,13 @@ bool XOnlyPubKey::VerifySchnorr(const uint256& msg, Span<const unsigned char> si
181181
return secp256k1_schnorrsig_verify(secp256k1_context_verify, sigbytes.data(), msg.begin(), &pubkey);
182182
}
183183

184+
bool XOnlyPubKey::CheckPayToContract(const XOnlyPubKey& base, const uint256& hash, bool parity) const
185+
{
186+
secp256k1_xonly_pubkey base_point;
187+
if (!secp256k1_xonly_pubkey_parse(secp256k1_context_verify, &base_point, base.data())) return false;
188+
return secp256k1_xonly_pubkey_tweak_add_check(secp256k1_context_verify, m_keydata.begin(), parity, &base_point, hash.begin());
189+
}
190+
184191
bool CPubKey::Verify(const uint256 &hash, const std::vector<unsigned char>& vchSig) const {
185192
if (!IsValid())
186193
return false;

src/pubkey.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,10 @@ class XOnlyPubKey
221221
* sigbytes must be exactly 64 bytes.
222222
*/
223223
bool VerifySchnorr(const uint256& msg, Span<const unsigned char> sigbytes) const;
224+
bool CheckPayToContract(const XOnlyPubKey& base, const uint256& hash, bool parity) const;
224225

225226
const unsigned char& operator[](int pos) const { return *(m_keydata.begin() + pos); }
227+
const unsigned char* data() const { return m_keydata.begin(); }
226228
size_t size() const { return m_keydata.size(); }
227229
};
228230

src/script/interpreter.cpp

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1379,6 +1379,9 @@ template PrecomputedTransactionData::PrecomputedTransactionData(const CTransacti
13791379
template PrecomputedTransactionData::PrecomputedTransactionData(const CMutableTransaction& txTo);
13801380

13811381
static const CHashWriter HASHER_TAPSIGHASH = TaggedHash("TapSighash");
1382+
static const CHashWriter HASHER_TAPLEAF = TaggedHash("TapLeaf");
1383+
static const CHashWriter HASHER_TAPBRANCH = TaggedHash("TapBranch");
1384+
static const CHashWriter HASHER_TAPTWEAK = TaggedHash("TapTweak");
13821385

13831386
template<typename T>
13841387
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
16791682
return true;
16801683
}
16811684

1682-
static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, const std::vector<unsigned char>& program, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror)
1685+
static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, const std::vector<unsigned char>& program, const CScript& script)
16831686
{
1684-
CScript exec_script; //!< Actually executed script (last stack item in P2WSH; implied P2PKH script in P2WPKH)
1687+
const int path_len = (control.size() - TAPROOT_CONTROL_BASE_SIZE) / TAPROOT_CONTROL_NODE_SIZE;
1688+
const XOnlyPubKey p{uint256(std::vector<unsigned char>(control.begin() + 1, control.begin() + TAPROOT_CONTROL_BASE_SIZE))};
1689+
const XOnlyPubKey q{uint256(program)};
1690+
uint256 tapleaf_hash = (CHashWriter(HASHER_TAPLEAF) << uint8_t(control[0] & TAPROOT_LEAF_MASK) << script).GetSHA256();
1691+
uint256 k = tapleaf_hash;
1692+
for (int i = 0; i < path_len; ++i) {
1693+
CHashWriter ss_branch{HASHER_TAPBRANCH};
1694+
Span<const unsigned char> node(control.data() + TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * i, TAPROOT_CONTROL_NODE_SIZE);
1695+
if (std::lexicographical_compare(k.begin(), k.end(), node.begin(), node.end())) {
1696+
ss_branch << k << node;
1697+
} else {
1698+
ss_branch << node << k;
1699+
}
1700+
k = ss_branch.GetSHA256();
1701+
}
1702+
k = (CHashWriter(HASHER_TAPTWEAK) << MakeSpan(p) << k).GetSHA256();
1703+
return q.CheckPayToContract(p, k, control[0] & 1);
1704+
}
1705+
1706+
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)
1707+
{
1708+
CScript exec_script; //!< Actually executed script (last stack item in P2WSH; implied P2PKH script in P2WPKH; leaf script in P2TR)
16851709
Span<const valtype> stack{witness.stack};
16861710

16871711
if (witversion == 0) {
16881712
if (program.size() == WITNESS_V0_SCRIPTHASH_SIZE) {
1689-
// Version 0 segregated witness program: SHA256(CScript) inside the program, CScript + inputs in witness
1713+
// BIP141 P2WSH: 32-byte witness v0 program (which encodes SHA256(script))
16901714
if (stack.size() == 0) {
16911715
return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY);
16921716
}
@@ -1699,7 +1723,7 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion,
16991723
}
17001724
return ExecuteWitnessScript(stack, exec_script, flags, SigVersion::WITNESS_V0, checker, serror);
17011725
} else if (program.size() == WITNESS_V0_KEYHASH_SIZE) {
1702-
// Special case for pay-to-pubkeyhash; signature + pubkey in witness
1726+
// BIP141 P2WPKH: 20-byte witness v0 program (which encodes Hash160(pubkey))
17031727
if (stack.size() != 2) {
17041728
return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH); // 2 items in witness
17051729
}
@@ -1708,11 +1732,41 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion,
17081732
} else {
17091733
return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH);
17101734
}
1735+
} else if (witversion == 1 && program.size() == WITNESS_V1_TAPROOT_SIZE && !is_p2sh) {
1736+
// BIP341 Taproot: 32-byte non-P2SH witness v1 program (which encodes a P2C-tweaked pubkey)
1737+
if (!(flags & SCRIPT_VERIFY_TAPROOT)) return set_success(serror);
1738+
if (stack.size() == 0) return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY);
1739+
if (stack.size() >= 2 && !stack.back().empty() && stack.back()[0] == ANNEX_TAG) {
1740+
// Drop annex (this is non-standard; see IsWitnessStandard)
1741+
SpanPopBack(stack);
1742+
}
1743+
if (stack.size() == 1) {
1744+
// Key path spending (stack size is 1 after removing optional annex)
1745+
if (!checker.CheckSchnorrSignature(stack.front(), program, SigVersion::TAPROOT, serror)) {
1746+
return false; // serror is set
1747+
}
1748+
return set_success(serror);
1749+
} else {
1750+
// Script path spending (stack size is >1 after removing optional annex)
1751+
const valtype& control = SpanPopBack(stack);
1752+
const valtype& script_bytes = SpanPopBack(stack);
1753+
exec_script = CScript(script_bytes.begin(), script_bytes.end());
1754+
if (control.size() < TAPROOT_CONTROL_BASE_SIZE || control.size() > TAPROOT_CONTROL_MAX_SIZE || ((control.size() - TAPROOT_CONTROL_BASE_SIZE) % TAPROOT_CONTROL_NODE_SIZE) != 0) {
1755+
return set_error(serror, SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE);
1756+
}
1757+
if (!VerifyTaprootCommitment(control, program, exec_script)) {
1758+
return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH);
1759+
}
1760+
if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION) {
1761+
return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION);
1762+
}
1763+
return set_success(serror);
1764+
}
17111765
} else {
17121766
if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM) {
17131767
return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM);
17141768
}
1715-
// Higher version witness scripts return true for future softfork compatibility
1769+
// Other version/size/p2sh combinations return true for future softfork compatibility
17161770
return true;
17171771
}
17181772
// 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
17581812
// The scriptSig must be _exactly_ CScript(), otherwise we reintroduce malleability.
17591813
return set_error(serror, SCRIPT_ERR_WITNESS_MALLEATED);
17601814
}
1761-
if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror)) {
1815+
if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror, /* is_p2sh */ false)) {
17621816
return false;
17631817
}
17641818
// 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
18031857
// reintroduce malleability.
18041858
return set_error(serror, SCRIPT_ERR_WITNESS_MALLEATED_P2SH);
18051859
}
1806-
if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror)) {
1860+
if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror, /* is_p2sh */ true)) {
18071861
return false;
18081862
}
18091863
// Bypass the cleanstack check at the end. The actual stack is obviously not clean

src/script/interpreter.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,14 @@ enum
121121
// Making OP_CODESEPARATOR and FindAndDelete fail any non-segwit scripts
122122
//
123123
SCRIPT_VERIFY_CONST_SCRIPTCODE = (1U << 16),
124+
125+
// Taproot validation (BIP 341)
126+
//
127+
SCRIPT_VERIFY_TAPROOT = (1U << 17),
128+
129+
// Making unknown Taproot leaf versions non-standard
130+
//
131+
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION = (1U << 18),
124132
};
125133

126134
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;
167175
static constexpr size_t WITNESS_V0_KEYHASH_SIZE = 20;
168176
static constexpr size_t WITNESS_V1_TAPROOT_SIZE = 32;
169177

178+
static constexpr uint8_t TAPROOT_LEAF_MASK = 0xfe;
179+
static constexpr size_t TAPROOT_CONTROL_BASE_SIZE = 33;
180+
static constexpr size_t TAPROOT_CONTROL_NODE_SIZE = 32;
181+
static constexpr size_t TAPROOT_CONTROL_MAX_NODE_COUNT = 128;
182+
static constexpr size_t TAPROOT_CONTROL_MAX_SIZE = TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * TAPROOT_CONTROL_MAX_NODE_COUNT;
183+
170184
template <class T>
171185
uint256 SignatureHash(const CScript& scriptCode, const T& txTo, unsigned int nIn, int nHashType, const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* cache = nullptr);
172186

src/script/script_error.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ std::string ScriptErrorString(const ScriptError serror)
7373
return "NOPx reserved for soft-fork upgrades";
7474
case SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM:
7575
return "Witness version reserved for soft-fork upgrades";
76+
case SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION:
77+
return "Taproot version reserved for soft-fork upgrades";
7678
case SCRIPT_ERR_PUBKEYTYPE:
7779
return "Public key is neither compressed or uncompressed";
7880
case SCRIPT_ERR_CLEANSTACK:
@@ -97,6 +99,8 @@ std::string ScriptErrorString(const ScriptError serror)
9799
return "Invalid Schnorr signature hash type";
98100
case SCRIPT_ERR_SCHNORR_SIG:
99101
return "Invalid Schnorr signature";
102+
case SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE:
103+
return "Invalid Taproot control block size";
100104
case SCRIPT_ERR_OP_CODESEPARATOR:
101105
return "Using OP_CODESEPARATOR in non-witness script";
102106
case SCRIPT_ERR_SIG_FINDANDDELETE:

src/script/script_error.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ typedef enum ScriptError_t
5656
/* softfork safeness */
5757
SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS,
5858
SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM,
59+
SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION,
5960

6061
/* segregated witness */
6162
SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH,
@@ -70,6 +71,7 @@ typedef enum ScriptError_t
7071
SCRIPT_ERR_SCHNORR_SIG_SIZE,
7172
SCRIPT_ERR_SCHNORR_SIG_HASHTYPE,
7273
SCRIPT_ERR_SCHNORR_SIG,
74+
SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE,
7375

7476
/* Constant scriptCode */
7577
SCRIPT_ERR_OP_CODESEPARATOR,

0 commit comments

Comments
 (0)