fix: implement BIP341 key tweaking for P2TR transaction signing
This commit fixes P2TR (Pay-to-Taproot) transaction signing by properly implementing BIP341 key path spending. Key changes: - Add SignSchnorrTaproot() method to CKey for BIP341 tweaked signing - Implement ComputeTapTweak() and CreatePayToTaprootPubKey() in XOnlyPubKey - Add GetTaprootInternalKey() to SigningProvider interface for internal key lookup - Store taproot internal key mappings in LegacyScriptPubKeyMan - Fix FindTaprootPubKey() to use internal key mapping with fallback - Use empty scriptCode for Taproot key-path spending (per BIP341 spec) - Update HaveTaprootKey() to verify tweaked keys correctly Technical details: - Internal keys are tweaked using secp256k1_keypair_xonly_tweak_add - Parity handling is automatic via secp256k1 library - Empty scriptCode ensures correct sighash for key-path spending - Internal key to output key mapping stored for efficient lookup Testing: - P2TR address creation, funding, and spending work end-to-end - Multi-hop P2TR transactions tested successfully - All functional tests pass (feature_taproot.py, wallet_*, rpc_*) Fixes: non-mandatory-script-verify-flag error on P2TR spending
This commit is contained in:
40
src/key.cpp
40
src/key.cpp
@@ -7,7 +7,10 @@
|
||||
|
||||
#include <crypto/common.h>
|
||||
#include <crypto/hmac_sha512.h>
|
||||
#include <crypto/sha256.h>
|
||||
#include <random.h>
|
||||
#include <util/strencodings.h>
|
||||
#include <logging.h>
|
||||
|
||||
#include <secp256k1.h>
|
||||
#include <secp256k1_extrakeys.h>
|
||||
@@ -255,6 +258,43 @@ bool CKey::SignSchnorr(const uint256& hash, std::vector<unsigned char>& vchSig,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CKey::SignSchnorrTaproot(const uint256& hash, std::vector<unsigned char>& vchSig, const uint256* merkle_root, const unsigned char* aux) const
|
||||
{
|
||||
if (!fValid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// BIP341: Create keypair from internal private key
|
||||
secp256k1_keypair keypair;
|
||||
if (!secp256k1_keypair_create(secp256k1_context_sign, &keypair, begin())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the internal x-only public key to compute the tweak
|
||||
CPubKey internal_pubkey = GetPubKey();
|
||||
XOnlyPubKey internal_xonly = internal_pubkey.GetXOnlyPubKey();
|
||||
|
||||
// Compute BIP341 TapTweak: tagged_hash("TapTweak", internal_xonly || merkle_root)
|
||||
uint256 tweak = internal_xonly.ComputeTapTweak(merkle_root);
|
||||
|
||||
// Apply BIP341 tweak to the keypair
|
||||
// This handles the parity negation automatically:
|
||||
// - If internal pubkey has odd y, it negates the privkey before adding tweak
|
||||
// - Then adds tweak: tweaked_privkey = (possibly_negated_privkey + tweak) mod n
|
||||
if (!secp256k1_keypair_xonly_tweak_add(secp256k1_context_sign, &keypair, tweak.begin())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sign with the tweaked keypair using BIP340 Schnorr
|
||||
vchSig.resize(XOnlyPubKey::SCHNORR_SIGNATURE_SIZE);
|
||||
if (!secp256k1_schnorrsig_sign32(secp256k1_context_sign, vchSig.data(), hash.begin(), &keypair, aux)) {
|
||||
vchSig.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CKey::VerifyPubKey(const CPubKey& pubkey) const {
|
||||
if (pubkey.IsCompressed() != fCompressed) {
|
||||
return false;
|
||||
|
||||
@@ -130,6 +130,13 @@ public:
|
||||
*/
|
||||
bool SignSchnorr(const uint256& hash, std::vector<unsigned char>& vchSig, const unsigned char* aux = nullptr) const;
|
||||
|
||||
/**
|
||||
* Create a 64-byte BIP340 Schnorr signature for Taproot key-path spending.
|
||||
* This applies the Taproot tweak before signing.
|
||||
* merkle_root: nullptr for key-path only spend (no script tree)
|
||||
*/
|
||||
bool SignSchnorrTaproot(const uint256& hash, std::vector<unsigned char>& vchSig, const uint256* merkle_root = nullptr, const unsigned char* aux = nullptr) const;
|
||||
|
||||
/**
|
||||
* Create a compact signature (65 bytes), which allows reconstructing the used public key.
|
||||
* The format is one header byte, followed by two times 32 bytes for the serialized r and s values.
|
||||
|
||||
@@ -25,14 +25,21 @@ const std::array<OutputType, 4> OUTPUT_TYPES = {OutputType::LEGACY, OutputType::
|
||||
|
||||
static CTxDestination GetDestinationForTaprootKey(const CPubKey& key)
|
||||
{
|
||||
XOnlyPubKey xonly = key.GetXOnlyPubKey();
|
||||
if (!xonly.IsFullyValid()) {
|
||||
XOnlyPubKey internal_key = key.GetXOnlyPubKey();
|
||||
if (!internal_key.IsFullyValid()) {
|
||||
return CNoDestination();
|
||||
}
|
||||
|
||||
// Compute the tweaked output key: output_key = internal_key + H(internal_key) * G
|
||||
auto [output_key, parity] = internal_key.CreatePayToTaprootPubKey(nullptr);
|
||||
if (!output_key.IsFullyValid()) {
|
||||
return CNoDestination();
|
||||
}
|
||||
|
||||
WitnessUnknown dest;
|
||||
dest.version = 1;
|
||||
dest.length = WITNESS_V1_TAPROOT_SIZE;
|
||||
std::copy(xonly.begin(), xonly.end(), dest.program);
|
||||
std::copy(output_key.begin(), output_key.end(), dest.program);
|
||||
return dest;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <pubkey.h>
|
||||
|
||||
#include <crypto/sha256.h>
|
||||
#include <secp256k1.h>
|
||||
#include <secp256k1_extrakeys.h>
|
||||
#include <secp256k1_recovery.h>
|
||||
@@ -299,6 +300,63 @@ bool XOnlyPubKey::VerifySchnorr(const uint256& hash, const std::vector<unsigned
|
||||
return secp256k1_schnorrsig_verify(secp256k1_context_verify, sig.data(), hash.begin(), 32, &pubkey);
|
||||
}
|
||||
|
||||
uint256 XOnlyPubKey::ComputeTapTweak(const uint256* merkle_root) const
|
||||
{
|
||||
// BIP341: The tweak is H_TapTweak(internal_key || merkle_root)
|
||||
// For key-path only, merkle_root is empty, so: H_TapTweak(internal_key)
|
||||
// H_TapTweak is SHA256 with "TapTweak" tag
|
||||
|
||||
// Compute tagged hash: SHA256(SHA256("TapTweak") || SHA256("TapTweak") || data)
|
||||
CSHA256 hasher;
|
||||
unsigned char tag_hash[CSHA256::OUTPUT_SIZE];
|
||||
const char* tag = "TapTweak";
|
||||
CSHA256().Write((const unsigned char*)tag, 8).Finalize(tag_hash);
|
||||
|
||||
hasher.Write(tag_hash, sizeof(tag_hash));
|
||||
hasher.Write(tag_hash, sizeof(tag_hash));
|
||||
hasher.Write(m_keydata, SIZE);
|
||||
if (merkle_root) {
|
||||
hasher.Write(merkle_root->begin(), 32);
|
||||
}
|
||||
|
||||
uint256 result;
|
||||
hasher.Finalize(result.begin());
|
||||
return result;
|
||||
}
|
||||
|
||||
std::pair<XOnlyPubKey, bool> XOnlyPubKey::CreatePayToTaprootPubKey(const uint256* merkle_root) const
|
||||
{
|
||||
assert(secp256k1_context_verify && "secp256k1_context_verify must be initialized to use XOnlyPubKey.");
|
||||
|
||||
// Parse internal pubkey
|
||||
secp256k1_xonly_pubkey internal_pubkey;
|
||||
if (!secp256k1_xonly_pubkey_parse(secp256k1_context_verify, &internal_pubkey, m_keydata)) {
|
||||
return {XOnlyPubKey(), false};
|
||||
}
|
||||
|
||||
// Compute tweak
|
||||
uint256 tweak = ComputeTapTweak(merkle_root);
|
||||
|
||||
// Apply tweak to get output pubkey
|
||||
secp256k1_pubkey output_pubkey;
|
||||
if (!secp256k1_xonly_pubkey_tweak_add(secp256k1_context_verify, &output_pubkey, &internal_pubkey, tweak.begin())) {
|
||||
return {XOnlyPubKey(), false};
|
||||
}
|
||||
|
||||
// Convert to xonly and get parity
|
||||
secp256k1_xonly_pubkey output_xonly;
|
||||
int parity;
|
||||
if (!secp256k1_xonly_pubkey_from_pubkey(secp256k1_context_verify, &output_xonly, &parity, &output_pubkey)) {
|
||||
return {XOnlyPubKey(), false};
|
||||
}
|
||||
|
||||
// Serialize the output xonly pubkey
|
||||
unsigned char output_keydata[SIZE];
|
||||
secp256k1_xonly_pubkey_serialize(secp256k1_context_verify, output_keydata, &output_xonly);
|
||||
|
||||
return {XOnlyPubKey(output_keydata, output_keydata + SIZE), parity != 0};
|
||||
}
|
||||
|
||||
void CExtPubKey::Encode(unsigned char code[BIP32_EXTKEY_SIZE]) const {
|
||||
code[0] = nDepth;
|
||||
memcpy(code+1, vchFingerprint, 4);
|
||||
|
||||
14
src/pubkey.h
14
src/pubkey.h
@@ -87,6 +87,20 @@ public:
|
||||
|
||||
bool IsFullyValid() const;
|
||||
bool VerifySchnorr(const uint256& hash, const std::vector<unsigned char>& sig) const;
|
||||
|
||||
/**
|
||||
* Compute the BIP340/BIP341 tagged hash for TapTweak.
|
||||
* tweak = H_TapTweak(xonly_pubkey || merkle_root) if merkle_root is provided
|
||||
* tweak = H_TapTweak(xonly_pubkey) otherwise (key-path only spend)
|
||||
*/
|
||||
uint256 ComputeTapTweak(const uint256* merkle_root = nullptr) const;
|
||||
|
||||
/**
|
||||
* Compute the Taproot output key from this internal key.
|
||||
* output_key = internal_key + H_TapTweak(internal_key || merkle_root) * G
|
||||
* Returns the tweaked output key and optionally the parity.
|
||||
*/
|
||||
std::pair<XOnlyPubKey, bool> CreatePayToTaprootPubKey(const uint256* merkle_root = nullptr) const;
|
||||
};
|
||||
|
||||
/** An encapsulated public key. */
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include <script/signingprovider.h>
|
||||
#include <script/standard.h>
|
||||
#include <uint256.h>
|
||||
#include <logging.h>
|
||||
#include <util/strencodings.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
@@ -26,11 +28,28 @@ bool MutableTransactionSignatureCreator::CreateSig(const SigningProvider& provid
|
||||
return false;
|
||||
|
||||
if (sigversion == SigVersion::TAPROOT || sigversion == SigVersion::TAPSCRIPT) {
|
||||
if (!txdata || !txdata->m_bip341_taproot_ready || !key.IsCompressed()) return false;
|
||||
uint256 hash = SignatureHash(scriptCode, *txTo, nIn, nHashType, amount, sigversion, txdata);
|
||||
if (!key.SignSchnorr(hash, vchSig)) {
|
||||
if (!txdata) {
|
||||
return false;
|
||||
}
|
||||
if (!txdata->m_bip341_taproot_ready) {
|
||||
return false;
|
||||
}
|
||||
if (!key.IsCompressed()) {
|
||||
return false;
|
||||
}
|
||||
uint256 hash = SignatureHash(scriptCode, *txTo, nIn, nHashType, amount, sigversion, txdata);
|
||||
// For Taproot key-path spending, we need to apply the taproot tweak before signing
|
||||
// For TAPSCRIPT, we sign without the tweak (the script takes care of the commitment)
|
||||
if (sigversion == SigVersion::TAPROOT) {
|
||||
if (!key.SignSchnorrTaproot(hash, vchSig, nullptr)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// For script-path (TAPSCRIPT), use regular Schnorr signing
|
||||
if (!key.SignSchnorr(hash, vchSig)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (nHashType != SIGHASH_DEFAULT) {
|
||||
vchSig.push_back((unsigned char)nHashType);
|
||||
}
|
||||
@@ -84,18 +103,43 @@ static bool GetPubKey(const SigningProvider& provider, const SignatureData& sigd
|
||||
|
||||
static bool FindTaprootPubKey(const SigningProvider& provider, const std::vector<unsigned char>& xonly_bytes, CPubKey& pubkey)
|
||||
{
|
||||
if (xonly_bytes.size() != WITNESS_V1_TAPROOT_SIZE) return false;
|
||||
if (xonly_bytes.size() != WITNESS_V1_TAPROOT_SIZE) {
|
||||
return false;
|
||||
}
|
||||
const XOnlyPubKey output_key(xonly_bytes.begin(), xonly_bytes.end());
|
||||
if (!output_key.IsFullyValid()) return false;
|
||||
if (!output_key.IsFullyValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// First, try to look up the internal key directly from the provider's mapping.
|
||||
// This is the correct and efficient way for wallets that store the mapping.
|
||||
if (provider.GetTaprootInternalKey(output_key, pubkey)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fallback: for backwards compatibility or providers without the mapping,
|
||||
// enumerate keys and check if their tweaked version matches the output key.
|
||||
// This is less efficient but ensures compatibility with older code.
|
||||
std::array<unsigned char, CPubKey::COMPRESSED_SIZE> candidate;
|
||||
std::copy(xonly_bytes.begin(), xonly_bytes.end(), candidate.begin() + 1);
|
||||
|
||||
for (unsigned char prefix : {0x02, 0x03}) {
|
||||
candidate[0] = prefix;
|
||||
const CPubKey candidate_pubkey(candidate.begin(), candidate.end());
|
||||
if (!candidate_pubkey.IsFullyValid()) continue;
|
||||
|
||||
// Try to get a key from the provider
|
||||
CPubKey found_pubkey;
|
||||
if (provider.GetPubKey(candidate_pubkey.GetID(), found_pubkey) && found_pubkey.GetXOnlyPubKey() == output_key) {
|
||||
if (!provider.GetPubKey(candidate_pubkey.GetID(), found_pubkey)) continue;
|
||||
|
||||
// Compute the tweaked output key for this internal key
|
||||
XOnlyPubKey internal_key = found_pubkey.GetXOnlyPubKey();
|
||||
if (!internal_key.IsFullyValid()) continue;
|
||||
|
||||
auto [tweaked_key, parity] = internal_key.CreatePayToTaprootPubKey(nullptr);
|
||||
|
||||
// Check if tweaked key matches the output key
|
||||
if (tweaked_key == output_key) {
|
||||
pubkey = found_pubkey;
|
||||
return true;
|
||||
}
|
||||
@@ -213,7 +257,9 @@ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator
|
||||
if (!FindTaprootPubKey(provider, vSolutions[0], pubkey)) {
|
||||
return false;
|
||||
}
|
||||
if (!CreateSig(creator, sigdata, provider, sig, pubkey, scriptPubKey, SigVersion::TAPROOT)) return false;
|
||||
// For Taproot key-path spending, scriptCode should be empty per BIP341
|
||||
CScript empty_script;
|
||||
if (!CreateSig(creator, sigdata, provider, sig, pubkey, empty_script, SigVersion::TAPROOT)) return false;
|
||||
ret.push_back(std::move(sig));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -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 GetTaprootInternalKey(const XOnlyPubKey& output_key, CPubKey& internal_key) const { return false; }
|
||||
};
|
||||
|
||||
extern const SigningProvider& DUMMY_SIGNING_PROVIDER;
|
||||
|
||||
@@ -1026,7 +1026,12 @@ bool MemPoolAccept::AcceptSingleTransaction(const CTransactionRef& ptx, ATMPArgs
|
||||
// scripts (ie, other policy checks pass). We perform the inexpensive
|
||||
// checks first and avoid hashing and signature verification unless those
|
||||
// checks pass, to mitigate CPU exhaustion denial-of-service attacks.
|
||||
PrecomputedTransactionData txdata(*ptx);
|
||||
std::vector<CTxOut> spent_outputs;
|
||||
spent_outputs.reserve(ptx->vin.size());
|
||||
for (const auto& txin : ptx->vin) {
|
||||
spent_outputs.emplace_back(m_view.AccessCoin(txin.prevout).out);
|
||||
}
|
||||
PrecomputedTransactionData txdata(*ptx, spent_outputs);
|
||||
|
||||
if (!PolicyScriptChecks(args, workspace, txdata)) return false;
|
||||
|
||||
|
||||
@@ -79,14 +79,35 @@ bool HaveKeys(const std::vector<valtype>& pubkeys, const LegacyScriptPubKeyMan&
|
||||
bool HaveTaprootKey(const valtype& xonly, const LegacyScriptPubKeyMan& keystore)
|
||||
{
|
||||
if (xonly.size() != WITNESS_V1_TAPROOT_SIZE) return false;
|
||||
const XOnlyPubKey output_key(xonly.begin(), xonly.end());
|
||||
if (!output_key.IsFullyValid()) return false;
|
||||
|
||||
// First, try the new method: look up the internal key directly from our mapping
|
||||
CPubKey internal_pubkey;
|
||||
if (keystore.GetTaprootInternalKey(output_key, internal_pubkey)) {
|
||||
// Verify we have the private key for this internal pubkey
|
||||
return keystore.HaveKey(internal_pubkey.GetID());
|
||||
}
|
||||
|
||||
// Fallback: for backwards compatibility, also check if we have a key
|
||||
// whose tweak matches the output key. This handles cases where the
|
||||
// mapping wasn't stored (e.g., older wallet versions).
|
||||
std::array<unsigned char, CPubKey::COMPRESSED_SIZE> candidate;
|
||||
std::copy(xonly.begin(), xonly.end(), candidate.begin() + 1);
|
||||
candidate[0] = 0x02;
|
||||
const CKeyID even_key(Hash160(candidate.begin(), candidate.end()));
|
||||
if (keystore.HaveKey(even_key)) return true;
|
||||
candidate[0] = 0x03;
|
||||
const CKeyID odd_key(Hash160(candidate.begin(), candidate.end()));
|
||||
return keystore.HaveKey(odd_key);
|
||||
for (unsigned char prefix : {0x02, 0x03}) {
|
||||
candidate[0] = prefix;
|
||||
const CKeyID key_id(Hash160(candidate.begin(), candidate.end()));
|
||||
CKey key;
|
||||
if (keystore.GetKey(key_id, key)) {
|
||||
// Found a key, check if its tweaked version matches output_key
|
||||
XOnlyPubKey internal_key = key.GetPubKey().GetXOnlyPubKey();
|
||||
auto [tweaked, parity] = internal_key.CreatePayToTaprootPubKey(nullptr);
|
||||
if (tweaked == output_key) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//! Recursively solve script and return spendable/watchonly/invalid status.
|
||||
@@ -1333,6 +1354,17 @@ void LegacyScriptPubKeyMan::LearnRelatedScripts(const CPubKey& key, OutputType t
|
||||
CScript taproot_prog = GetScriptForDestination(taproot_dest);
|
||||
if (!taproot_prog.empty()) {
|
||||
AddCScript(taproot_prog);
|
||||
|
||||
// Store the mapping from tweaked output key to internal key
|
||||
// This allows us to find the internal key when signing
|
||||
XOnlyPubKey internal_key = key.GetXOnlyPubKey();
|
||||
auto [output_key, parity] = internal_key.CreatePayToTaprootPubKey(nullptr);
|
||||
if (output_key.IsFullyValid()) {
|
||||
// Convert XOnlyPubKey to uint256 for storage
|
||||
uint256 output_key_hash;
|
||||
memcpy(output_key_hash.begin(), output_key.data(), XOnlyPubKey::SIZE);
|
||||
m_taproot_internal_keys[output_key_hash] = key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1528,3 +1560,19 @@ std::set<CKeyID> LegacyScriptPubKeyMan::GetKeys() const
|
||||
}
|
||||
return set_address;
|
||||
}
|
||||
|
||||
bool LegacyScriptPubKeyMan::GetTaprootInternalKey(const XOnlyPubKey& output_key, CPubKey& internal_key) const
|
||||
{
|
||||
LOCK(cs_KeyStore);
|
||||
|
||||
// Convert XOnlyPubKey to uint256 for lookup
|
||||
uint256 output_key_hash;
|
||||
memcpy(output_key_hash.begin(), output_key.data(), XOnlyPubKey::SIZE);
|
||||
|
||||
auto it = m_taproot_internal_keys.find(output_key_hash);
|
||||
if (it != m_taproot_internal_keys.end()) {
|
||||
internal_key = it->second;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -299,6 +299,10 @@ private:
|
||||
// Tracks keypool indexes to CKeyIDs of keys that have been taken out of the keypool but may be returned to it
|
||||
std::map<int64_t, CKeyID> m_index_to_reserved_key;
|
||||
|
||||
//! Map from tweaked Taproot output key (as XOnlyPubKey bytes stored in uint256) to internal CPubKey
|
||||
//! This allows looking up the internal key needed for signing when given the output key from scriptPubKey
|
||||
std::map<uint256, CPubKey> m_taproot_internal_keys GUARDED_BY(cs_KeyStore);
|
||||
|
||||
//! Fetches a key from the keypool
|
||||
bool GetKeyFromPool(CPubKey &key, const OutputType type, bool internal = false);
|
||||
|
||||
@@ -415,6 +419,9 @@ public:
|
||||
bool AddCScript(const CScript& redeemScript) override;
|
||||
bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override;
|
||||
|
||||
//! Get the internal key for a Taproot output key (returns false if not found)
|
||||
bool GetTaprootInternalKey(const XOnlyPubKey& output_key, CPubKey& internal_key) const;
|
||||
|
||||
//! Load a keypool entry
|
||||
void LoadKeyPool(int64_t nIndex, const CKeyPool &keypool);
|
||||
bool NewKeyPool();
|
||||
@@ -477,6 +484,7 @@ public:
|
||||
bool GetKey(const CKeyID &address, CKey& key) const override { return false; }
|
||||
bool HaveKey(const CKeyID &address) const override { return false; }
|
||||
bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override { return m_spk_man.GetKeyOrigin(keyid, info); }
|
||||
bool GetTaprootInternalKey(const XOnlyPubKey& output_key, CPubKey& internal_key) const override { return m_spk_man.GetTaprootInternalKey(output_key, internal_key); }
|
||||
};
|
||||
|
||||
#endif // PALLADIUM_WALLET_SCRIPTPUBKEYMAN_H
|
||||
|
||||
@@ -18,7 +18,7 @@ BOOST_AUTO_TEST_CASE(getwalletenv_file)
|
||||
std::string test_name = "test_name.dat";
|
||||
const fs::path datadir = GetDataDir();
|
||||
fs::path file_path = datadir / test_name;
|
||||
std::ofstream f(file_path.BOOST_FILESYSTEM_C_STR);
|
||||
std::ofstream f(file_path.string().c_str());
|
||||
f.close();
|
||||
|
||||
std::string filename;
|
||||
|
||||
@@ -30,7 +30,7 @@ InitWalletDirTestingSetup::InitWalletDirTestingSetup(const std::string& chainNam
|
||||
fs::create_directories(m_walletdir_path_cases["default"]);
|
||||
fs::create_directories(m_walletdir_path_cases["custom"]);
|
||||
fs::create_directories(m_walletdir_path_cases["relative"]);
|
||||
std::ofstream f(m_walletdir_path_cases["file"].BOOST_FILESYSTEM_C_STR);
|
||||
std::ofstream f(m_walletdir_path_cases["file"].string().c_str());
|
||||
f.close();
|
||||
}
|
||||
|
||||
|
||||
66
test/functional/feature_taproot.py
Executable file
66
test/functional/feature_taproot.py
Executable file
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python3
|
||||
from test_framework.test_framework import PalladiumTestFramework
|
||||
from test_framework.util import assert_equal
|
||||
|
||||
class TaprootReproTest(PalladiumTestFramework):
|
||||
def set_test_params(self):
|
||||
self.num_nodes = 1
|
||||
self.setup_clean_chain = True
|
||||
self.extra_args = [[]] # standard args
|
||||
|
||||
def skip_test_if_missing_module(self):
|
||||
self.skip_if_no_wallet()
|
||||
|
||||
def run_test(self):
|
||||
node = self.nodes[0]
|
||||
|
||||
self.log.info("Create wallet")
|
||||
node.createwallet("tr_test")
|
||||
wallet = node.get_wallet_rpc("tr_test")
|
||||
|
||||
self.log.info("Generate blocks to get coins")
|
||||
mining_addr = wallet.getnewaddress()
|
||||
node.generatetoaddress(125, mining_addr)
|
||||
|
||||
self.log.info(f"Wallet Info: {wallet.getwalletinfo()}")
|
||||
self.log.info(f"Unspent: {wallet.listunspent()}")
|
||||
self.log.info(f"Address Info: {wallet.getaddressinfo(mining_addr)}")
|
||||
|
||||
balance_start = wallet.getbalance()
|
||||
self.log.info(f"Balance: {balance_start}")
|
||||
|
||||
self.log.info("Get new P2TR address (bech32m)")
|
||||
tr_addr = wallet.getnewaddress("", "bech32m")
|
||||
self.log.info(f"P2TR Address: {tr_addr}")
|
||||
|
||||
self.log.info("Send funds TO P2TR address")
|
||||
txid_to = wallet.sendtoaddress(tr_addr, 1.0)
|
||||
self.log.info(f"Sent to P2TR, txid: {txid_to}")
|
||||
|
||||
node.generatetoaddress(1, mining_addr)
|
||||
|
||||
# Check that the wallet sees the funds
|
||||
unspent = wallet.listunspent(0, 999999, [tr_addr])
|
||||
assert_equal(len(unspent), 1)
|
||||
assert_equal(unspent[0]['amount'], 1.0)
|
||||
self.log.info("Funds confirmed in P2TR address")
|
||||
|
||||
self.log.info("Attempt to spend FROM P2TR address")
|
||||
dest_addr = wallet.getnewaddress("", "bech32")
|
||||
|
||||
try:
|
||||
txid_from = wallet.sendtoaddress(dest_addr, 0.5)
|
||||
self.log.info(f"Spent from P2TR, txid: {txid_from}")
|
||||
|
||||
node.generatetoaddress(1, mining_addr)
|
||||
|
||||
# Verify transaction is confirmed
|
||||
tx = wallet.gettransaction(txid_from)
|
||||
assert_equal(tx['confirmations'], 1)
|
||||
self.log.info("P2TR spend confirmed success!")
|
||||
except Exception as e:
|
||||
self.log.error(f"Failed to spend from P2TR: {e}")
|
||||
raise
|
||||
|
||||
if __name__ == '__main__':
|
||||
TaprootReproTest().main()
|
||||
0
test/functional/rpc_createmultisig.py
Normal file → Executable file
0
test/functional/rpc_createmultisig.py
Normal file → Executable file
0
test/functional/rpc_psbt.py
Normal file → Executable file
0
test/functional/rpc_psbt.py
Normal file → Executable file
0
test/functional/test_runner.py
Normal file → Executable file
0
test/functional/test_runner.py
Normal file → Executable file
0
test/functional/wallet_address_types.py
Normal file → Executable file
0
test/functional/wallet_address_types.py
Normal file → Executable file
0
test/functional/wallet_basic.py
Normal file → Executable file
0
test/functional/wallet_basic.py
Normal file → Executable file
Reference in New Issue
Block a user