diff options
-rw-r--r-- | src/bitcoin-tx.cpp | 3 | ||||
-rw-r--r-- | src/core_read.cpp | 1 | ||||
-rw-r--r-- | src/key.cpp | 21 | ||||
-rw-r--r-- | src/key.h | 12 | ||||
-rw-r--r-- | src/node/psbt.cpp | 6 | ||||
-rw-r--r-- | src/psbt.cpp | 31 | ||||
-rw-r--r-- | src/psbt.h | 11 | ||||
-rw-r--r-- | src/pubkey.cpp | 4 | ||||
-rw-r--r-- | src/pubkey.h | 10 | ||||
-rw-r--r-- | src/rpc/rawtransaction.cpp | 6 | ||||
-rw-r--r-- | src/script/descriptor.cpp | 4 | ||||
-rw-r--r-- | src/script/interpreter.cpp | 14 | ||||
-rw-r--r-- | src/script/interpreter.h | 5 | ||||
-rw-r--r-- | src/script/sign.cpp | 195 | ||||
-rw-r--r-- | src/script/sign.h | 9 | ||||
-rw-r--r-- | src/script/signingprovider.cpp | 13 | ||||
-rw-r--r-- | src/script/signingprovider.h | 4 | ||||
-rw-r--r-- | src/script/standard.cpp | 57 | ||||
-rw-r--r-- | src/script/standard.h | 46 | ||||
-rw-r--r-- | src/test/key_tests.cpp | 42 | ||||
-rw-r--r-- | src/wallet/external_signer_scriptpubkeyman.cpp | 4 | ||||
-rw-r--r-- | src/wallet/external_signer_scriptpubkeyman.h | 2 | ||||
-rw-r--r-- | src/wallet/rpcwallet.cpp | 12 | ||||
-rw-r--r-- | src/wallet/scriptpubkeyman.cpp | 8 | ||||
-rw-r--r-- | src/wallet/scriptpubkeyman.h | 6 | ||||
-rw-r--r-- | src/wallet/test/psbt_wallet_tests.cpp | 2 | ||||
-rw-r--r-- | src/wallet/wallet.cpp | 5 | ||||
-rwxr-xr-x | test/functional/wallet_taproot.py | 126 |
28 files changed, 594 insertions, 65 deletions
diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index 93ac4b8f7e..3fc87ae1ff 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -506,11 +506,12 @@ static void MutateTxDelOutput(CMutableTransaction& tx, const std::string& strOut tx.vout.erase(tx.vout.begin() + outIdx); } -static const unsigned int N_SIGHASH_OPTS = 6; +static const unsigned int N_SIGHASH_OPTS = 7; static const struct { const char *flagStr; int flags; } sighashOptions[N_SIGHASH_OPTS] = { + {"DEFAULT", SIGHASH_DEFAULT}, {"ALL", SIGHASH_ALL}, {"NONE", SIGHASH_NONE}, {"SINGLE", SIGHASH_SINGLE}, diff --git a/src/core_read.cpp b/src/core_read.cpp index b5fc93886d..6108961010 100644 --- a/src/core_read.cpp +++ b/src/core_read.cpp @@ -260,6 +260,7 @@ int ParseSighashString(const UniValue& sighash) int hash_type = SIGHASH_ALL; if (!sighash.isNull()) { static std::map<std::string, int> map_sighash_values = { + {std::string("DEFAULT"), int(SIGHASH_DEFAULT)}, {std::string("ALL"), int(SIGHASH_ALL)}, {std::string("ALL|ANYONECANPAY"), int(SIGHASH_ALL|SIGHASH_ANYONECANPAY)}, {std::string("NONE"), int(SIGHASH_NONE)}, diff --git a/src/key.cpp b/src/key.cpp index 5666adebb8..dcad386e77 100644 --- a/src/key.cpp +++ b/src/key.cpp @@ -7,10 +7,13 @@ #include <crypto/common.h> #include <crypto/hmac_sha512.h> +#include <hash.h> #include <random.h> #include <secp256k1.h> +#include <secp256k1_extrakeys.h> #include <secp256k1_recovery.h> +#include <secp256k1_schnorrsig.h> static secp256k1_context* secp256k1_context_sign = nullptr; @@ -258,6 +261,24 @@ bool CKey::SignCompact(const uint256 &hash, std::vector<unsigned char>& vchSig) return true; } +bool CKey::SignSchnorr(const uint256& hash, Span<unsigned char> sig, const uint256* merkle_root, const uint256* aux) const +{ + assert(sig.size() == 64); + secp256k1_keypair keypair; + if (!secp256k1_keypair_create(secp256k1_context_sign, &keypair, begin())) return false; + if (merkle_root) { + secp256k1_xonly_pubkey pubkey; + if (!secp256k1_keypair_xonly_pub(secp256k1_context_sign, &pubkey, nullptr, &keypair)) return false; + unsigned char pubkey_bytes[32]; + if (!secp256k1_xonly_pubkey_serialize(secp256k1_context_sign, pubkey_bytes, &pubkey)) return false; + uint256 tweak = XOnlyPubKey(pubkey_bytes).ComputeTapTweakHash(merkle_root->IsNull() ? nullptr : merkle_root); + if (!secp256k1_keypair_xonly_tweak_add(GetVerifyContext(), &keypair, tweak.data())) return false; + } + bool ret = secp256k1_schnorrsig_sign(secp256k1_context_sign, sig.data(), hash.data(), &keypair, secp256k1_nonce_function_bip340, aux ? (void*)aux->data() : nullptr); + memory_cleanse(&keypair, sizeof(keypair)); + return ret; +} + bool CKey::Load(const CPrivKey &seckey, const CPubKey &vchPubKey, bool fSkipCheck=false) { if (!ec_seckey_import_der(secp256k1_context_sign, (unsigned char*)begin(), seckey.data(), seckey.size())) return false; @@ -128,6 +128,18 @@ public: */ bool SignCompact(const uint256& hash, std::vector<unsigned char>& vchSig) const; + /** + * Create a BIP-340 Schnorr signature, for the xonly-pubkey corresponding to *this, + * optionally tweaked by *merkle_root. Additional nonce entropy can be provided through + * aux. + * + * When merkle_root is not nullptr, this results in a signature with a modified key as + * specified in BIP341: + * - If merkle_root->IsNull(): key + H_TapTweak(pubkey)*G + * - Otherwise: key + H_TapTweak(pubkey || *merkle_root) + */ + bool SignSchnorr(const uint256& hash, Span<unsigned char> sig, const uint256* merkle_root = nullptr, const uint256* aux = nullptr) const; + //! Derive BIP32 child key. bool Derive(CKey& keyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc) const; diff --git a/src/node/psbt.cpp b/src/node/psbt.cpp index c189018268..b013b6d579 100644 --- a/src/node/psbt.cpp +++ b/src/node/psbt.cpp @@ -23,6 +23,8 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) result.inputs.resize(psbtx.tx->vin.size()); + const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx); + for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { PSBTInput& input = psbtx.inputs[i]; PSBTInputAnalysis& input_analysis = result.inputs[i]; @@ -61,7 +63,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) // Figure out what is missing SignatureData outdata; - bool complete = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, 1, &outdata); + bool complete = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, 1, &outdata); // Things are missing if (!complete) { @@ -121,7 +123,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) PSBTInput& input = psbtx.inputs[i]; Coin newcoin; - if (!SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, 1, nullptr, true) || !psbtx.GetInputUTXO(newcoin.out, i)) { + if (!SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, nullptr, 1) || !psbtx.GetInputUTXO(newcoin.out, i)) { success = false; break; } else { diff --git a/src/psbt.cpp b/src/psbt.cpp index a849b2ea53..5445bc8aa1 100644 --- a/src/psbt.cpp +++ b/src/psbt.cpp @@ -59,12 +59,15 @@ bool PartiallySignedTransaction::AddOutput(const CTxOut& txout, const PSBTOutput bool PartiallySignedTransaction::GetInputUTXO(CTxOut& utxo, int input_index) const { - PSBTInput input = inputs[input_index]; + const PSBTInput& input = inputs[input_index]; uint32_t prevout_index = tx->vin[input_index].prevout.n; if (input.non_witness_utxo) { if (prevout_index >= input.non_witness_utxo->vout.size()) { return false; } + if (input.non_witness_utxo->GetHash() != tx->vin[input_index].prevout.hash) { + return false; + } utxo = input.non_witness_utxo->vout[prevout_index]; } else if (!input.witness_utxo.IsNull()) { utxo = input.witness_utxo; @@ -227,7 +230,24 @@ void UpdatePSBTOutput(const SigningProvider& provider, PartiallySignedTransactio psbt_out.FromSignatureData(sigdata); } -bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, int sighash, SignatureData* out_sigdata, bool use_dummy) +PrecomputedTransactionData PrecomputePSBTData(const PartiallySignedTransaction& psbt) +{ + const CMutableTransaction& tx = *psbt.tx; + bool have_all_spent_outputs = true; + std::vector<CTxOut> utxos(tx.vin.size()); + for (size_t idx = 0; idx < tx.vin.size(); ++idx) { + if (!psbt.GetInputUTXO(utxos[idx], idx)) have_all_spent_outputs = false; + } + PrecomputedTransactionData txdata; + if (have_all_spent_outputs) { + txdata.Init(tx, std::move(utxos), true); + } else { + txdata.Init(tx, {}, true); + } + return txdata; +} + +bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, int sighash, SignatureData* out_sigdata) { PSBTInput& input = psbt.inputs.at(index); const CMutableTransaction& tx = *psbt.tx; @@ -267,10 +287,10 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& sigdata.witness = false; bool sig_complete; - if (use_dummy) { + if (txdata == nullptr) { sig_complete = ProduceSignature(provider, DUMMY_SIGNATURE_CREATOR, utxo.scriptPubKey, sigdata); } else { - MutableTransactionSignatureCreator creator(&tx, index, utxo.nValue, sighash); + MutableTransactionSignatureCreator creator(&tx, index, utxo.nValue, txdata, sighash); sig_complete = ProduceSignature(provider, creator, utxo.scriptPubKey, sigdata); } // Verify that a witness signature was produced in case one was required. @@ -302,8 +322,9 @@ bool FinalizePSBT(PartiallySignedTransaction& psbtx) // PartiallySignedTransaction did not understand them), this will combine them into a final // script. bool complete = true; + const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx); for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { - complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, SIGHASH_ALL); + complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, SIGHASH_ALL); } return complete; diff --git a/src/psbt.h b/src/psbt.h index 96ae39fdb8..f6b82b43de 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -567,11 +567,18 @@ enum class PSBTRole { std::string PSBTRoleName(PSBTRole role); +/** Compute a PrecomputedTransactionData object from a psbt. */ +PrecomputedTransactionData PrecomputePSBTData(const PartiallySignedTransaction& psbt); + /** Checks whether a PSBTInput is already signed. */ bool PSBTInputSigned(const PSBTInput& input); -/** Signs a PSBTInput, verifying that all provided data matches what is being signed. */ -bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, int sighash = SIGHASH_ALL, SignatureData* out_sigdata = nullptr, bool use_dummy = false); +/** Signs a PSBTInput, verifying that all provided data matches what is being signed. + * + * txdata should be the output of PrecomputePSBTData (which can be shared across + * multiple SignPSBTInput calls). If it is nullptr, a dummy signature will be created. + **/ +bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, int sighash = SIGHASH_ALL, SignatureData* out_sigdata = nullptr); /** Counts the unsigned inputs of a PSBT. */ size_t CountPSBTUnsignedInputs(const PartiallySignedTransaction& psbt); diff --git a/src/pubkey.cpp b/src/pubkey.cpp index 51cc826b00..175a39b805 100644 --- a/src/pubkey.cpp +++ b/src/pubkey.cpp @@ -373,3 +373,7 @@ ECCVerifyHandle::~ECCVerifyHandle() secp256k1_context_verify = nullptr; } } + +const secp256k1_context* GetVerifyContext() { + return secp256k1_context_verify; +} diff --git a/src/pubkey.h b/src/pubkey.h index 152a48dd18..eec34a89c2 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -234,6 +234,10 @@ public: * fail. */ bool IsFullyValid() const; + /** Test whether this is the 0 key (the result of default construction). This implies + * !IsFullyValid(). */ + bool IsNull() const { return m_keydata.IsNull(); } + /** Construct an x-only pubkey from exactly 32 bytes. */ explicit XOnlyPubKey(Span<const unsigned char> bytes); @@ -312,4 +316,10 @@ public: ~ECCVerifyHandle(); }; +typedef struct secp256k1_context_struct secp256k1_context; + +/** Access to the internal secp256k1 context used for verification. Only intended to be used + * by key.cpp. */ +const secp256k1_context* GetVerifyContext(); + #endif // BITCOIN_PUBKEY_H diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 414c6637a5..ccb3123714 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -753,7 +753,8 @@ static RPCHelpMan signrawtransactionwithkey() }, }, }, - {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"ALL"}, "The signature hash type. Must be one of:\n" + {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"DEFAULT"}, "The signature hash type. Must be one of:\n" + " \"DEFAULT\"\n" " \"ALL\"\n" " \"NONE\"\n" " \"SINGLE\"\n" @@ -1655,6 +1656,7 @@ static RPCHelpMan utxoupdatepsbt() } // Fill the inputs + const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx); for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { PSBTInput& input = psbtx.inputs.at(i); @@ -1671,7 +1673,7 @@ static RPCHelpMan utxoupdatepsbt() // Update script/keypath information using descriptor data. // Note that SignPSBTInput does a lot more than just constructing ECDSA signatures // we don't actually care about those here, in fact. - SignPSBTInput(public_provider, psbtx, i, /* sighash_type */ 1); + SignPSBTInput(public_provider, psbtx, i, &txdata, /* sighash_type */ 1); } // Update script/keypath information using descriptor data. diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index 51cf8a7d62..84a8b06c5c 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -843,7 +843,9 @@ protected: XOnlyPubKey xpk(keys[0]); if (!xpk.IsFullyValid()) return {}; builder.Finalize(xpk); - return Vector(GetScriptForDestination(builder.GetOutput())); + WitnessV1Taproot output = builder.GetOutput(); + out.tr_spenddata[output].Merge(builder.GetSpendData()); + return Vector(GetScriptForDestination(output)); } bool ToStringSubScriptHelper(const SigningProvider* arg, std::string& ret, bool priv, bool normalized) const override { diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp index 3c3c3ac1a8..2dd173ee20 100644 --- a/src/script/interpreter.cpp +++ b/src/script/interpreter.cpp @@ -1420,7 +1420,7 @@ uint256 GetSpentScriptsSHA256(const std::vector<CTxOut>& outputs_spent) } // namespace template <class T> -void PrecomputedTransactionData::Init(const T& txTo, std::vector<CTxOut>&& spent_outputs) +void PrecomputedTransactionData::Init(const T& txTo, std::vector<CTxOut>&& spent_outputs, bool force) { assert(!m_spent_outputs_ready); @@ -1431,9 +1431,9 @@ void PrecomputedTransactionData::Init(const T& txTo, std::vector<CTxOut>&& spent } // Determine which precomputation-impacting features this transaction uses. - bool uses_bip143_segwit = false; - bool uses_bip341_taproot = false; - for (size_t inpos = 0; inpos < txTo.vin.size(); ++inpos) { + bool uses_bip143_segwit = force; + bool uses_bip341_taproot = force; + for (size_t inpos = 0; inpos < txTo.vin.size() && !(uses_bip143_segwit && uses_bip341_taproot); ++inpos) { if (!txTo.vin[inpos].scriptWitness.IsNull()) { if (m_spent_outputs_ready && m_spent_outputs[inpos].scriptPubKey.size() == 2 + WITNESS_V1_TAPROOT_SIZE && m_spent_outputs[inpos].scriptPubKey[0] == OP_1) { @@ -1478,8 +1478,8 @@ PrecomputedTransactionData::PrecomputedTransactionData(const T& txTo) } // explicit instantiation -template void PrecomputedTransactionData::Init(const CTransaction& txTo, std::vector<CTxOut>&& spent_outputs); -template void PrecomputedTransactionData::Init(const CMutableTransaction& txTo, std::vector<CTxOut>&& spent_outputs); +template void PrecomputedTransactionData::Init(const CTransaction& txTo, std::vector<CTxOut>&& spent_outputs, bool force); +template void PrecomputedTransactionData::Init(const CMutableTransaction& txTo, std::vector<CTxOut>&& spent_outputs, bool force); template PrecomputedTransactionData::PrecomputedTransactionData(const CTransaction& txTo); template PrecomputedTransactionData::PrecomputedTransactionData(const CMutableTransaction& txTo); @@ -1711,7 +1711,7 @@ bool GenericTransactionSignatureChecker<T>::CheckSchnorrSignature(Span<const uns if (hashtype == SIGHASH_DEFAULT) return set_error(serror, SCRIPT_ERR_SCHNORR_SIG_HASHTYPE); } uint256 sighash; - assert(this->txdata); + if (!this->txdata) return HandleMissingData(m_mdb); if (!SignatureHashSchnorr(sighash, execdata, *txTo, nIn, hashtype, sigversion, *this->txdata, m_mdb)) { return set_error(serror, SCRIPT_ERR_SCHNORR_SIG_HASHTYPE); } diff --git a/src/script/interpreter.h b/src/script/interpreter.h index fa4ee83e04..ced5c28bc1 100644 --- a/src/script/interpreter.h +++ b/src/script/interpreter.h @@ -168,7 +168,7 @@ struct PrecomputedTransactionData PrecomputedTransactionData() = default; template <class T> - void Init(const T& tx, std::vector<CTxOut>&& spent_outputs); + void Init(const T& tx, std::vector<CTxOut>&& spent_outputs, bool force = false); template <class T> explicit PrecomputedTransactionData(const T& tx); @@ -260,6 +260,9 @@ enum class MissingDataBehavior FAIL, //!< Just act as if the signature was invalid }; +template<typename T> +bool SignatureHashSchnorr(uint256& hash_out, const ScriptExecutionData& execdata, const T& tx_to, uint32_t in_pos, uint8_t hash_type, SigVersion sigversion, const PrecomputedTransactionData& cache, MissingDataBehavior mdb); + template <class T> class GenericTransactionSignatureChecker : public BaseSignatureChecker { diff --git a/src/script/sign.cpp b/src/script/sign.cpp index da0092f9e3..65276f641f 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -11,13 +11,28 @@ #include <script/signingprovider.h> #include <script/standard.h> #include <uint256.h> +#include <util/vector.h> typedef std::vector<unsigned char> valtype; -MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn) : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), checker(txTo, nIn, amountIn, MissingDataBehavior::FAIL) {} +MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn) + : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), checker(txTo, nIn, amountIn, MissingDataBehavior::FAIL), + m_txdata(nullptr) +{ +} + +MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData* txdata, int nHashTypeIn) + : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), + checker(txdata ? MutableTransactionSignatureChecker(txTo, nIn, amount, *txdata, MissingDataBehavior::FAIL) : + MutableTransactionSignatureChecker(txTo, nIn, amount, MissingDataBehavior::FAIL)), + m_txdata(txdata) +{ +} bool MutableTransactionSignatureCreator::CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& address, const CScript& scriptCode, SigVersion sigversion) const { + assert(sigversion == SigVersion::BASE || sigversion == SigVersion::WITNESS_V0); + CKey key; if (!provider.GetKey(address, key)) return false; @@ -26,13 +41,61 @@ bool MutableTransactionSignatureCreator::CreateSig(const SigningProvider& provid if (sigversion == SigVersion::WITNESS_V0 && !key.IsCompressed()) return false; - // Signing for witness scripts needs the amount. - if (sigversion == SigVersion::WITNESS_V0 && amount < 0) return false; + // Signing without known amount does not work in witness scripts. + if (sigversion == SigVersion::WITNESS_V0 && !MoneyRange(amount)) return false; + + // BASE/WITNESS_V0 signatures don't support explicit SIGHASH_DEFAULT, use SIGHASH_ALL instead. + const int hashtype = nHashType == SIGHASH_DEFAULT ? SIGHASH_ALL : nHashType; - uint256 hash = SignatureHash(scriptCode, *txTo, nIn, nHashType, amount, sigversion); + uint256 hash = SignatureHash(scriptCode, *txTo, nIn, hashtype, amount, sigversion, m_txdata); if (!key.Sign(hash, vchSig)) return false; - vchSig.push_back((unsigned char)nHashType); + vchSig.push_back((unsigned char)hashtype); + return true; +} + +bool MutableTransactionSignatureCreator::CreateSchnorrSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const XOnlyPubKey& pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion) const +{ + assert(sigversion == SigVersion::TAPROOT || sigversion == SigVersion::TAPSCRIPT); + + CKey key; + { + // For now, use the old full pubkey-based key derivation logic. As it indexed by + // Hash160(full pubkey), we need to try both a version prefixed with 0x02, and one + // with 0x03. + unsigned char b[33] = {0x02}; + std::copy(pubkey.begin(), pubkey.end(), b + 1); + CPubKey fullpubkey; + fullpubkey.Set(b, b + 33); + CKeyID keyid = fullpubkey.GetID(); + if (!provider.GetKey(keyid, key)) { + b[0] = 0x03; + fullpubkey.Set(b, b + 33); + CKeyID keyid = fullpubkey.GetID(); + if (!provider.GetKey(keyid, key)) return false; + } + } + + // BIP341/BIP342 signing needs lots of precomputed transaction data. While some + // (non-SIGHASH_DEFAULT) sighash modes exist that can work with just some subset + // of data present, for now, only support signing when everything is provided. + if (!m_txdata || !m_txdata->m_bip341_taproot_ready || !m_txdata->m_spent_outputs_ready) return false; + + ScriptExecutionData execdata; + execdata.m_annex_init = true; + execdata.m_annex_present = false; // Only support annex-less signing for now. + if (sigversion == SigVersion::TAPSCRIPT) { + execdata.m_codeseparator_pos_init = true; + execdata.m_codeseparator_pos = 0xFFFFFFFF; // Only support non-OP_CODESEPARATOR BIP342 signing for now. + if (!leaf_hash) return false; // BIP342 signing needs leaf hash. + execdata.m_tapleaf_hash_init = true; + execdata.m_tapleaf_hash = *leaf_hash; + } + uint256 hash; + if (!SignatureHashSchnorr(hash, execdata, *txTo, nIn, nHashType, sigversion, *m_txdata, MissingDataBehavior::FAIL)) return false; + sig.resize(64); + if (!key.SignSchnorr(hash, sig, merkle_root, nullptr)) return false; + if (nHashType) sig.push_back(nHashType); return true; } @@ -92,6 +155,86 @@ static bool CreateSig(const BaseSignatureCreator& creator, SignatureData& sigdat return false; } +static bool CreateTaprootScriptSig(const BaseSignatureCreator& creator, SignatureData& sigdata, const SigningProvider& provider, std::vector<unsigned char>& sig_out, const XOnlyPubKey& pubkey, const uint256& leaf_hash, SigVersion sigversion) +{ + auto lookup_key = std::make_pair(pubkey, leaf_hash); + auto it = sigdata.taproot_script_sigs.find(lookup_key); + if (it != sigdata.taproot_script_sigs.end()) { + sig_out = it->second; + } + if (creator.CreateSchnorrSig(provider, sig_out, pubkey, &leaf_hash, nullptr, sigversion)) { + sigdata.taproot_script_sigs[lookup_key] = sig_out; + return true; + } + return false; +} + +static bool SignTaprootScript(const SigningProvider& provider, const BaseSignatureCreator& creator, SignatureData& sigdata, int leaf_version, const CScript& script, std::vector<valtype>& result) +{ + // Only BIP342 tapscript signing is supported for now. + if (leaf_version != TAPROOT_LEAF_TAPSCRIPT) return false; + SigVersion sigversion = SigVersion::TAPSCRIPT; + + uint256 leaf_hash = (CHashWriter(HASHER_TAPLEAF) << uint8_t(leaf_version) << script).GetSHA256(); + + // <xonly pubkey> OP_CHECKSIG + if (script.size() == 34 && script[33] == OP_CHECKSIG && script[0] == 0x20) { + XOnlyPubKey pubkey(MakeSpan(script).subspan(1, 32)); + std::vector<unsigned char> sig; + if (CreateTaprootScriptSig(creator, sigdata, provider, sig, pubkey, leaf_hash, sigversion)) { + result = Vector(std::move(sig)); + return true; + } + } + + return false; +} + +static bool SignTaproot(const SigningProvider& provider, const BaseSignatureCreator& creator, const WitnessV1Taproot& output, SignatureData& sigdata, std::vector<valtype>& result) +{ + TaprootSpendData spenddata; + + // Gather information about this output. + if (provider.GetTaprootSpendData(output, spenddata)) { + sigdata.tr_spenddata.Merge(spenddata); + } + + // Try key path spending. + { + std::vector<unsigned char> sig; + if (sigdata.taproot_key_path_sig.size() == 0) { + if (creator.CreateSchnorrSig(provider, sig, spenddata.internal_key, nullptr, &spenddata.merkle_root, SigVersion::TAPROOT)) { + sigdata.taproot_key_path_sig = sig; + } + } + if (sigdata.taproot_key_path_sig.size()) { + result = Vector(sigdata.taproot_key_path_sig); + return true; + } + } + + // Try script path spending. + std::vector<std::vector<unsigned char>> smallest_result_stack; + for (const auto& [key, control_blocks] : sigdata.tr_spenddata.scripts) { + const auto& [script, leaf_ver] = key; + std::vector<std::vector<unsigned char>> result_stack; + if (SignTaprootScript(provider, creator, sigdata, leaf_ver, script, result_stack)) { + result_stack.emplace_back(std::begin(script), std::end(script)); // Push the script + result_stack.push_back(*control_blocks.begin()); // Push the smallest control block + if (smallest_result_stack.size() == 0 || + GetSerializeSize(result_stack, PROTOCOL_VERSION) < GetSerializeSize(smallest_result_stack, PROTOCOL_VERSION)) { + smallest_result_stack = std::move(result_stack); + } + } + } + if (smallest_result_stack.size() != 0) { + result = std::move(smallest_result_stack); + return true; + } + + return false; +} + /** * Sign scriptPubKey using signature made with creator. * Signatures are returned in scriptSigRet (or returns false if scriptPubKey can't be signed), @@ -113,7 +256,6 @@ 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; @@ -175,6 +317,9 @@ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator // Could not find witnessScript, add to missing sigdata.missing_witness_script = uint256(vSolutions[0]); return false; + + case TxoutType::WITNESS_V1_TAPROOT: + return SignTaproot(provider, creator, WitnessV1Taproot(XOnlyPubKey{vSolutions[0]}), sigdata, ret); } // no default case, so the compiler can warn about missing cases assert(false); } @@ -205,7 +350,6 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato bool solved = SignStep(provider, creator, fromPubKey, result, whichType, SigVersion::BASE, sigdata); bool P2SH = false; CScript subscript; - sigdata.scriptWitness.stack.clear(); if (solved && whichType == TxoutType::SCRIPTHASH) { @@ -238,10 +382,17 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato sigdata.scriptWitness.stack = result; sigdata.witness = true; result.clear(); + } else if (whichType == TxoutType::WITNESS_V1_TAPROOT && !P2SH) { + sigdata.witness = true; + if (solved) { + sigdata.scriptWitness.stack = std::move(result); + } + result.clear(); } else if (solved && whichType == TxoutType::WITNESS_UNKNOWN) { sigdata.witness = true; } + if (!sigdata.witness) sigdata.scriptWitness.stack.clear(); if (P2SH) { result.push_back(std::vector<unsigned char>(subscript.begin(), subscript.end())); } @@ -402,6 +553,7 @@ class DummySignatureChecker final : public BaseSignatureChecker public: DummySignatureChecker() {} bool CheckECDSASignature(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override { return true; } + bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey, SigVersion sigversion, const ScriptExecutionData& execdata, ScriptError* serror) const override { return true; } }; const DummySignatureChecker DUMMY_CHECKER; @@ -427,6 +579,11 @@ public: vchSig[6 + m_r_len + m_s_len] = SIGHASH_ALL; return true; } + bool CreateSchnorrSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const XOnlyPubKey& pubkey, const uint256* leaf_hash, const uint256* tweak, SigVersion sigversion) const override + { + sig.assign(64, '\000'); + return true; + } }; } @@ -476,6 +633,26 @@ bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, // Use CTransaction for the constant parts of the // transaction to avoid rehashing. const CTransaction txConst(mtx); + + PrecomputedTransactionData txdata; + std::vector<CTxOut> spent_outputs; + spent_outputs.resize(mtx.vin.size()); + bool have_all_spent_outputs = true; + for (unsigned int i = 0; i < mtx.vin.size(); i++) { + CTxIn& txin = mtx.vin[i]; + auto coin = coins.find(txin.prevout); + if (coin == coins.end() || coin->second.IsSpent()) { + have_all_spent_outputs = false; + } else { + spent_outputs[i] = CTxOut(coin->second.out.nValue, coin->second.out.scriptPubKey); + } + } + if (have_all_spent_outputs) { + txdata.Init(txConst, std::move(spent_outputs), true); + } else { + txdata.Init(txConst, {}, true); + } + // Sign what we can: for (unsigned int i = 0; i < mtx.vin.size(); i++) { CTxIn& txin = mtx.vin[i]; @@ -490,7 +667,7 @@ bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, SignatureData sigdata = DataFromTransaction(mtx, i, coin->second.out); // Only sign SIGHASH_SINGLE if there's a corresponding output: if (!fHashSingle || (i < mtx.vout.size())) { - ProduceSignature(*keystore, MutableTransactionSignatureCreator(&mtx, i, amount, nHashType), prevPubKey, sigdata); + ProduceSignature(*keystore, MutableTransactionSignatureCreator(&mtx, i, amount, &txdata, nHashType), prevPubKey, sigdata); } UpdateInput(txin, sigdata); @@ -502,7 +679,7 @@ bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, } ScriptError serror = SCRIPT_ERR_OK; - if (!VerifyScript(txin.scriptSig, prevPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount, MissingDataBehavior::FAIL), &serror)) { + if (!VerifyScript(txin.scriptSig, prevPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount, txdata, MissingDataBehavior::FAIL), &serror)) { if (serror == SCRIPT_ERR_INVALID_STACK_OPERATION) { // Unable to sign input and verification failed (possible attempt to partially sign). input_errors[i] = "Unable to sign input, invalid stack size (possibly missing key)"; diff --git a/src/script/sign.h b/src/script/sign.h index a1cfe1574d..b4e7318892 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -11,13 +11,13 @@ #include <pubkey.h> #include <script/interpreter.h> #include <script/keyorigin.h> +#include <script/standard.h> #include <span.h> #include <streams.h> class CKey; class CKeyID; class CScript; -class CScriptID; class CTransaction; class SigningProvider; @@ -31,6 +31,7 @@ public: /** Create a singular (non-script) signature. */ virtual bool CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& keyid, const CScript& scriptCode, SigVersion sigversion) const =0; + virtual bool CreateSchnorrSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const XOnlyPubKey& pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion) const =0; }; /** A signature creator for transactions. */ @@ -40,11 +41,14 @@ class MutableTransactionSignatureCreator : public BaseSignatureCreator { int nHashType; CAmount amount; const MutableTransactionSignatureChecker checker; + const PrecomputedTransactionData* m_txdata; public: MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn = SIGHASH_ALL); + MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData* txdata, int nHashTypeIn = SIGHASH_ALL); const BaseSignatureChecker& Checker() const override { return checker; } bool CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& keyid, const CScript& scriptCode, SigVersion sigversion) const override; + bool CreateSchnorrSig(const SigningProvider& provider, std::vector<unsigned char>& sig, const XOnlyPubKey& pubkey, const uint256* leaf_hash, const uint256* merkle_root, SigVersion sigversion) const override; }; /** A signature creator that just produces 71-byte empty signatures. */ @@ -64,8 +68,11 @@ struct SignatureData { CScript redeem_script; ///< The redeemScript (if any) for the input CScript witness_script; ///< The witnessScript (if any) for the input. witnessScripts are used in P2WSH outputs. CScriptWitness scriptWitness; ///< The scriptWitness of an input. Contains complete signatures or the traditional partial signatures format. scriptWitness is part of a transaction input per BIP 144. + TaprootSpendData tr_spenddata; ///< Taproot spending data. std::map<CKeyID, SigPair> signatures; ///< BIP 174 style partial signatures for the input. May contain all signatures necessary for producing a final scriptSig or scriptWitness. std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> misc_pubkeys; + std::vector<unsigned char> taproot_key_path_sig; /// Schnorr signature for key path spending + std::map<std::pair<XOnlyPubKey, uint256>, std::vector<unsigned char>> taproot_script_sigs; ///< (Partial) schnorr signatures, indexed by XOnlyPubKey and leaf_hash. std::vector<CKeyID> missing_pubkeys; ///< KeyIDs of pubkeys which could not be found std::vector<CKeyID> missing_sigs; ///< KeyIDs of pubkeys for signatures which could not be found uint160 missing_redeem_script; ///< ScriptID of the missing redeemScript (if any) diff --git a/src/script/signingprovider.cpp b/src/script/signingprovider.cpp index 9781ec32af..b80fbe22ce 100644 --- a/src/script/signingprovider.cpp +++ b/src/script/signingprovider.cpp @@ -44,6 +44,11 @@ bool HidingSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& inf return m_provider->GetKeyOrigin(keyid, info); } +bool HidingSigningProvider::GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const +{ + return m_provider->GetTaprootSpendData(output_key, spenddata); +} + bool FlatSigningProvider::GetCScript(const CScriptID& scriptid, CScript& script) const { return LookupHelper(scripts, scriptid, script); } bool FlatSigningProvider::GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const { return LookupHelper(pubkeys, keyid, pubkey); } bool FlatSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const @@ -54,6 +59,10 @@ bool FlatSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) return ret; } bool FlatSigningProvider::GetKey(const CKeyID& keyid, CKey& key) const { return LookupHelper(keys, keyid, key); } +bool FlatSigningProvider::GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const +{ + return LookupHelper(tr_spenddata, output_key, spenddata); +} FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvider& b) { @@ -66,6 +75,10 @@ FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvide ret.keys.insert(b.keys.begin(), b.keys.end()); ret.origins = a.origins; ret.origins.insert(b.origins.begin(), b.origins.end()); + ret.tr_spenddata = a.tr_spenddata; + for (const auto& [output_key, spenddata] : b.tr_spenddata) { + ret.tr_spenddata[output_key].Merge(spenddata); + } return ret; } diff --git a/src/script/signingprovider.h b/src/script/signingprovider.h index 76f31d2f6f..939ae10622 100644 --- a/src/script/signingprovider.h +++ b/src/script/signingprovider.h @@ -25,6 +25,7 @@ public: virtual bool GetKey(const CKeyID &address, CKey& key) const { return false; } virtual bool HaveKey(const CKeyID &address) const { return false; } virtual bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const { return false; } + virtual bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const { return false; } }; extern const SigningProvider& DUMMY_SIGNING_PROVIDER; @@ -42,6 +43,7 @@ public: bool GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const override; bool GetKey(const CKeyID& keyid, CKey& key) const override; bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override; + bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const override; }; struct FlatSigningProvider final : public SigningProvider @@ -50,11 +52,13 @@ struct FlatSigningProvider final : public SigningProvider std::map<CKeyID, CPubKey> pubkeys; std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> origins; std::map<CKeyID, CKey> keys; + std::map<XOnlyPubKey, TaprootSpendData> tr_spenddata; /** Map from output key to spend data. */ bool GetCScript(const CScriptID& scriptid, CScript& script) const override; bool GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const override; bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override; bool GetKey(const CKeyID& keyid, CKey& key) const override; + bool GetTaprootSpendData(const XOnlyPubKey& output_key, TaprootSpendData& spenddata) const override; }; FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvider& b); diff --git a/src/script/standard.cpp b/src/script/standard.cpp index a4b11cc0a9..748f00dda5 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -377,6 +377,16 @@ bool IsValidDestination(const CTxDestination& dest) { /*static*/ TaprootBuilder::NodeInfo TaprootBuilder::Combine(NodeInfo&& a, NodeInfo&& b) { NodeInfo ret; + /* Iterate over all tracked leaves in a, add b's hash to their Merkle branch, and move them to ret. */ + for (auto& leaf : a.leaves) { + leaf.merkle_branch.push_back(b.hash); + ret.leaves.emplace_back(std::move(leaf)); + } + /* Iterate over all tracked leaves in b, add a's hash to their Merkle branch, and move them to ret. */ + for (auto& leaf : b.leaves) { + leaf.merkle_branch.push_back(a.hash); + ret.leaves.emplace_back(std::move(leaf)); + } /* Lexicographically sort a and b's hash, and compute parent hash. */ if (a.hash < b.hash) { ret.hash = (CHashWriter(HASHER_TAPBRANCH) << a.hash << b.hash).GetSHA256(); @@ -386,6 +396,21 @@ bool IsValidDestination(const CTxDestination& dest) { return ret; } +void TaprootSpendData::Merge(TaprootSpendData other) +{ + // TODO: figure out how to better deal with conflicting information + // being merged. + if (internal_key.IsNull() && !other.internal_key.IsNull()) { + internal_key = other.internal_key; + } + if (merkle_root.IsNull() && !other.merkle_root.IsNull()) { + merkle_root = other.merkle_root; + } + for (auto& [key, control_blocks] : other.scripts) { + scripts[key].merge(std::move(control_blocks)); + } +} + void TaprootBuilder::Insert(TaprootBuilder::NodeInfo&& node, int depth) { assert(depth >= 0 && (size_t)depth <= TAPROOT_CONTROL_MAX_NODE_COUNT); @@ -435,13 +460,14 @@ void TaprootBuilder::Insert(TaprootBuilder::NodeInfo&& node, int depth) return branch.size() == 0 || (branch.size() == 1 && branch[0]); } -TaprootBuilder& TaprootBuilder::Add(int depth, const CScript& script, int leaf_version) +TaprootBuilder& TaprootBuilder::Add(int depth, const CScript& script, int leaf_version, bool track) { assert((leaf_version & ~TAPROOT_LEAF_MASK) == 0); if (!IsValid()) return *this; - /* Construct NodeInfo object with leaf hash. */ + /* Construct NodeInfo object with leaf hash and (if track is true) also leaf information. */ NodeInfo node; node.hash = (CHashWriter{HASHER_TAPLEAF} << uint8_t(leaf_version) << script).GetSHA256(); + if (track) node.leaves.emplace_back(LeafInfo{script, leaf_version, {}}); /* Insert into the branch. */ Insert(std::move(node), depth); return *this; @@ -464,8 +490,33 @@ TaprootBuilder& TaprootBuilder::Finalize(const XOnlyPubKey& internal_key) m_internal_key = internal_key; auto ret = m_internal_key.CreateTapTweak(m_branch.size() == 0 ? nullptr : &m_branch[0]->hash); assert(ret.has_value()); - std::tie(m_output_key, std::ignore) = *ret; + std::tie(m_output_key, m_parity) = *ret; return *this; } WitnessV1Taproot TaprootBuilder::GetOutput() { return WitnessV1Taproot{m_output_key}; } + +TaprootSpendData TaprootBuilder::GetSpendData() const +{ + TaprootSpendData spd; + spd.merkle_root = m_branch.size() == 0 ? uint256() : m_branch[0]->hash; + spd.internal_key = m_internal_key; + if (m_branch.size()) { + // If any script paths exist, they have been combined into the root m_branch[0] + // by now. Compute the control block for each of its tracked leaves, and put them in + // spd.scripts. + for (const auto& leaf : m_branch[0]->leaves) { + std::vector<unsigned char> control_block; + control_block.resize(TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE * leaf.merkle_branch.size()); + control_block[0] = leaf.leaf_version | (m_parity ? 1 : 0); + std::copy(m_internal_key.begin(), m_internal_key.end(), control_block.begin() + 1); + if (leaf.merkle_branch.size()) { + std::copy(leaf.merkle_branch[0].begin(), + leaf.merkle_branch[0].begin() + TAPROOT_CONTROL_NODE_SIZE * leaf.merkle_branch.size(), + control_block.begin() + TAPROOT_CONTROL_BASE_SIZE); + } + spd.scripts[{leaf.script, leaf.leaf_version}].insert(std::move(control_block)); + } + } + return spd; +} diff --git a/src/script/standard.h b/src/script/standard.h index d7ea5cef27..285dd4c116 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -11,6 +11,7 @@ #include <uint256.h> #include <util/hash_type.h> +#include <map> #include <string> #include <variant> @@ -209,15 +210,50 @@ CScript GetScriptForRawPubKey(const CPubKey& pubkey); /** Generate a multisig script. */ CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys); +struct ShortestVectorFirstComparator +{ + bool operator()(const std::vector<unsigned char>& a, const std::vector<unsigned char>& b) const + { + if (a.size() < b.size()) return true; + if (a.size() > b.size()) return false; + return a < b; + } +}; + +struct TaprootSpendData +{ + /** The BIP341 internal key. */ + XOnlyPubKey internal_key; + /** The Merkle root of the script tree (0 if no scripts). */ + uint256 merkle_root; + /** Map from (script, leaf_version) to (sets of) control blocks. + * The control blocks are sorted by size, so that the signing logic can + * easily prefer the cheapest one. */ + std::map<std::pair<CScript, int>, std::set<std::vector<unsigned char>, ShortestVectorFirstComparator>> scripts; + /** Merge other TaprootSpendData (for the same scriptPubKey) into this. */ + void Merge(TaprootSpendData other); +}; + /** Utility class to construct Taproot outputs from internal key and script tree. */ class TaprootBuilder { private: + /** Information about a tracked leaf in the Merkle tree. */ + struct LeafInfo + { + CScript script; //!< The script. + int leaf_version; //!< The leaf version for that script. + std::vector<uint256> merkle_branch; //!< The hashing partners above this leaf. + }; + /** Information associated with a node in the Merkle tree. */ struct NodeInfo { /** Merkle hash of this node. */ uint256 hash; + /** Tracked leaves underneath this node (either from the node itself, or its children). + * The merkle_branch field for each is the partners to get to *this* node. */ + std::vector<LeafInfo> leaves; }; /** Whether the builder is in a valid state so far. */ bool m_valid = true; @@ -260,7 +296,8 @@ private: std::vector<std::optional<NodeInfo>> m_branch; XOnlyPubKey m_internal_key; //!< The internal key, set when finalizing. - XOnlyPubKey m_output_key; //!< The output key, computed when finalizing. */ + XOnlyPubKey m_output_key; //!< The output key, computed when finalizing. + bool m_parity; //!< The tweak parity, computed when finalizing. /** Combine information about a parent Merkle tree node from its child nodes. */ static NodeInfo Combine(NodeInfo&& a, NodeInfo&& b); @@ -269,8 +306,9 @@ private: public: /** Add a new script at a certain depth in the tree. Add() operations must be called - * in depth-first traversal order of binary tree. */ - TaprootBuilder& Add(int depth, const CScript& script, int leaf_version); + * in depth-first traversal order of binary tree. If track is true, it will be included in + * the GetSpendData() output. */ + TaprootBuilder& Add(int depth, const CScript& script, int leaf_version, bool track = true); /** Like Add(), but for a Merkle node with a given hash to the tree. */ TaprootBuilder& AddOmitted(int depth, const uint256& hash); /** Finalize the construction. Can only be called when IsComplete() is true. @@ -285,6 +323,8 @@ public: WitnessV1Taproot GetOutput(); /** Check if a list of depths is legal (will lead to IsComplete()). */ static bool ValidDepths(const std::vector<int>& depths); + /** Compute spending data (after Finalize()). */ + TaprootSpendData GetSpendData() const; }; #endif // BITCOIN_SCRIPT_STANDARD_H diff --git a/src/test/key_tests.cpp b/src/test/key_tests.cpp index cb66d5164e..b915982d98 100644 --- a/src/test/key_tests.cpp +++ b/src/test/key_tests.cpp @@ -300,6 +300,48 @@ BOOST_AUTO_TEST_CASE(bip340_test_vectors) auto sig = ParseHex(test.first[2]); BOOST_CHECK_EQUAL(XOnlyPubKey(pubkey).VerifySchnorr(uint256(msg), sig), test.second); } + + static const std::vector<std::array<std::string, 5>> SIGN_VECTORS = { + {{"0000000000000000000000000000000000000000000000000000000000000003", "F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9", "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000000", "E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0"}}, + {{"B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF", "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", "0000000000000000000000000000000000000000000000000000000000000001", "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", "6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A"}}, + {{"C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9", "DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8", "C87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906", "7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C", "5831AAEED7B44BB74E5EAB94BA9D4294C49BCF2A60728D8B4C200F50DD313C1BAB745879A5AD954A72C45A91C3A51D3C7ADEA98D82F8481E0E1E03674A6F3FB7"}}, + {{"0B432B2677937381AEF05BB02A66ECD012773062CF3FA2549E44F58ED2401710", "25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160D8F517", "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", "7EB0509757E246F19449885651611CB965ECC1A187DD51B64FDA1EDC9637D5EC97582B9CB13DB3933705B32BA982AF5AF25FD78881EBB32771FC5922EFC66EA3"}}, + }; + + for (const auto& [sec_hex, pub_hex, aux_hex, msg_hex, sig_hex] : SIGN_VECTORS) { + auto sec = ParseHex(sec_hex); + auto pub = ParseHex(pub_hex); + uint256 aux256(ParseHex(aux_hex)); + uint256 msg256(ParseHex(msg_hex)); + auto sig = ParseHex(sig_hex); + unsigned char sig64[64]; + + // Run the untweaked test vectors above, comparing with exact expected signature. + CKey key; + key.Set(sec.begin(), sec.end(), true); + XOnlyPubKey pubkey(key.GetPubKey()); + BOOST_CHECK(std::equal(pubkey.begin(), pubkey.end(), pub.begin(), pub.end())); + bool ok = key.SignSchnorr(msg256, sig64, nullptr, &aux256); + BOOST_CHECK(ok); + BOOST_CHECK(std::vector<unsigned char>(sig64, sig64 + 64) == sig); + // Verify those signatures for good measure. + BOOST_CHECK(pubkey.VerifySchnorr(msg256, sig64)); + + // Do 10 iterations where we sign with a random Merkle root to tweak, + // and compare against the resulting tweaked keys, with random aux. + // In iteration i=0 we tweak with empty Merkle tree. + for (int i = 0; i < 10; ++i) { + uint256 merkle_root; + if (i) merkle_root = InsecureRand256(); + auto tweaked = pubkey.CreateTapTweak(i ? &merkle_root : nullptr); + BOOST_CHECK(tweaked); + XOnlyPubKey tweaked_key = tweaked->first; + aux256 = InsecureRand256(); + bool ok = key.SignSchnorr(msg256, sig64, &merkle_root, &aux256); + BOOST_CHECK(ok); + BOOST_CHECK(tweaked_key.VerifySchnorr(msg256, sig64)); + } + } } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet/external_signer_scriptpubkeyman.cpp b/src/wallet/external_signer_scriptpubkeyman.cpp index e5464c5b89..efef1ec754 100644 --- a/src/wallet/external_signer_scriptpubkeyman.cpp +++ b/src/wallet/external_signer_scriptpubkeyman.cpp @@ -60,10 +60,10 @@ bool ExternalSignerScriptPubKeyMan::DisplayAddress(const CScript scriptPubKey, c } // If sign is true, transaction must previously have been filled -TransactionError ExternalSignerScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbt, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const +TransactionError ExternalSignerScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const { if (!sign) { - return DescriptorScriptPubKeyMan::FillPSBT(psbt, sighash_type, false, bip32derivs, n_signed); + return DescriptorScriptPubKeyMan::FillPSBT(psbt, txdata, sighash_type, false, bip32derivs, n_signed); } // Already complete if every input is now signed diff --git a/src/wallet/external_signer_scriptpubkeyman.h b/src/wallet/external_signer_scriptpubkeyman.h index 5df1e6d939..8eed947b7b 100644 --- a/src/wallet/external_signer_scriptpubkeyman.h +++ b/src/wallet/external_signer_scriptpubkeyman.h @@ -28,6 +28,6 @@ class ExternalSignerScriptPubKeyMan : public DescriptorScriptPubKeyMan bool DisplayAddress(const CScript scriptPubKey, const ExternalSigner &signer) const; - TransactionError FillPSBT(PartiallySignedTransaction& psbt, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override; + TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override; }; #endif // BITCOIN_WALLET_EXTERNAL_SIGNER_SCRIPTPUBKEYMAN_H diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 534c974178..ab34af2329 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3320,7 +3320,8 @@ RPCHelpMan signrawtransactionwithwallet() }, }, }, - {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"ALL"}, "The signature hash type. Must be one of\n" + {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"DEFAULT"}, "The signature hash type. Must be one of\n" + " \"DEFAULT\"\n" " \"ALL\"\n" " \"NONE\"\n" " \"SINGLE\"\n" @@ -3542,7 +3543,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name) } else { PartiallySignedTransaction psbtx(mtx); bool complete = false; - const TransactionError err = pwallet->FillPSBT(psbtx, complete, SIGHASH_ALL, false /* sign */, true /* bip32derivs */); + const TransactionError err = pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, false /* sign */, true /* bip32derivs */); CHECK_NONFATAL(err == TransactionError::OK); CHECK_NONFATAL(!complete); CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); @@ -4175,8 +4176,8 @@ static RPCHelpMan send() // First fill transaction with our data without signing, // so external signers are not asked sign more than once. bool complete; - pwallet->FillPSBT(psbtx, complete, SIGHASH_ALL, false, true); - const TransactionError err = pwallet->FillPSBT(psbtx, complete, SIGHASH_ALL, true, false); + pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, false, true); + const TransactionError err = pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, true, false); if (err != TransactionError::OK) { throw JSONRPCTransactionError(err); } @@ -4291,7 +4292,8 @@ static RPCHelpMan walletprocesspsbt() { {"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction base64 string"}, {"sign", RPCArg::Type::BOOL, RPCArg::Default{true}, "Also sign the transaction when updating"}, - {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"ALL"}, "The signature hash type to sign with if not specified by the PSBT. Must be one of\n" + {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"DEFAULT"}, "The signature hash type to sign with if not specified by the PSBT. Must be one of\n" + " \"DEFAULT\"\n" " \"ALL\"\n" " \"NONE\"\n" " \"SINGLE\"\n" diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 6af936b1ac..c8baa0665e 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -596,7 +596,7 @@ SigningResult LegacyScriptPubKeyMan::SignMessage(const std::string& message, con return SigningResult::SIGNING_FAILED; } -TransactionError LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const +TransactionError LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const { if (n_signed) { *n_signed = 0; @@ -625,7 +625,7 @@ TransactionError LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psb } SignatureData sigdata; input.FillSignatureData(sigdata); - SignPSBTInput(HidingSigningProvider(this, !sign, !bip32derivs), psbtx, i, sighash_type); + SignPSBTInput(HidingSigningProvider(this, !sign, !bip32derivs), psbtx, i, &txdata, sighash_type); bool signed_one = PSBTInputSigned(input); if (n_signed && (signed_one || !sign)) { @@ -2082,7 +2082,7 @@ SigningResult DescriptorScriptPubKeyMan::SignMessage(const std::string& message, return SigningResult::OK; } -TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const +TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const { if (n_signed) { *n_signed = 0; @@ -2132,7 +2132,7 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& } } - SignPSBTInput(HidingSigningProvider(keys.get(), !sign, !bip32derivs), psbtx, i, sighash_type); + SignPSBTInput(HidingSigningProvider(keys.get(), !sign, !bip32derivs), psbtx, i, &txdata, sighash_type); bool signed_one = PSBTInputSigned(input); if (n_signed && (signed_one || !sign)) { diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h index b8e34fbac3..3c4603608c 100644 --- a/src/wallet/scriptpubkeyman.h +++ b/src/wallet/scriptpubkeyman.h @@ -235,7 +235,7 @@ public: /** Sign a message with the given script */ virtual SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const { return SigningResult::SIGNING_FAILED; }; /** Adds script and derivation path information to a PSBT, and optionally signs it. */ - virtual TransactionError FillPSBT(PartiallySignedTransaction& psbt, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const { return TransactionError::INVALID_PSBT; } + virtual TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const { return TransactionError::INVALID_PSBT; } virtual uint256 GetID() const { return uint256(); } @@ -394,7 +394,7 @@ public: bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors) const override; SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override; - TransactionError FillPSBT(PartiallySignedTransaction& psbt, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override; + TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override; uint256 GetID() const override; @@ -605,7 +605,7 @@ public: bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors) const override; SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override; - TransactionError FillPSBT(PartiallySignedTransaction& psbt, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override; + TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override; uint256 GetID() const override; diff --git a/src/wallet/test/psbt_wallet_tests.cpp b/src/wallet/test/psbt_wallet_tests.cpp index ce7e661b67..1cefa386b7 100644 --- a/src/wallet/test/psbt_wallet_tests.cpp +++ b/src/wallet/test/psbt_wallet_tests.cpp @@ -71,7 +71,7 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test) // Try to sign the mutated input SignatureData sigdata; - BOOST_CHECK(spk_man->FillPSBT(psbtx, SIGHASH_ALL, true, true) != TransactionError::OK); + BOOST_CHECK(spk_man->FillPSBT(psbtx, PrecomputePSBTData(psbtx), SIGHASH_ALL, true, true) != TransactionError::OK); } BOOST_AUTO_TEST_CASE(parse_hd_keypath) diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 073d5e269b..256faf2b23 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1808,7 +1808,7 @@ bool CWallet::SignTransaction(CMutableTransaction& tx) const coins[input.prevout] = Coin(wtx.tx->vout[input.prevout.n], wtx.m_confirm.block_height, wtx.IsCoinBase()); } std::map<int, std::string> input_errors; - return SignTransaction(tx, coins, SIGHASH_ALL, input_errors); + return SignTransaction(tx, coins, SIGHASH_DEFAULT, input_errors); } bool CWallet::SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, std::string>& input_errors) const @@ -1831,6 +1831,7 @@ TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& comp if (n_signed) { *n_signed = 0; } + const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx); LOCK(cs_wallet); // Get all of the previous transactions for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { @@ -1857,7 +1858,7 @@ TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& comp // Fill in information from ScriptPubKeyMans for (ScriptPubKeyMan* spk_man : GetAllScriptPubKeyMans()) { int n_signed_this_spkm = 0; - TransactionError res = spk_man->FillPSBT(psbtx, sighash_type, sign, bip32derivs, &n_signed_this_spkm); + TransactionError res = spk_man->FillPSBT(psbtx, txdata, sighash_type, sign, bip32derivs, &n_signed_this_spkm); if (res != TransactionError::OK) { return res; } diff --git a/test/functional/wallet_taproot.py b/test/functional/wallet_taproot.py index 5803c0cf54..1547a90125 100755 --- a/test/functional/wallet_taproot.py +++ b/test/functional/wallet_taproot.py @@ -6,6 +6,7 @@ import random +from decimal import Decimal from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal from test_framework.descriptors import descsum_create @@ -233,20 +234,85 @@ class WalletTaprootTest(BitcoinTestFramework): # tr descriptors cannot be imported when Taproot is not active result = self.privs_tr_enabled.importdescriptors([{"desc": desc, "timestamp": "now"}]) assert(result[0]["success"]) - result = self.privs_tr_disabled.importdescriptors([{"desc": desc, "timestamp": "now"}]) - assert(not result[0]["success"]) - assert_equal(result[0]["error"]["code"], -4) - assert_equal(result[0]["error"]["message"], "Cannot import tr() descriptor when Taproot is not active") result = self.pubs_tr_enabled.importdescriptors([{"desc": desc_pub, "timestamp": "now"}]) assert(result[0]["success"]) - result = self.pubs_tr_disabled.importdescriptors([{"desc": desc_pub, "timestamp": "now"}]) - assert(not result[0]["success"]) - assert_equal(result[0]["error"]["code"], -4) - assert_equal(result[0]["error"]["message"], "Cannot import tr() descriptor when Taproot is not active") + if desc.startswith("tr"): + result = self.privs_tr_disabled.importdescriptors([{"desc": desc, "timestamp": "now"}]) + assert(not result[0]["success"]) + assert_equal(result[0]["error"]["code"], -4) + assert_equal(result[0]["error"]["message"], "Cannot import tr() descriptor when Taproot is not active") + result = self.pubs_tr_disabled.importdescriptors([{"desc": desc_pub, "timestamp": "now"}]) + assert(not result[0]["success"]) + assert_equal(result[0]["error"]["code"], -4) + assert_equal(result[0]["error"]["message"], "Cannot import tr() descriptor when Taproot is not active") + + def do_test_sendtoaddress(self, comment, pattern, privmap, treefn, keys_pay, keys_change): + self.log.info("Testing %s through sendtoaddress" % comment) + desc_pay = self.make_desc(pattern, privmap, keys_pay) + desc_change = self.make_desc(pattern, privmap, keys_change) + desc_pay_pub = self.make_desc(pattern, privmap, keys_pay, True) + desc_change_pub = self.make_desc(pattern, privmap, keys_change, True) + assert_equal(self.nodes[0].getdescriptorinfo(desc_pay)['descriptor'], desc_pay_pub) + assert_equal(self.nodes[0].getdescriptorinfo(desc_change)['descriptor'], desc_change_pub) + result = self.rpc_online.importdescriptors([{"desc": desc_pay, "active": True, "timestamp": "now"}]) + assert(result[0]['success']) + result = self.rpc_online.importdescriptors([{"desc": desc_change, "active": True, "timestamp": "now", "internal": True}]) + assert(result[0]['success']) + for i in range(4): + addr_g = self.rpc_online.getnewaddress(address_type='bech32') + if treefn is not None: + addr_r = self.make_addr(treefn, keys_pay, i) + assert_equal(addr_g, addr_r) + boring_balance = int(self.boring.getbalance() * 100000000) + to_amnt = random.randrange(1000000, boring_balance) + self.boring.sendtoaddress(address=addr_g, amount=Decimal(to_amnt) / 100000000, subtractfeefromamount=True) + self.nodes[0].generatetoaddress(1, self.boring.getnewaddress()) + test_balance = int(self.rpc_online.getbalance() * 100000000) + ret_amnt = random.randrange(100000, test_balance) + res = self.rpc_online.sendtoaddress(address=self.boring.getnewaddress(), amount=Decimal(ret_amnt) / 100000000, subtractfeefromamount=True) + self.nodes[0].generatetoaddress(1, self.boring.getnewaddress()) + assert(self.rpc_online.gettransaction(res)["confirmations"] > 0) + + def do_test_psbt(self, comment, pattern, privmap, treefn, keys_pay, keys_change): + self.log.info("Testing %s through PSBT" % comment) + desc_pay = self.make_desc(pattern, privmap, keys_pay, False) + desc_change = self.make_desc(pattern, privmap, keys_change, False) + desc_pay_pub = self.make_desc(pattern, privmap, keys_pay, True) + desc_change_pub = self.make_desc(pattern, privmap, keys_change, True) + assert_equal(self.nodes[0].getdescriptorinfo(desc_pay)['descriptor'], desc_pay_pub) + assert_equal(self.nodes[0].getdescriptorinfo(desc_change)['descriptor'], desc_change_pub) + result = self.psbt_online.importdescriptors([{"desc": desc_pay_pub, "active": True, "timestamp": "now"}]) + assert(result[0]['success']) + result = self.psbt_online.importdescriptors([{"desc": desc_change_pub, "active": True, "timestamp": "now", "internal": True}]) + assert(result[0]['success']) + result = self.psbt_offline.importdescriptors([{"desc": desc_pay, "active": True, "timestamp": "now"}]) + assert(result[0]['success']) + result = self.psbt_offline.importdescriptors([{"desc": desc_change, "active": True, "timestamp": "now", "internal": True}]) + assert(result[0]['success']) + for i in range(4): + addr_g = self.psbt_online.getnewaddress(address_type='bech32') + if treefn is not None: + addr_r = self.make_addr(treefn, keys_pay, i) + assert_equal(addr_g, addr_r) + boring_balance = int(self.boring.getbalance() * 100000000) + to_amnt = random.randrange(1000000, boring_balance) + self.boring.sendtoaddress(address=addr_g, amount=Decimal(to_amnt) / 100000000, subtractfeefromamount=True) + self.nodes[0].generatetoaddress(1, self.boring.getnewaddress()) + test_balance = int(self.psbt_online.getbalance() * 100000000) + ret_amnt = random.randrange(100000, test_balance) + psbt = self.psbt_online.walletcreatefundedpsbt([], [{self.boring.getnewaddress(): Decimal(ret_amnt) / 100000000}], None, {"subtractFeeFromOutputs":[0]})['psbt'] + res = self.psbt_offline.walletprocesspsbt(psbt) + assert(res['complete']) + rawtx = self.nodes[0].finalizepsbt(res['psbt'])['hex'] + txid = self.nodes[0].sendrawtransaction(rawtx) + self.nodes[0].generatetoaddress(1, self.boring.getnewaddress()) + assert(self.psbt_online.gettransaction(txid)['confirmations'] > 0) def do_test(self, comment, pattern, privmap, treefn, nkeys): - keys = self.rand_keys(nkeys) - self.do_test_addr(comment, pattern, privmap, treefn, keys) + keys = self.rand_keys(nkeys * 4) + self.do_test_addr(comment, pattern, privmap, treefn, keys[0:nkeys]) + self.do_test_sendtoaddress(comment, pattern, privmap, treefn, keys[0:nkeys], keys[nkeys:2*nkeys]) + self.do_test_psbt(comment, pattern, privmap, treefn, keys[2*nkeys:3*nkeys], keys[3*nkeys:4*nkeys]) def run_test(self): self.log.info("Creating wallets...") @@ -258,8 +324,20 @@ class WalletTaprootTest(BitcoinTestFramework): self.pubs_tr_enabled = self.nodes[0].get_wallet_rpc("pubs_tr_enabled") self.nodes[2].createwallet(wallet_name="pubs_tr_disabled", descriptors=True, blank=True, disable_private_keys=True) self.pubs_tr_disabled=self.nodes[2].get_wallet_rpc("pubs_tr_disabled") + self.nodes[0].createwallet(wallet_name="boring") self.nodes[0].createwallet(wallet_name="addr_gen", descriptors=True, disable_private_keys=True, blank=True) + self.nodes[0].createwallet(wallet_name="rpc_online", descriptors=True, blank=True) + self.nodes[0].createwallet(wallet_name="psbt_online", descriptors=True, disable_private_keys=True, blank=True) + self.nodes[1].createwallet(wallet_name="psbt_offline", descriptors=True, blank=True) + self.boring = self.nodes[0].get_wallet_rpc("boring") self.addr_gen = self.nodes[0].get_wallet_rpc("addr_gen") + self.rpc_online = self.nodes[0].get_wallet_rpc("rpc_online") + self.psbt_online = self.nodes[0].get_wallet_rpc("psbt_online") + self.psbt_offline = self.nodes[1].get_wallet_rpc("psbt_offline") + + self.log.info("Mining blocks...") + gen_addr = self.boring.getnewaddress() + self.nodes[0].generatetoaddress(101, gen_addr) self.do_test( "tr(XPRV)", @@ -276,6 +354,13 @@ class WalletTaprootTest(BitcoinTestFramework): 1 ) self.do_test( + "wpkh(XPRV)", + "wpkh($1/*)", + [True], + None, + 1 + ) + self.do_test( "tr(XPRV,{H,{H,XPUB}})", "tr($1/*,{pk($H),{pk($H),pk($2/*)}})", [True, False], @@ -283,6 +368,13 @@ class WalletTaprootTest(BitcoinTestFramework): 2 ) self.do_test( + "wsh(multi(1,XPRV,XPUB))", + "wsh(multi(1,$1/*,$2/*))", + [True, False], + None, + 2 + ) + self.do_test( "tr(XPUB,{{H,{H,XPUB}},{H,{H,{H,XPRV}}}})", "tr($1/*,{{pk($H),{pk($H),pk($2/*)}},{pk($H),{pk($H),{pk($H),pk($3/*)}}}})", [False, False, True], @@ -290,5 +382,19 @@ class WalletTaprootTest(BitcoinTestFramework): 3 ) + self.log.info("Sending everything back...") + + txid = self.rpc_online.sendtoaddress(address=self.boring.getnewaddress(), amount=self.rpc_online.getbalance(), subtractfeefromamount=True) + self.nodes[0].generatetoaddress(1, self.boring.getnewaddress()) + assert(self.rpc_online.gettransaction(txid)["confirmations"] > 0) + + psbt = self.psbt_online.walletcreatefundedpsbt([], [{self.boring.getnewaddress(): self.psbt_online.getbalance()}], None, {"subtractFeeFromOutputs": [0]})['psbt'] + res = self.psbt_offline.walletprocesspsbt(psbt) + assert(res['complete']) + rawtx = self.nodes[0].finalizepsbt(res['psbt'])['hex'] + txid = self.nodes[0].sendrawtransaction(rawtx) + self.nodes[0].generatetoaddress(1, self.boring.getnewaddress()) + assert(self.psbt_online.gettransaction(txid)['confirmations'] > 0) + if __name__ == '__main__': WalletTaprootTest().main() |