aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPieter Wuille <pieter@wuille.net>2020-09-11 14:34:10 -0700
committerPieter Wuille <pieter@wuille.net>2020-10-12 17:18:47 -0700
commite9a021d7e6a454d610a45cb9b3995f0d96a5fbb6 (patch)
tree010e43f4b0a219779aaa562f6636e20ac18e848c
parent865d2c37e2e44678498b7f425b65e01b1e231cde (diff)
downloadbitcoin-e9a021d7e6a454d610a45cb9b3995f0d96a5fbb6.tar.xz
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).
-rw-r--r--src/policy/policy.cpp34
-rw-r--r--src/policy/policy.h8
-rw-r--r--src/script/sign.cpp1
-rw-r--r--src/script/standard.cpp8
-rw-r--r--src/script/standard.h4
-rw-r--r--src/wallet/rpcdump.cpp1
-rw-r--r--src/wallet/scriptpubkeyman.cpp1
-rwxr-xr-xtest/functional/p2p_segwit.py11
8 files changed, 61 insertions, 7 deletions
diff --git a/src/policy/policy.cpp b/src/policy/policy.cpp
index 0e9820da1e..69f2b456f1 100644
--- a/src/policy/policy.cpp
+++ b/src/policy/policy.cpp
@@ -9,7 +9,7 @@
#include <consensus/validation.h>
#include <coins.h>
-
+#include <span.h>
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:
CScript prevScript = prev.scriptPubKey;
+ bool p2sh = false;
if (prevScript.IsPayToScriptHash()) {
std::vector <std::vector<unsigned char> > stack;
// 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())
return false;
prevScript = CScript(stack.back().begin(), stack.back().end());
+ p2sh = true;
}
int witnessversion = 0;
@@ -237,6 +239,36 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
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;
}
diff --git a/src/policy/policy.h b/src/policy/policy.h
index 7f168ee20f..51d67b9390 100644
--- a/src/policy/policy.h
+++ b/src/policy/policy.h
@@ -40,6 +40,8 @@ static const bool DEFAULT_PERMIT_BAREMULTISIG = true;
static const unsigned int MAX_STANDARD_P2WSH_STACK_ITEMS = 100;
/** The maximum size of each witness stack item in a standard P2WSH script */
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 */
static const unsigned int MAX_STANDARD_P2WSH_SCRIPT_SIZE = 3600;
/** 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_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM |
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. */
static constexpr unsigned int STANDARD_NOT_MANDATORY_VERIFY_FLAGS = STANDARD_SCRIPT_VERIFY_FLAGS & ~MANDATORY_SCRIPT_VERIFY_FLAGS;
diff --git a/src/script/sign.cpp b/src/script/sign.cpp
index ace798eec1..0e6864d547 100644
--- a/src/script/sign.cpp
+++ b/src/script/sign.cpp
@@ -111,6 +111,7 @@ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator
case TxoutType::NONSTANDARD:
case TxoutType::NULL_DATA:
case TxoutType::WITNESS_UNKNOWN:
+ case TxoutType::WITNESS_V1_TAPROOT:
return false;
case TxoutType::PUBKEY:
if (!CreateSig(creator, sigdata, provider, sig, CPubKey(vSolutions[0]), scriptPubKey, sigversion)) return false;
diff --git a/src/script/standard.cpp b/src/script/standard.cpp
index 96a3d311a6..f2f81664f6 100644
--- a/src/script/standard.cpp
+++ b/src/script/standard.cpp
@@ -55,6 +55,7 @@ std::string GetTxnOutputType(TxoutType t)
case TxoutType::NULL_DATA: return "nulldata";
case TxoutType::WITNESS_V0_KEYHASH: return "witness_v0_keyhash";
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";
} // no default case, so the compiler can warn about missing cases
assert(false);
@@ -130,6 +131,11 @@ TxoutType Solver(const CScript& scriptPubKey, std::vector<std::vector<unsigned c
vSolutionsRet.push_back(witnessprogram);
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) {
vSolutionsRet.push_back(std::vector<unsigned char>{(unsigned char)witnessversion});
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());
addressRet = hash;
return true;
- } else if (whichType == TxoutType::WITNESS_UNKNOWN) {
+ } else if (whichType == TxoutType::WITNESS_UNKNOWN || whichType == TxoutType::WITNESS_V1_TAPROOT) {
WitnessUnknown unk;
unk.version = vSolutions[0][0];
std::copy(vSolutions[1].begin(), vSolutions[1].end(), unk.program);
diff --git a/src/script/standard.h b/src/script/standard.h
index 6dbcd04968..721203385e 100644
--- a/src/script/standard.h
+++ b/src/script/standard.h
@@ -129,6 +129,7 @@ enum class TxoutType {
NULL_DATA, //!< unspendable OP_RETURN script that carries data
WITNESS_V0_SCRIPTHASH,
WITNESS_V0_KEYHASH,
+ WITNESS_V1_TAPROOT,
WITNESS_UNKNOWN, //!< Only for Witness versions not already defined above
};
@@ -206,7 +207,8 @@ struct WitnessUnknown
* * ScriptHash: TxoutType::SCRIPTHASH destination (P2SH)
* * WitnessV0ScriptHash: TxoutType::WITNESS_V0_SCRIPTHASH destination (P2WSH)
* * 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
*/
typedef boost::variant<CNoDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessUnknown> CTxDestination;
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp
index 9e36a09780..7dcab46ad3 100644
--- a/src/wallet/rpcdump.cpp
+++ b/src/wallet/rpcdump.cpp
@@ -932,6 +932,7 @@ static std::string RecurseImportData(const CScript& script, ImportData& import_d
return "unspendable script";
case TxoutType::NONSTANDARD:
case TxoutType::WITNESS_UNKNOWN:
+ case TxoutType::WITNESS_V1_TAPROOT:
default:
return "unrecognized script";
}
diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp
index 435716e56a..b7c70dac3a 100644
--- a/src/wallet/scriptpubkeyman.cpp
+++ b/src/wallet/scriptpubkeyman.cpp
@@ -96,6 +96,7 @@ IsMineResult IsMineInner(const LegacyScriptPubKeyMan& keystore, const CScript& s
case TxoutType::NONSTANDARD:
case TxoutType::NULL_DATA:
case TxoutType::WITNESS_UNKNOWN:
+ case TxoutType::WITNESS_V1_TAPROOT:
break;
case TxoutType::PUBKEY:
keyID = CPubKey(vSolutions[0]).GetID();
diff --git a/test/functional/p2p_segwit.py b/test/functional/p2p_segwit.py
index d79ed449e5..29735b0fb3 100755
--- a/test/functional/p2p_segwit.py
+++ b/test/functional/p2p_segwit.py
@@ -55,6 +55,7 @@ from test_framework.script import (
MAX_SCRIPT_ELEMENT_SIZE,
OP_0,
OP_1,
+ OP_2,
OP_16,
OP_2DROP,
OP_CHECKMULTISIG,
@@ -1400,7 +1401,11 @@ class SegWitTest(BitcoinTestFramework):
assert_equal(len(self.nodes[1].getrawmempool()), 0)
for version in list(range(OP_1, OP_16 + 1)) + [OP_0]:
# First try to spend to a future version segwit script_pubkey.
- script_pubkey = CScript([CScriptOp(version), witness_hash])
+ 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])
tx.vin = [CTxIn(COutPoint(self.utxo[0].sha256, self.utxo[0].n), b"")]
tx.vout = [CTxOut(self.utxo[0].nValue - 1000, script_pubkey)]
tx.rehash()
@@ -1413,9 +1418,9 @@ class SegWitTest(BitcoinTestFramework):
self.sync_blocks()
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
- script_pubkey = CScript([CScriptOp(OP_1), witness_hash])
+ script_pubkey = CScript([CScriptOp(OP_2), witness_hash])
tx2 = CTransaction()
tx2.vin = [CTxIn(COutPoint(tx.sha256, 0), b"")]
tx2.vout = [CTxOut(tx.vout[0].nValue - 1000, script_pubkey)]