Skip to content

Commit a6ca508

Browse files
committed
Implement Taproot validation (BIP 341)
This includes key path spending and script path spending, but not the Tapscript execution implementation. Includes constants for various aspects of the consensus rules suggested by Jeremy Rubin.
1 parent 75d77b1 commit a6ca508

File tree

6 files changed

+93
-6
lines changed

6 files changed

+93
-6
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 negated) 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(), negated, &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
@@ -220,8 +220,10 @@ class XOnlyPubKey {
220220
* If the signature is not exactly 64 bytes, false is returned.
221221
*/
222222
bool VerifySchnorr(const uint256& msg, Span<const unsigned char> sigbytes) const;
223+
bool CheckPayToContract(const XOnlyPubKey& base, const uint256& hash, bool sign) const;
223224

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

src/script/interpreter.cpp

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1376,6 +1376,9 @@ template PrecomputedTransactionData::PrecomputedTransactionData(const CTransacti
13761376
template PrecomputedTransactionData::PrecomputedTransactionData(const CMutableTransaction& txTo);
13771377

13781378
static const CHashWriter HASHER_TAPSIGHASH = TaggedHash("TapSighash");
1379+
static const CHashWriter HASHER_TAPLEAF = TaggedHash("TapLeaf");
1380+
static const CHashWriter HASHER_TAPBRANCH = TaggedHash("TapBranch");
1381+
static const CHashWriter HASHER_TAPTWEAK = TaggedHash("TapTweak");
13791382

13801383
template<typename T>
13811384
bool SignatureHashSchnorr(uint256& hash_out, const T& tx_to, const uint32_t in_pos, const uint8_t hash_type, const SigVersion sigversion, const PrecomputedTransactionData* cache)
@@ -1671,14 +1674,34 @@ static bool ExecuteWitnessScript(const Span<const valtype>& stack_span, const CS
16711674
return true;
16721675
}
16731676

1674-
static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion, const std::vector<unsigned char>& program, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror)
1677+
static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, const std::vector<unsigned char>& program, const CScript& script)
1678+
{
1679+
int path_len = (control.size() - TAPROOT_CONTROL_BASE_SIZE) / TAPROOT_CONTROL_NODE_SIZE;
1680+
XOnlyPubKey p{uint256(std::vector<unsigned char>(control.begin() + 1, control.begin() + TAPROOT_CONTROL_BASE_SIZE))};
1681+
XOnlyPubKey q{uint256(program)};
1682+
uint256 k = (CHashWriter(HASHER_TAPLEAF) << uint8_t(control[0] & TAPROOT_LEAF_MASK) << script).GetSHA256();
1683+
for (int i = 0; i < path_len; ++i) {
1684+
CHashWriter ss_branch = HASHER_TAPBRANCH;
1685+
Span<const unsigned char> node(control.data() + TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * i, TAPROOT_CONTROL_NODE_SIZE);
1686+
if (std::lexicographical_compare(k.begin(), k.end(), node.begin(), node.end())) {
1687+
ss_branch << k << node;
1688+
} else {
1689+
ss_branch << node << k;
1690+
}
1691+
k = ss_branch.GetSHA256();
1692+
}
1693+
k = (CHashWriter(HASHER_TAPTWEAK) << MakeSpan(p) << k).GetSHA256();
1694+
return q.CheckPayToContract(p, k, control[0] & 1);
1695+
}
1696+
1697+
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)
16751698
{
16761699
CScript scriptPubKey;
16771700
Span<const valtype> stack{witness.stack};
16781701

16791702
if (witversion == 0) {
16801703
if (program.size() == WITNESS_V0_SCRIPTHASH_SIZE) {
1681-
// Version 0 segregated witness program: SHA256(CScript) inside the program, CScript + inputs in witness
1704+
// BIP141 P2WSH: 32-byte witness v0 program (which encodes SHA256(script))
16821705
if (stack.size() == 0) {
16831706
return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY);
16841707
}
@@ -1691,7 +1714,7 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion,
16911714
}
16921715
return ExecuteWitnessScript(stack, scriptPubKey, flags, SigVersion::WITNESS_V0, checker, serror);
16931716
} else if (program.size() == WITNESS_V0_KEYHASH_SIZE) {
1694-
// Special case for pay-to-pubkeyhash; signature + pubkey in witness
1717+
// BIP141 P2WPKH: 20-byte witness v0 program (which encodes Hash160(pubkey))
16951718
if (stack.size() != 2) {
16961719
return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH); // 2 items in witness
16971720
}
@@ -1700,11 +1723,41 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion,
17001723
} else {
17011724
return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH);
17021725
}
1726+
} else if (witversion == 1 && program.size() == WITNESS_V1_TAPROOT_SIZE && !is_p2sh) {
1727+
// BIP341 Taproot: 32-byte non-P2SH witness v1 program (which encodes a P2C-tweaked pubkey)
1728+
if (!(flags & SCRIPT_VERIFY_TAPROOT)) return set_success(serror);
1729+
if (stack.size() == 0) return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY);
1730+
if (stack.size() >= 2 && !stack.back().empty() && stack.back()[0] == ANNEX_TAG) {
1731+
// Drop annex
1732+
SpanPopBack(stack);
1733+
}
1734+
if (stack.size() == 1) {
1735+
// Key path spending (stack size is 1 after removing optional annex)
1736+
if (!checker.CheckSchnorrSignature(stack.front(), program, SigVersion::TAPROOT)) {
1737+
return set_error(serror, SCRIPT_ERR_TAPROOT_INVALID_SIG);
1738+
}
1739+
return set_success(serror);
1740+
} else {
1741+
// Script path spending (stack size is >1 after removing optional annex)
1742+
const valtype& control = SpanPopBack(stack);
1743+
const valtype& script_bytes = SpanPopBack(stack);
1744+
scriptPubKey = CScript(script_bytes.begin(), script_bytes.end());
1745+
if (control.size() < TAPROOT_CONTROL_BASE_SIZE || control.size() > TAPROOT_CONTROL_MAX_SIZE || ((control.size() - TAPROOT_CONTROL_BASE_SIZE) % TAPROOT_CONTROL_NODE_SIZE) != 0) {
1746+
return set_error(serror, SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE);
1747+
}
1748+
if (!VerifyTaprootCommitment(control, program, scriptPubKey)) {
1749+
return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH);
1750+
}
1751+
if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION) {
1752+
return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION);
1753+
}
1754+
return set_success(serror);
1755+
}
17031756
} else {
17041757
if (flags & SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM) {
17051758
return set_error(serror, SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM);
17061759
}
1707-
// Higher version witness scripts return true for future softfork compatibility
1760+
// Other version/size/p2sh combinations return true for future softfork compatibility
17081761
return true;
17091762
}
17101763
// 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.
@@ -1750,7 +1803,7 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C
17501803
// The scriptSig must be _exactly_ CScript(), otherwise we reintroduce malleability.
17511804
return set_error(serror, SCRIPT_ERR_WITNESS_MALLEATED);
17521805
}
1753-
if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror)) {
1806+
if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror, /* is_p2sh */ false)) {
17541807
return false;
17551808
}
17561809
// Bypass the cleanstack check at the end. The actual stack is obviously not clean
@@ -1795,7 +1848,7 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C
17951848
// reintroduce malleability.
17961849
return set_error(serror, SCRIPT_ERR_WITNESS_MALLEATED_P2SH);
17971850
}
1798-
if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror)) {
1851+
if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror, /* is_p2sh */ true)) {
17991852
return false;
18001853
}
18011854
// 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: 6 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:
@@ -91,6 +93,10 @@ std::string ScriptErrorString(const ScriptError serror)
9193
return "Witness provided for non-witness script";
9294
case SCRIPT_ERR_WITNESS_PUBKEYTYPE:
9395
return "Using non-compressed keys in segwit";
96+
case SCRIPT_ERR_TAPROOT_INVALID_SIG:
97+
return "Invalid signature for Taproot key path spending";
98+
case SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE:
99+
return "Invalid Taproot control block size";
94100
case SCRIPT_ERR_OP_CODESEPARATOR:
95101
return "Using OP_CODESEPARATOR in non-witness script";
96102
case SCRIPT_ERR_SIG_FINDANDDELETE:

src/script/script_error.h

Lines changed: 5 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,
@@ -66,6 +67,10 @@ typedef enum ScriptError_t
6667
SCRIPT_ERR_WITNESS_UNEXPECTED,
6768
SCRIPT_ERR_WITNESS_PUBKEYTYPE,
6869

70+
/* Taproot */
71+
SCRIPT_ERR_TAPROOT_INVALID_SIG,
72+
SCRIPT_ERR_TAPROOT_WRONG_CONTROL_SIZE,
73+
6974
/* Constant scriptCode */
7075
SCRIPT_ERR_OP_CODESEPARATOR,
7176
SCRIPT_ERR_SIG_FINDANDDELETE,

0 commit comments

Comments
 (0)