Make Taproot spends standard + policy limits

This adds a `TxoutType::WITNESS_V1_TAPROOT` for P2TR outputs, and permits spending
them in standardness rules. No corresponding `CTxDestination` is added for it,
as that isn't needed until we want wallet integration. The taproot validation flags
are also enabled for mempool transactions, and standardness rules are added
(stack item size limit, no annexes).
This commit is contained in:
Pieter Wuille 2020-09-11 14:34:10 -07:00
parent 865d2c37e2
commit e9a021d7e6
8 changed files with 61 additions and 7 deletions

View File

@ -9,7 +9,7 @@
#include <consensus/validation.h> #include <consensus/validation.h>
#include <coins.h> #include <coins.h>
#include <span.h>
CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFeeIn) CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFeeIn)
{ {
@ -206,6 +206,7 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
// get the scriptPubKey corresponding to this input: // get the scriptPubKey corresponding to this input:
CScript prevScript = prev.scriptPubKey; CScript prevScript = prev.scriptPubKey;
bool p2sh = false;
if (prevScript.IsPayToScriptHash()) { if (prevScript.IsPayToScriptHash()) {
std::vector <std::vector<unsigned char> > stack; std::vector <std::vector<unsigned char> > stack;
// If the scriptPubKey is P2SH, we try to extract the redeemScript casually by converting the scriptSig // If the scriptPubKey is P2SH, we try to extract the redeemScript casually by converting the scriptSig
@ -216,6 +217,7 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
if (stack.empty()) if (stack.empty())
return false; return false;
prevScript = CScript(stack.back().begin(), stack.back().end()); prevScript = CScript(stack.back().begin(), stack.back().end());
p2sh = true;
} }
int witnessversion = 0; int witnessversion = 0;
@ -237,6 +239,36 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
return false; return false;
} }
} }
// Check policy limits for Taproot spends:
// - MAX_STANDARD_TAPSCRIPT_STACK_ITEM_SIZE limit for stack item size
// - No annexes
if (witnessversion == 1 && witnessprogram.size() == WITNESS_V1_TAPROOT_SIZE && !p2sh) {
// Taproot spend (non-P2SH-wrapped, version 1, witness program size 32; see BIP 341)
auto stack = MakeSpan(tx.vin[i].scriptWitness.stack);
if (stack.size() >= 2 && !stack.back().empty() && stack.back()[0] == ANNEX_TAG) {
// Annexes are nonstandard as long as no semantics are defined for them.
return false;
}
if (stack.size() >= 2) {
// Script path spend (2 or more stack elements after removing optional annex)
const auto& control_block = SpanPopBack(stack);
SpanPopBack(stack); // Ignore script
if (control_block.empty()) return false; // Empty control block is invalid
if ((control_block[0] & TAPROOT_LEAF_MASK) == TAPROOT_LEAF_TAPSCRIPT) {
// Leaf version 0xc0 (aka Tapscript, see BIP 342)
for (const auto& item : stack) {
if (item.size() > MAX_STANDARD_TAPSCRIPT_STACK_ITEM_SIZE) return false;
}
}
} else if (stack.size() == 1) {
// Key path spend (1 stack element after removing optional annex)
// (no policy rules apply)
} else {
// 0 stack elements; this is already invalid by consensus rules
return false;
}
}
} }
return true; return true;
} }

View File

@ -40,6 +40,8 @@ static const bool DEFAULT_PERMIT_BAREMULTISIG = true;
static const unsigned int MAX_STANDARD_P2WSH_STACK_ITEMS = 100; static const unsigned int MAX_STANDARD_P2WSH_STACK_ITEMS = 100;
/** The maximum size of each witness stack item in a standard P2WSH script */ /** The maximum size of each witness stack item in a standard P2WSH script */
static const unsigned int MAX_STANDARD_P2WSH_STACK_ITEM_SIZE = 80; static const unsigned int MAX_STANDARD_P2WSH_STACK_ITEM_SIZE = 80;
/** The maximum size of each witness stack item in a standard BIP 342 script (Taproot, leaf version 0xc0) */
static const unsigned int MAX_STANDARD_TAPSCRIPT_STACK_ITEM_SIZE = 80;
/** The maximum size of a standard witnessScript */ /** The maximum size of a standard witnessScript */
static const unsigned int MAX_STANDARD_P2WSH_SCRIPT_SIZE = 3600; static const unsigned int MAX_STANDARD_P2WSH_SCRIPT_SIZE = 3600;
/** Min feerate for defining dust. Historically this has been based on the /** Min feerate for defining dust. Historically this has been based on the
@ -68,7 +70,11 @@ static constexpr unsigned int STANDARD_SCRIPT_VERIFY_FLAGS = MANDATORY_SCRIPT_VE
SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_WITNESS |
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM | SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM |
SCRIPT_VERIFY_WITNESS_PUBKEYTYPE | SCRIPT_VERIFY_WITNESS_PUBKEYTYPE |
SCRIPT_VERIFY_CONST_SCRIPTCODE; SCRIPT_VERIFY_CONST_SCRIPTCODE |
SCRIPT_VERIFY_TAPROOT |
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION |
SCRIPT_VERIFY_DISCOURAGE_OP_SUCCESS |
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_PUBKEYTYPE;
/** For convenience, standard but not mandatory verify flags. */ /** For convenience, standard but not mandatory verify flags. */
static constexpr unsigned int STANDARD_NOT_MANDATORY_VERIFY_FLAGS = STANDARD_SCRIPT_VERIFY_FLAGS & ~MANDATORY_SCRIPT_VERIFY_FLAGS; static constexpr unsigned int STANDARD_NOT_MANDATORY_VERIFY_FLAGS = STANDARD_SCRIPT_VERIFY_FLAGS & ~MANDATORY_SCRIPT_VERIFY_FLAGS;

View File

@ -111,6 +111,7 @@ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator
case TxoutType::NONSTANDARD: case TxoutType::NONSTANDARD:
case TxoutType::NULL_DATA: case TxoutType::NULL_DATA:
case TxoutType::WITNESS_UNKNOWN: case TxoutType::WITNESS_UNKNOWN:
case TxoutType::WITNESS_V1_TAPROOT:
return false; return false;
case TxoutType::PUBKEY: case TxoutType::PUBKEY:
if (!CreateSig(creator, sigdata, provider, sig, CPubKey(vSolutions[0]), scriptPubKey, sigversion)) return false; if (!CreateSig(creator, sigdata, provider, sig, CPubKey(vSolutions[0]), scriptPubKey, sigversion)) return false;

View File

@ -55,6 +55,7 @@ std::string GetTxnOutputType(TxoutType t)
case TxoutType::NULL_DATA: return "nulldata"; case TxoutType::NULL_DATA: return "nulldata";
case TxoutType::WITNESS_V0_KEYHASH: return "witness_v0_keyhash"; case TxoutType::WITNESS_V0_KEYHASH: return "witness_v0_keyhash";
case TxoutType::WITNESS_V0_SCRIPTHASH: return "witness_v0_scripthash"; case TxoutType::WITNESS_V0_SCRIPTHASH: return "witness_v0_scripthash";
case TxoutType::WITNESS_V1_TAPROOT: return "witness_v1_taproot";
case TxoutType::WITNESS_UNKNOWN: return "witness_unknown"; case TxoutType::WITNESS_UNKNOWN: return "witness_unknown";
} // no default case, so the compiler can warn about missing cases } // no default case, so the compiler can warn about missing cases
assert(false); assert(false);
@ -130,6 +131,11 @@ TxoutType Solver(const CScript& scriptPubKey, std::vector<std::vector<unsigned c
vSolutionsRet.push_back(witnessprogram); vSolutionsRet.push_back(witnessprogram);
return TxoutType::WITNESS_V0_SCRIPTHASH; return TxoutType::WITNESS_V0_SCRIPTHASH;
} }
if (witnessversion == 1 && witnessprogram.size() == WITNESS_V1_TAPROOT_SIZE) {
vSolutionsRet.push_back(std::vector<unsigned char>{(unsigned char)witnessversion});
vSolutionsRet.push_back(std::move(witnessprogram));
return TxoutType::WITNESS_V1_TAPROOT;
}
if (witnessversion != 0) { if (witnessversion != 0) {
vSolutionsRet.push_back(std::vector<unsigned char>{(unsigned char)witnessversion}); vSolutionsRet.push_back(std::vector<unsigned char>{(unsigned char)witnessversion});
vSolutionsRet.push_back(std::move(witnessprogram)); vSolutionsRet.push_back(std::move(witnessprogram));
@ -203,7 +209,7 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet)
std::copy(vSolutions[0].begin(), vSolutions[0].end(), hash.begin()); std::copy(vSolutions[0].begin(), vSolutions[0].end(), hash.begin());
addressRet = hash; addressRet = hash;
return true; return true;
} else if (whichType == TxoutType::WITNESS_UNKNOWN) { } else if (whichType == TxoutType::WITNESS_UNKNOWN || whichType == TxoutType::WITNESS_V1_TAPROOT) {
WitnessUnknown unk; WitnessUnknown unk;
unk.version = vSolutions[0][0]; unk.version = vSolutions[0][0];
std::copy(vSolutions[1].begin(), vSolutions[1].end(), unk.program); std::copy(vSolutions[1].begin(), vSolutions[1].end(), unk.program);

View File

@ -129,6 +129,7 @@ enum class TxoutType {
NULL_DATA, //!< unspendable OP_RETURN script that carries data NULL_DATA, //!< unspendable OP_RETURN script that carries data
WITNESS_V0_SCRIPTHASH, WITNESS_V0_SCRIPTHASH,
WITNESS_V0_KEYHASH, WITNESS_V0_KEYHASH,
WITNESS_V1_TAPROOT,
WITNESS_UNKNOWN, //!< Only for Witness versions not already defined above WITNESS_UNKNOWN, //!< Only for Witness versions not already defined above
}; };
@ -206,7 +207,8 @@ struct WitnessUnknown
* * ScriptHash: TxoutType::SCRIPTHASH destination (P2SH) * * ScriptHash: TxoutType::SCRIPTHASH destination (P2SH)
* * WitnessV0ScriptHash: TxoutType::WITNESS_V0_SCRIPTHASH destination (P2WSH) * * WitnessV0ScriptHash: TxoutType::WITNESS_V0_SCRIPTHASH destination (P2WSH)
* * WitnessV0KeyHash: TxoutType::WITNESS_V0_KEYHASH destination (P2WPKH) * * WitnessV0KeyHash: TxoutType::WITNESS_V0_KEYHASH destination (P2WPKH)
* * WitnessUnknown: TxoutType::WITNESS_UNKNOWN destination (P2W???) * * WitnessUnknown: TxoutType::WITNESS_UNKNOWN/WITNESS_V1_TAPROOT destination (P2W???)
* (taproot outputs do not require their own type as long as no wallet support exists)
* A CTxDestination is the internal data type encoded in a bitcoin address * A CTxDestination is the internal data type encoded in a bitcoin address
*/ */
typedef boost::variant<CNoDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessUnknown> CTxDestination; typedef boost::variant<CNoDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessUnknown> CTxDestination;

View File

@ -932,6 +932,7 @@ static std::string RecurseImportData(const CScript& script, ImportData& import_d
return "unspendable script"; return "unspendable script";
case TxoutType::NONSTANDARD: case TxoutType::NONSTANDARD:
case TxoutType::WITNESS_UNKNOWN: case TxoutType::WITNESS_UNKNOWN:
case TxoutType::WITNESS_V1_TAPROOT:
default: default:
return "unrecognized script"; return "unrecognized script";
} }

View File

@ -96,6 +96,7 @@ IsMineResult IsMineInner(const LegacyScriptPubKeyMan& keystore, const CScript& s
case TxoutType::NONSTANDARD: case TxoutType::NONSTANDARD:
case TxoutType::NULL_DATA: case TxoutType::NULL_DATA:
case TxoutType::WITNESS_UNKNOWN: case TxoutType::WITNESS_UNKNOWN:
case TxoutType::WITNESS_V1_TAPROOT:
break; break;
case TxoutType::PUBKEY: case TxoutType::PUBKEY:
keyID = CPubKey(vSolutions[0]).GetID(); keyID = CPubKey(vSolutions[0]).GetID();

View File

@ -55,6 +55,7 @@ from test_framework.script import (
MAX_SCRIPT_ELEMENT_SIZE, MAX_SCRIPT_ELEMENT_SIZE,
OP_0, OP_0,
OP_1, OP_1,
OP_2,
OP_16, OP_16,
OP_2DROP, OP_2DROP,
OP_CHECKMULTISIG, OP_CHECKMULTISIG,
@ -1400,6 +1401,10 @@ class SegWitTest(BitcoinTestFramework):
assert_equal(len(self.nodes[1].getrawmempool()), 0) assert_equal(len(self.nodes[1].getrawmempool()), 0)
for version in list(range(OP_1, OP_16 + 1)) + [OP_0]: for version in list(range(OP_1, OP_16 + 1)) + [OP_0]:
# First try to spend to a future version segwit script_pubkey. # First try to spend to a future version segwit script_pubkey.
if version == OP_1:
# Don't use 32-byte v1 witness (used by Taproot; see BIP 341)
script_pubkey = CScript([CScriptOp(version), witness_hash + b'\x00'])
else:
script_pubkey = CScript([CScriptOp(version), witness_hash]) script_pubkey = CScript([CScriptOp(version), witness_hash])
tx.vin = [CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b"")] tx.vin = [CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b"")]
tx.vout = [CTxOut(self.utxo[0].nValue - 1000, script_pubkey)] tx.vout = [CTxOut(self.utxo[0].nValue - 1000, script_pubkey)]
@ -1413,9 +1418,9 @@ class SegWitTest(BitcoinTestFramework):
self.sync_blocks() self.sync_blocks()
assert len(self.nodes[0].getrawmempool()) == 0 assert len(self.nodes[0].getrawmempool()) == 0
# Finally, verify that version 0 -> version 1 transactions # Finally, verify that version 0 -> version 2 transactions
# are standard # are standard
script_pubkey = CScript([CScriptOp(OP_1), witness_hash]) script_pubkey = CScript([CScriptOp(OP_2), witness_hash])
tx2 = CTransaction() tx2 = CTransaction()
tx2.vin = [CTxIn(COutPoint(tx.sha256, 0), b"")] tx2.vin = [CTxIn(COutPoint(tx.sha256, 0), b"")]
tx2.vout = [CTxOut(tx.vout[0].nValue - 1000, script_pubkey)] tx2.vout = [CTxOut(tx.vout[0].nValue - 1000, script_pubkey)]