diff --git a/src/psbt.cpp b/src/psbt.cpp index c1c8a385cc..36fec74bc9 100644 --- a/src/psbt.cpp +++ b/src/psbt.cpp @@ -113,6 +113,24 @@ void PSBTInput::FillSignatureData(SignatureData& sigdata) const for (const auto& key_pair : hd_keypaths) { sigdata.misc_pubkeys.emplace(key_pair.first.GetID(), key_pair); } + if (!m_tap_key_sig.empty()) { + sigdata.taproot_key_path_sig = m_tap_key_sig; + } + for (const auto& [pubkey_leaf, sig] : m_tap_script_sigs) { + sigdata.taproot_script_sigs.emplace(pubkey_leaf, sig); + } + if (!m_tap_internal_key.IsNull()) { + sigdata.tr_spenddata.internal_key = m_tap_internal_key; + } + if (!m_tap_merkle_root.IsNull()) { + sigdata.tr_spenddata.merkle_root = m_tap_merkle_root; + } + for (const auto& [leaf_script, control_block] : m_tap_scripts) { + sigdata.tr_spenddata.scripts.emplace(leaf_script, control_block); + } + for (const auto& [pubkey, leaf_origin] : m_tap_bip32_paths) { + sigdata.taproot_misc_pubkeys.emplace(pubkey, leaf_origin); + } } void PSBTInput::FromSignatureData(const SignatureData& sigdata) @@ -142,13 +160,30 @@ void PSBTInput::FromSignatureData(const SignatureData& sigdata) for (const auto& entry : sigdata.misc_pubkeys) { hd_keypaths.emplace(entry.second); } + if (!sigdata.taproot_key_path_sig.empty()) { + m_tap_key_sig = sigdata.taproot_key_path_sig; + } + for (const auto& [pubkey_leaf, sig] : sigdata.taproot_script_sigs) { + m_tap_script_sigs.emplace(pubkey_leaf, sig); + } + if (!sigdata.tr_spenddata.internal_key.IsNull()) { + m_tap_internal_key = sigdata.tr_spenddata.internal_key; + } + if (!sigdata.tr_spenddata.merkle_root.IsNull()) { + m_tap_merkle_root = sigdata.tr_spenddata.merkle_root; + } + for (const auto& [leaf_script, control_block] : sigdata.tr_spenddata.scripts) { + m_tap_scripts.emplace(leaf_script, control_block); + } + for (const auto& [pubkey, leaf_origin] : sigdata.taproot_misc_pubkeys) { + m_tap_bip32_paths.emplace(pubkey, leaf_origin); + } } void PSBTInput::Merge(const PSBTInput& input) { if (!non_witness_utxo && input.non_witness_utxo) non_witness_utxo = input.non_witness_utxo; if (witness_utxo.IsNull() && !input.witness_utxo.IsNull()) { - // TODO: For segwit v1, we will want to clear out the non-witness utxo when setting a witness one. For v0 and non-segwit, this is not safe witness_utxo = input.witness_utxo; } @@ -159,11 +194,17 @@ void PSBTInput::Merge(const PSBTInput& input) hash256_preimages.insert(input.hash256_preimages.begin(), input.hash256_preimages.end()); hd_keypaths.insert(input.hd_keypaths.begin(), input.hd_keypaths.end()); unknown.insert(input.unknown.begin(), input.unknown.end()); + m_tap_script_sigs.insert(input.m_tap_script_sigs.begin(), input.m_tap_script_sigs.end()); + m_tap_scripts.insert(input.m_tap_scripts.begin(), input.m_tap_scripts.end()); + m_tap_bip32_paths.insert(input.m_tap_bip32_paths.begin(), input.m_tap_bip32_paths.end()); if (redeem_script.empty() && !input.redeem_script.empty()) redeem_script = input.redeem_script; if (witness_script.empty() && !input.witness_script.empty()) witness_script = input.witness_script; if (final_script_sig.empty() && !input.final_script_sig.empty()) final_script_sig = input.final_script_sig; if (final_script_witness.IsNull() && !input.final_script_witness.IsNull()) final_script_witness = input.final_script_witness; + if (m_tap_key_sig.empty() && !input.m_tap_key_sig.empty()) m_tap_key_sig = input.m_tap_key_sig; + if (m_tap_internal_key.IsNull() && !input.m_tap_internal_key.IsNull()) m_tap_internal_key = input.m_tap_internal_key; + if (m_tap_merkle_root.IsNull() && !input.m_tap_merkle_root.IsNull()) m_tap_merkle_root = input.m_tap_merkle_root; } void PSBTOutput::FillSignatureData(SignatureData& sigdata) const @@ -177,6 +218,15 @@ void PSBTOutput::FillSignatureData(SignatureData& sigdata) const for (const auto& key_pair : hd_keypaths) { sigdata.misc_pubkeys.emplace(key_pair.first.GetID(), key_pair); } + if (m_tap_tree.has_value() && m_tap_internal_key.IsFullyValid()) { + TaprootSpendData spenddata = m_tap_tree->GetSpendData(); + + sigdata.tr_spenddata.internal_key = m_tap_internal_key; + sigdata.tr_spenddata.Merge(spenddata); + } + for (const auto& [pubkey, leaf_origin] : m_tap_bip32_paths) { + sigdata.taproot_misc_pubkeys.emplace(pubkey, leaf_origin); + } } void PSBTOutput::FromSignatureData(const SignatureData& sigdata) @@ -190,6 +240,15 @@ void PSBTOutput::FromSignatureData(const SignatureData& sigdata) for (const auto& entry : sigdata.misc_pubkeys) { hd_keypaths.emplace(entry.second); } + if (!sigdata.tr_spenddata.internal_key.IsNull()) { + m_tap_internal_key = sigdata.tr_spenddata.internal_key; + } + if (sigdata.tr_builder.has_value()) { + m_tap_tree = sigdata.tr_builder; + } + for (const auto& [pubkey, leaf_origin] : sigdata.taproot_misc_pubkeys) { + m_tap_bip32_paths.emplace(pubkey, leaf_origin); + } } bool PSBTOutput::IsNull() const @@ -201,9 +260,12 @@ void PSBTOutput::Merge(const PSBTOutput& output) { hd_keypaths.insert(output.hd_keypaths.begin(), output.hd_keypaths.end()); unknown.insert(output.unknown.begin(), output.unknown.end()); + m_tap_bip32_paths.insert(output.m_tap_bip32_paths.begin(), output.m_tap_bip32_paths.end()); if (redeem_script.empty() && !output.redeem_script.empty()) redeem_script = output.redeem_script; if (witness_script.empty() && !output.witness_script.empty()) witness_script = output.witness_script; + if (m_tap_internal_key.IsNull() && !output.m_tap_internal_key.IsNull()) m_tap_internal_key = output.m_tap_internal_key; + if (m_tap_tree.has_value() && !output.m_tap_tree.has_value()) m_tap_tree = output.m_tap_tree; } bool PSBTInputSigned(const PSBTInput& input) { @@ -313,10 +375,11 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& input.FromSignatureData(sigdata); // If we have a witness signature, put a witness UTXO. - // TODO: For segwit v1, we should remove the non_witness_utxo if (sigdata.witness) { input.witness_utxo = utxo; - // input.non_witness_utxo = nullptr; + // We can remove the non_witness_utxo if and only if there are no non-segwit or segwit v0 + // inputs in this transaction. Since this requires inspecting the entire transaction, this + // is something for the caller to deal with (i.e. FillPSBT). } // Fill in the missing info diff --git a/src/psbt.h b/src/psbt.h index 4a6d41076f..a143a99988 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -40,12 +40,21 @@ static constexpr uint8_t PSBT_IN_RIPEMD160 = 0x0A; static constexpr uint8_t PSBT_IN_SHA256 = 0x0B; static constexpr uint8_t PSBT_IN_HASH160 = 0x0C; static constexpr uint8_t PSBT_IN_HASH256 = 0x0D; +static constexpr uint8_t PSBT_IN_TAP_KEY_SIG = 0x13; +static constexpr uint8_t PSBT_IN_TAP_SCRIPT_SIG = 0x14; +static constexpr uint8_t PSBT_IN_TAP_LEAF_SCRIPT = 0x15; +static constexpr uint8_t PSBT_IN_TAP_BIP32_DERIVATION = 0x16; +static constexpr uint8_t PSBT_IN_TAP_INTERNAL_KEY = 0x17; +static constexpr uint8_t PSBT_IN_TAP_MERKLE_ROOT = 0x18; static constexpr uint8_t PSBT_IN_PROPRIETARY = 0xFC; // Output types static constexpr uint8_t PSBT_OUT_REDEEMSCRIPT = 0x00; static constexpr uint8_t PSBT_OUT_WITNESSSCRIPT = 0x01; static constexpr uint8_t PSBT_OUT_BIP32_DERIVATION = 0x02; +static constexpr uint8_t PSBT_OUT_TAP_INTERNAL_KEY = 0x05; +static constexpr uint8_t PSBT_OUT_TAP_TREE = 0x06; +static constexpr uint8_t PSBT_OUT_TAP_BIP32_DERIVATION = 0x07; static constexpr uint8_t PSBT_OUT_PROPRIETARY = 0xFC; // The separator is 0x00. Reading this in means that the unserializer can interpret it @@ -97,22 +106,30 @@ void UnserializeFromVector(Stream& s, X&... args) } } -// Deserialize an individual HD keypath to a stream +// Deserialize bytes of given length from the stream as a KeyOriginInfo template -void DeserializeHDKeypath(Stream& s, KeyOriginInfo& hd_keypath) +KeyOriginInfo DeserializeKeyOrigin(Stream& s, uint64_t length) { // Read in key path - uint64_t value_len = ReadCompactSize(s); - if (value_len % 4 || value_len == 0) { + if (length % 4 || length == 0) { throw std::ios_base::failure("Invalid length for HD key path"); } + KeyOriginInfo hd_keypath; s >> hd_keypath.fingerprint; - for (unsigned int i = 4; i < value_len; i += sizeof(uint32_t)) { + for (unsigned int i = 4; i < length; i += sizeof(uint32_t)) { uint32_t index; s >> index; hd_keypath.path.push_back(index); } + return hd_keypath; +} + +// Deserialize a length prefixed KeyOriginInfo from a stream +template +void DeserializeHDKeypath(Stream& s, KeyOriginInfo& hd_keypath) +{ + hd_keypath = DeserializeKeyOrigin(s, ReadCompactSize(s)); } // Deserialize HD keypaths into a map @@ -139,17 +156,24 @@ void DeserializeHDKeypaths(Stream& s, const std::vector& key, std hd_keypaths.emplace(pubkey, std::move(keypath)); } -// Serialize an individual HD keypath to a stream +// Serialize a KeyOriginInfo to a stream template -void SerializeHDKeypath(Stream& s, KeyOriginInfo hd_keypath) +void SerializeKeyOrigin(Stream& s, KeyOriginInfo hd_keypath) { - WriteCompactSize(s, (hd_keypath.path.size() + 1) * sizeof(uint32_t)); s << hd_keypath.fingerprint; for (const auto& path : hd_keypath.path) { s << path; } } +// Serialize a length prefixed KeyOriginInfo to a stream +template +void SerializeHDKeypath(Stream& s, KeyOriginInfo hd_keypath) +{ + WriteCompactSize(s, (hd_keypath.path.size() + 1) * sizeof(uint32_t)); + SerializeKeyOrigin(s, hd_keypath); +} + // Serialize HD keypaths to a stream from a map template void SerializeHDKeypaths(Stream& s, const std::map& hd_keypaths, CompactSizeWriter type) @@ -178,6 +202,15 @@ struct PSBTInput std::map> sha256_preimages; std::map> hash160_preimages; std::map> hash256_preimages; + + // Taproot fields + std::vector m_tap_key_sig; + std::map, std::vector> m_tap_script_sigs; + std::map, std::set, ShortestVectorFirstComparator>> m_tap_scripts; + std::map, KeyOriginInfo>> m_tap_bip32_paths; + XOnlyPubKey m_tap_internal_key; + uint256 m_tap_merkle_root; + std::map, std::vector> unknown; std::set m_proprietary; std::optional sighash_type; @@ -252,6 +285,53 @@ struct PSBTInput SerializeToVector(s, CompactSizeWriter(PSBT_IN_HASH256), Span{hash}); s << preimage; } + + // Write taproot key sig + if (!m_tap_key_sig.empty()) { + SerializeToVector(s, PSBT_IN_TAP_KEY_SIG); + s << m_tap_key_sig; + } + + // Write taproot script sigs + for (const auto& [pubkey_leaf, sig] : m_tap_script_sigs) { + const auto& [xonly, leaf_hash] = pubkey_leaf; + SerializeToVector(s, PSBT_IN_TAP_SCRIPT_SIG, xonly, leaf_hash); + s << sig; + } + + // Write taproot leaf scripts + for (const auto& [leaf, control_blocks] : m_tap_scripts) { + const auto& [script, leaf_ver] = leaf; + for (const auto& control_block : control_blocks) { + SerializeToVector(s, PSBT_IN_TAP_LEAF_SCRIPT, Span{control_block}); + std::vector value_v(script.begin(), script.end()); + value_v.push_back((uint8_t)leaf_ver); + s << value_v; + } + } + + // Write taproot bip32 keypaths + for (const auto& [xonly, leaf_origin] : m_tap_bip32_paths) { + const auto& [leaf_hashes, origin] = leaf_origin; + SerializeToVector(s, PSBT_IN_TAP_BIP32_DERIVATION, xonly); + std::vector value; + CVectorWriter s_value(s.GetType(), s.GetVersion(), value, 0); + s_value << leaf_hashes; + SerializeKeyOrigin(s_value, origin); + s << value; + } + + // Write taproot internal key + if (!m_tap_internal_key.IsNull()) { + SerializeToVector(s, PSBT_IN_TAP_INTERNAL_KEY); + s << ToByteVector(m_tap_internal_key); + } + + // Write taproot merkle root + if (!m_tap_merkle_root.IsNull()) { + SerializeToVector(s, PSBT_IN_TAP_MERKLE_ROOT); + SerializeToVector(s, m_tap_merkle_root); + } } // Write script sig @@ -488,6 +568,103 @@ struct PSBTInput hash256_preimages.emplace(hash, std::move(preimage)); break; } + case PSBT_IN_TAP_KEY_SIG: + { + if (!key_lookup.emplace(key).second) { + throw std::ios_base::failure("Duplicate Key, input Taproot key signature already provided"); + } else if (key.size() != 1) { + throw std::ios_base::failure("Input Taproot key signature key is more than one byte type"); + } + s >> m_tap_key_sig; + if (m_tap_key_sig.size() < 64) { + throw std::ios_base::failure("Input Taproot key path signature is shorter than 64 bytes"); + } else if (m_tap_key_sig.size() > 65) { + throw std::ios_base::failure("Input Taproot key path signature is longer than 65 bytes"); + } + break; + } + case PSBT_IN_TAP_SCRIPT_SIG: + { + if (!key_lookup.emplace(key).second) { + throw std::ios_base::failure("Duplicate Key, input Taproot script signature already provided"); + } else if (key.size() != 65) { + throw std::ios_base::failure("Input Taproot script signature key is not 65 bytes"); + } + SpanReader s_key(s.GetType(), s.GetVersion(), Span{key}.subspan(1)); + XOnlyPubKey xonly; + uint256 hash; + s_key >> xonly; + s_key >> hash; + std::vector sig; + s >> sig; + if (sig.size() < 64) { + throw std::ios_base::failure("Input Taproot script path signature is shorter than 64 bytes"); + } else if (sig.size() > 65) { + throw std::ios_base::failure("Input Taproot script path signature is longer than 65 bytes"); + } + m_tap_script_sigs.emplace(std::make_pair(xonly, hash), sig); + break; + } + case PSBT_IN_TAP_LEAF_SCRIPT: + { + if (!key_lookup.emplace(key).second) { + throw std::ios_base::failure("Duplicate Key, input Taproot leaf script already provided"); + } else if (key.size() < 34) { + throw std::ios_base::failure("Taproot leaf script key is not at least 34 bytes"); + } else if ((key.size() - 2) % 32 != 0) { + throw std::ios_base::failure("Input Taproot leaf script key's control block size is not valid"); + } + std::vector script_v; + s >> script_v; + if (script_v.empty()) { + throw std::ios_base::failure("Input Taproot leaf script must be at least 1 byte"); + } + uint8_t leaf_ver = script_v.back(); + script_v.pop_back(); + const auto leaf_script = std::make_pair(CScript(script_v.begin(), script_v.end()), (int)leaf_ver); + m_tap_scripts[leaf_script].insert(std::vector(key.begin() + 1, key.end())); + break; + } + case PSBT_IN_TAP_BIP32_DERIVATION: + { + if (!key_lookup.emplace(key).second) { + throw std::ios_base::failure("Duplicate Key, input Taproot BIP32 keypath already provided"); + } else if (key.size() != 33) { + throw std::ios_base::failure("Input Taproot BIP32 keypath key is not at 33 bytes"); + } + SpanReader s_key(s.GetType(), s.GetVersion(), Span{key}.subspan(1)); + XOnlyPubKey xonly; + s_key >> xonly; + std::set leaf_hashes; + uint64_t value_len = ReadCompactSize(s); + size_t before_hashes = s.size(); + s >> leaf_hashes; + size_t after_hashes = s.size(); + size_t hashes_len = before_hashes - after_hashes; + size_t origin_len = value_len - hashes_len; + m_tap_bip32_paths.emplace(xonly, std::make_pair(leaf_hashes, DeserializeKeyOrigin(s, origin_len))); + break; + } + case PSBT_IN_TAP_INTERNAL_KEY: + { + if (!key_lookup.emplace(key).second) { + throw std::ios_base::failure("Duplicate Key, input Taproot internal key already provided"); + } else if (key.size() != 1) { + throw std::ios_base::failure("Input Taproot internal key key is more than one byte type"); + } + UnserializeFromVector(s, m_tap_internal_key); + break; + } + case PSBT_IN_TAP_MERKLE_ROOT: + { + if (!key_lookup.emplace(key).second) { + throw std::ios_base::failure("Duplicate Key, input Taproot merkle root already provided"); + } else if (key.size() != 1) { + throw std::ios_base::failure("Input Taproot merkle root key is more than one byte type"); + } + UnserializeFromVector(s, m_tap_merkle_root); + break; + } case PSBT_IN_PROPRIETARY: { PSBTProprietary this_prop; @@ -532,6 +709,9 @@ struct PSBTOutput CScript redeem_script; CScript witness_script; std::map hd_keypaths; + XOnlyPubKey m_tap_internal_key; + std::optional m_tap_tree; + std::map, KeyOriginInfo>> m_tap_bip32_paths; std::map, std::vector> unknown; std::set m_proprietary; @@ -564,6 +744,40 @@ struct PSBTOutput s << entry.value; } + // Write taproot internal key + if (!m_tap_internal_key.IsNull()) { + SerializeToVector(s, PSBT_OUT_TAP_INTERNAL_KEY); + s << ToByteVector(m_tap_internal_key); + } + + // Write taproot tree + if (m_tap_tree.has_value()) { + SerializeToVector(s, PSBT_OUT_TAP_TREE); + std::vector value; + CVectorWriter s_value(s.GetType(), s.GetVersion(), value, 0); + const auto& tuples = m_tap_tree->GetTreeTuples(); + for (const auto& tuple : tuples) { + uint8_t depth = std::get<0>(tuple); + uint8_t leaf_ver = std::get<1>(tuple); + CScript script = std::get<2>(tuple); + s_value << depth; + s_value << leaf_ver; + s_value << script; + } + s << value; + } + + // Write taproot bip32 keypaths + for (const auto& [xonly, leaf] : m_tap_bip32_paths) { + const auto& [leaf_hashes, origin] = leaf; + SerializeToVector(s, PSBT_OUT_TAP_BIP32_DERIVATION, xonly); + std::vector value; + CVectorWriter s_value(s.GetType(), s.GetVersion(), value, 0); + s_value << leaf_hashes; + SerializeKeyOrigin(s_value, origin); + s << value; + } + // Write unknown things for (auto& entry : unknown) { s << entry.first; @@ -624,6 +838,59 @@ struct PSBTOutput DeserializeHDKeypaths(s, key, hd_keypaths); break; } + case PSBT_OUT_TAP_INTERNAL_KEY: + { + if (!key_lookup.emplace(key).second) { + throw std::ios_base::failure("Duplicate Key, output Taproot internal key already provided"); + } else if (key.size() != 1) { + throw std::ios_base::failure("Output Taproot internal key key is more than one byte type"); + } + UnserializeFromVector(s, m_tap_internal_key); + break; + } + case PSBT_OUT_TAP_TREE: + { + if (!key_lookup.emplace(key).second) { + throw std::ios_base::failure("Duplicate Key, output Taproot tree already provided"); + } else if (key.size() != 1) { + throw std::ios_base::failure("Output Taproot tree key is more than one byte type"); + } + m_tap_tree.emplace(); + std::vector tree_v; + s >> tree_v; + SpanReader s_tree(s.GetType(), s.GetVersion(), tree_v); + while (!s_tree.empty()) { + uint8_t depth; + uint8_t leaf_ver; + CScript script; + s_tree >> depth; + s_tree >> leaf_ver; + s_tree >> script; + m_tap_tree->Add((int)depth, script, (int)leaf_ver, true /* track */); + } + if (!m_tap_tree->IsComplete()) { + throw std::ios_base::failure("Output Taproot tree is malformed"); + } + break; + } + case PSBT_OUT_TAP_BIP32_DERIVATION: + { + if (!key_lookup.emplace(key).second) { + throw std::ios_base::failure("Duplicate Key, output Taproot BIP32 keypath already provided"); + } else if (key.size() != 33) { + throw std::ios_base::failure("Output Taproot BIP32 keypath key is not at 33 bytes"); + } + XOnlyPubKey xonly(uint256({key.begin() + 1, key.begin() + 33})); + std::set leaf_hashes; + uint64_t value_len = ReadCompactSize(s); + size_t before_hashes = s.size(); + s >> leaf_hashes; + size_t after_hashes = s.size(); + size_t hashes_len = before_hashes - after_hashes; + size_t origin_len = value_len - hashes_len; + m_tap_bip32_paths.emplace(xonly, std::make_pair(leaf_hashes, DeserializeKeyOrigin(s, origin_len))); + break; + } case PSBT_OUT_PROPRIETARY: { PSBTProprietary this_prop; @@ -652,6 +919,11 @@ struct PSBTOutput } } + // Finalize m_tap_tree so that all of the computed things are computed + if (m_tap_tree.has_value() && m_tap_tree->IsComplete() && m_tap_internal_key.IsFullyValid()) { + m_tap_tree->Finalize(m_tap_internal_key); + } + if (!found_sep) { throw std::ios_base::failure("Separator is missing at the end of an output map"); } diff --git a/src/pubkey.h b/src/pubkey.h index dfe06f834c..463efe1b00 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -286,6 +286,9 @@ public: bool operator==(const XOnlyPubKey& other) const { return m_keydata == other.m_keydata; } bool operator!=(const XOnlyPubKey& other) const { return m_keydata != other.m_keydata; } bool operator<(const XOnlyPubKey& other) const { return m_keydata < other.m_keydata; } + + //! Implement serialization without length prefixes since it is a fixed length + SERIALIZE_METHODS(XOnlyPubKey, obj) { READWRITE(obj.m_keydata); } }; struct CExtPubKey { diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index b9b8c36bb3..792a1e13b0 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -790,6 +790,43 @@ static RPCHelpMan decodepsbt() { {RPCResult::Type::STR, "hash", "The hash and preimage that corresponds to it."}, }}, + {RPCResult::Type::STR_HEX, "taproot_key_path_sig", /*optional=*/ true, "hex-encoded signature for the Taproot key path spend"}, + {RPCResult::Type::ARR, "taproot_script_path_sigs", /*optional=*/ true, "", + { + {RPCResult::Type::OBJ, "signature", /*optional=*/ true, "The signature for the pubkey and leaf hash combination", + { + {RPCResult::Type::STR, "pubkey", "The x-only pubkey for this signature"}, + {RPCResult::Type::STR, "leaf_hash", "The leaf hash for this signature"}, + {RPCResult::Type::STR, "sig", "The signature itself"}, + }}, + }}, + {RPCResult::Type::ARR, "taproot_scripts", /*optional=*/ true, "", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR_HEX, "script", "A leaf script"}, + {RPCResult::Type::NUM, "leaf_ver", "The version number for the leaf script"}, + {RPCResult::Type::ARR, "control_blocks", "The control blocks for this script", + { + {RPCResult::Type::STR_HEX, "control_block", "A hex-encoded control block for this script"}, + }}, + }}, + }}, + {RPCResult::Type::ARR, "taproot_bip32_derivs", /*optional=*/ true, "", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR, "pubkey", "The x-only public key this path corresponds to"}, + {RPCResult::Type::STR, "master_fingerprint", "The fingerprint of the master key"}, + {RPCResult::Type::STR, "path", "The path"}, + {RPCResult::Type::ARR, "leaf_hashes", "The hashes of the leaves this pubkey appears in", + { + {RPCResult::Type::STR_HEX, "hash", "The hash of a leaf this pubkey appears in"}, + }}, + }}, + }}, + {RPCResult::Type::STR_HEX, "taproot_internal_key", /*optional=*/ true, "The hex-encoded Taproot x-only internal key"}, + {RPCResult::Type::STR_HEX, "taproot_merkle_root", /*optional=*/ true, "The hex-encoded Taproot merkle root"}, {RPCResult::Type::OBJ_DYN, "unknown", /*optional=*/ true, "The unknown input fields", { {RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"}, @@ -831,7 +868,30 @@ static RPCHelpMan decodepsbt() {RPCResult::Type::STR, "path", "The path"}, }}, }}, - {RPCResult::Type::OBJ_DYN, "unknown", /*optional=*/true, "The unknown global fields", + {RPCResult::Type::STR_HEX, "taproot_internal_key", /*optional=*/ true, "The hex-encoded Taproot x-only internal key"}, + {RPCResult::Type::ARR, "taproot_tree", /*optional=*/ true, "The tuples that make up the Taproot tree, in depth first search order", + { + {RPCResult::Type::OBJ, "tuple", /*optional=*/ true, "A single leaf script in the taproot tree", + { + {RPCResult::Type::NUM, "depth", "The depth of this element in the tree"}, + {RPCResult::Type::NUM, "leaf_ver", "The version of this leaf"}, + {RPCResult::Type::STR, "script", "The hex-encoded script itself"}, + }}, + }}, + {RPCResult::Type::ARR, "taproot_bip32_derivs", /*optional=*/ true, "", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR, "pubkey", "The x-only public key this path corresponds to"}, + {RPCResult::Type::STR, "master_fingerprint", "The fingerprint of the master key"}, + {RPCResult::Type::STR, "path", "The path"}, + {RPCResult::Type::ARR, "leaf_hashes", "The hashes of the leaves this pubkey appears in", + { + {RPCResult::Type::STR_HEX, "hash", "The hash of a leaf this pubkey appears in"}, + }}, + }}, + }}, + {RPCResult::Type::OBJ_DYN, "unknown", /*optional=*/true, "The unknown output fields", { {RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"}, }}, @@ -1045,6 +1105,72 @@ static RPCHelpMan decodepsbt() in.pushKV("hash256_preimages", hash256_preimages); } + // Taproot key path signature + if (!input.m_tap_key_sig.empty()) { + in.pushKV("taproot_key_path_sig", HexStr(input.m_tap_key_sig)); + } + + // Taproot script path signatures + if (!input.m_tap_script_sigs.empty()) { + UniValue script_sigs(UniValue::VARR); + for (const auto& [pubkey_leaf, sig] : input.m_tap_script_sigs) { + const auto& [xonly, leaf_hash] = pubkey_leaf; + UniValue sigobj(UniValue::VOBJ); + sigobj.pushKV("pubkey", HexStr(xonly)); + sigobj.pushKV("leaf_hash", HexStr(leaf_hash)); + sigobj.pushKV("sig", HexStr(sig)); + script_sigs.push_back(sigobj); + } + in.pushKV("taproot_script_path_sigs", script_sigs); + } + + // Taproot leaf scripts + if (!input.m_tap_scripts.empty()) { + UniValue tap_scripts(UniValue::VARR); + for (const auto& [leaf, control_blocks] : input.m_tap_scripts) { + const auto& [script, leaf_ver] = leaf; + UniValue script_info(UniValue::VOBJ); + script_info.pushKV("script", HexStr(script)); + script_info.pushKV("leaf_ver", leaf_ver); + UniValue control_blocks_univ(UniValue::VARR); + for (const auto& control_block : control_blocks) { + control_blocks_univ.push_back(HexStr(control_block)); + } + script_info.pushKV("control_blocks", control_blocks_univ); + tap_scripts.push_back(script_info); + } + in.pushKV("taproot_scripts", tap_scripts); + } + + // Taproot bip32 keypaths + if (!input.m_tap_bip32_paths.empty()) { + UniValue keypaths(UniValue::VARR); + for (const auto& [xonly, leaf_origin] : input.m_tap_bip32_paths) { + const auto& [leaf_hashes, origin] = leaf_origin; + UniValue path_obj(UniValue::VOBJ); + path_obj.pushKV("pubkey", HexStr(xonly)); + path_obj.pushKV("master_fingerprint", strprintf("%08x", ReadBE32(origin.fingerprint))); + path_obj.pushKV("path", WriteHDKeypath(origin.path)); + UniValue leaf_hashes_arr(UniValue::VARR); + for (const auto& leaf_hash : leaf_hashes) { + leaf_hashes_arr.push_back(HexStr(leaf_hash)); + } + path_obj.pushKV("leaf_hashes", leaf_hashes_arr); + keypaths.push_back(path_obj); + } + in.pushKV("taproot_bip32_derivs", keypaths); + } + + // Taproot internal key + if (!input.m_tap_internal_key.IsNull()) { + in.pushKV("taproot_internal_key", HexStr(input.m_tap_internal_key)); + } + + // Write taproot merkle root + if (!input.m_tap_merkle_root.IsNull()) { + in.pushKV("taproot_merkle_root", HexStr(input.m_tap_merkle_root)); + } + // Proprietary if (!input.m_proprietary.empty()) { UniValue proprietary(UniValue::VARR); @@ -1103,6 +1229,47 @@ static RPCHelpMan decodepsbt() out.pushKV("bip32_derivs", keypaths); } + // Taproot internal key + if (!output.m_tap_internal_key.IsNull()) { + out.pushKV("taproot_internal_key", HexStr(output.m_tap_internal_key)); + } + + // Taproot tree + if (output.m_tap_tree.has_value()) { + UniValue tree(UniValue::VARR); + const auto& tuples = output.m_tap_tree->GetTreeTuples(); + for (const auto& tuple : tuples) { + uint8_t depth = std::get<0>(tuple); + uint8_t leaf_ver = std::get<1>(tuple); + CScript script = std::get<2>(tuple); + UniValue elem(UniValue::VOBJ); + elem.pushKV("depth", (int)depth); + elem.pushKV("leaf_ver", (int)leaf_ver); + elem.pushKV("script", HexStr(script)); + tree.push_back(elem); + } + out.pushKV("taproot_tree", tree); + } + + // Taproot bip32 keypaths + if (!output.m_tap_bip32_paths.empty()) { + UniValue keypaths(UniValue::VARR); + for (const auto& [xonly, leaf_origin] : output.m_tap_bip32_paths) { + const auto& [leaf_hashes, origin] = leaf_origin; + UniValue path_obj(UniValue::VOBJ); + path_obj.pushKV("pubkey", HexStr(xonly)); + path_obj.pushKV("master_fingerprint", strprintf("%08x", ReadBE32(origin.fingerprint))); + path_obj.pushKV("path", WriteHDKeypath(origin.path)); + UniValue leaf_hashes_arr(UniValue::VARR); + for (const auto& leaf_hash : leaf_hashes) { + leaf_hashes_arr.push_back(HexStr(leaf_hash)); + } + path_obj.pushKV("leaf_hashes", leaf_hashes_arr); + keypaths.push_back(path_obj); + } + out.pushKV("taproot_bip32_derivs", keypaths); + } + // Proprietary if (!output.m_proprietary.empty()) { UniValue proprietary(UniValue::VARR); diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index cece0b60ce..ca0170c84b 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -882,7 +882,7 @@ protected: if (!xpk.IsFullyValid()) return {}; builder.Finalize(xpk); WitnessV1Taproot output = builder.GetOutput(); - out.tr_spenddata[output].Merge(builder.GetSpendData()); + out.tr_trees[output] = builder; out.pubkeys.emplace(keys[0].GetID(), keys[0]); return Vector(GetScriptForDestination(output)); } diff --git a/src/script/sign.cpp b/src/script/sign.cpp index 2d569d674a..a3681d26cc 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -150,6 +150,7 @@ static bool CreateTaprootScriptSig(const BaseSignatureCreator& creator, Signatur auto it = sigdata.taproot_script_sigs.find(lookup_key); if (it != sigdata.taproot_script_sigs.end()) { sig_out = it->second; + return true; } if (creator.CreateSchnorrSig(provider, sig_out, pubkey, &leaf_hash, nullptr, sigversion)) { sigdata.taproot_script_sigs[lookup_key] = sig_out; @@ -169,6 +170,17 @@ static bool SignTaprootScript(const SigningProvider& provider, const BaseSignatu // OP_CHECKSIG if (script.size() == 34 && script[33] == OP_CHECKSIG && script[0] == 0x20) { XOnlyPubKey pubkey{Span{script}.subspan(1, 32)}; + + KeyOriginInfo info; + if (provider.GetKeyOriginByXOnly(pubkey, info)) { + auto it = sigdata.taproot_misc_pubkeys.find(pubkey); + if (it == sigdata.taproot_misc_pubkeys.end()) { + sigdata.taproot_misc_pubkeys.emplace(pubkey, std::make_pair(std::set({leaf_hash}), info)); + } else { + it->second.first.insert(leaf_hash); + } + } + std::vector sig; if (CreateTaprootScriptSig(creator, sigdata, provider, sig, pubkey, leaf_hash, sigversion)) { result = Vector(std::move(sig)); @@ -205,17 +217,29 @@ static bool SignTaprootScript(const SigningProvider& provider, const BaseSignatu static bool SignTaproot(const SigningProvider& provider, const BaseSignatureCreator& creator, const WitnessV1Taproot& output, SignatureData& sigdata, std::vector& result) { TaprootSpendData spenddata; + TaprootBuilder builder; // Gather information about this output. if (provider.GetTaprootSpendData(output, spenddata)) { sigdata.tr_spenddata.Merge(spenddata); } + if (provider.GetTaprootBuilder(output, builder)) { + sigdata.tr_builder = builder; + } // Try key path spending. { + KeyOriginInfo info; + if (provider.GetKeyOriginByXOnly(sigdata.tr_spenddata.internal_key, info)) { + auto it = sigdata.taproot_misc_pubkeys.find(sigdata.tr_spenddata.internal_key); + if (it == sigdata.taproot_misc_pubkeys.end()) { + sigdata.taproot_misc_pubkeys.emplace(sigdata.tr_spenddata.internal_key, std::make_pair(std::set(), info)); + } + } + std::vector sig; if (sigdata.taproot_key_path_sig.size() == 0) { - if (creator.CreateSchnorrSig(provider, sig, spenddata.internal_key, nullptr, &spenddata.merkle_root, SigVersion::TAPROOT)) { + if (creator.CreateSchnorrSig(provider, sig, sigdata.tr_spenddata.internal_key, nullptr, &sigdata.tr_spenddata.merkle_root, SigVersion::TAPROOT)) { sigdata.taproot_key_path_sig = sig; } } diff --git a/src/script/sign.h b/src/script/sign.h index 71203d08ec..5e58272154 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -70,10 +70,12 @@ struct SignatureData { 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::optional tr_builder; ///< Taproot tree used to build tr_spenddata. std::map signatures; ///< BIP 174 style partial signatures for the input. May contain all signatures necessary for producing a final scriptSig or scriptWitness. std::map> misc_pubkeys; std::vector taproot_key_path_sig; /// Schnorr signature for key path spending std::map, std::vector> taproot_script_sigs; ///< (Partial) schnorr signatures, indexed by XOnlyPubKey and leaf_hash. + std::map, KeyOriginInfo>> taproot_misc_pubkeys; ///< Miscellaneous Taproot pubkeys involved in this input along with their leaf script hashes and key origin data. Also includes the Taproot internal key (may have no leaf script hashes). std::vector missing_pubkeys; ///< KeyIDs of pubkeys which could not be found std::vector 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 552934e0eb..c624a17628 100644 --- a/src/script/signingprovider.cpp +++ b/src/script/signingprovider.cpp @@ -48,6 +48,10 @@ bool HidingSigningProvider::GetTaprootSpendData(const XOnlyPubKey& output_key, T { return m_provider->GetTaprootSpendData(output_key, spenddata); } +bool HidingSigningProvider::GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const +{ + return m_provider->GetTaprootBuilder(output_key, builder); +} 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); } @@ -61,7 +65,16 @@ bool FlatSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) 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); + TaprootBuilder builder; + if (LookupHelper(tr_trees, output_key, builder)) { + spenddata = builder.GetSpendData(); + return true; + } + return false; +} +bool FlatSigningProvider::GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const +{ + return LookupHelper(tr_trees, output_key, builder); } FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvider& b) @@ -75,10 +88,8 @@ 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); - } + ret.tr_trees = a.tr_trees; + ret.tr_trees.insert(b.tr_trees.begin(), b.tr_trees.end()); return ret; } diff --git a/src/script/signingprovider.h b/src/script/signingprovider.h index f1bded1a8c..792cc903f2 100644 --- a/src/script/signingprovider.h +++ b/src/script/signingprovider.h @@ -25,6 +25,7 @@ public: 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; } + virtual bool GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const { return false; } bool GetKeyByXOnly(const XOnlyPubKey& pubkey, CKey& key) const { @@ -67,6 +68,7 @@ public: 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; + bool GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const override; }; struct FlatSigningProvider final : public SigningProvider @@ -75,13 +77,14 @@ struct FlatSigningProvider final : public SigningProvider std::map pubkeys; std::map> origins; std::map keys; - std::map tr_spenddata; /** Map from output key to spend data. */ + std::map tr_trees; /** Map from output key to Taproot tree (which can then make the TaprootSpendData */ 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; + bool GetTaprootBuilder(const XOnlyPubKey& output_key, TaprootBuilder& builder) const override; }; FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvider& b); diff --git a/src/script/standard.cpp b/src/script/standard.cpp index e25155d3dd..5d80891485 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -485,6 +485,7 @@ WitnessV1Taproot TaprootBuilder::GetOutput() { return WitnessV1Taproot{m_output_ TaprootSpendData TaprootBuilder::GetSpendData() const { assert(IsComplete()); + assert(m_output_key.IsFullyValid()); TaprootSpendData spd; spd.merkle_root = m_branch.size() == 0 ? uint256() : m_branch[0]->hash; spd.internal_key = m_internal_key; @@ -642,3 +643,19 @@ std::optional>> InferTaprootTree(const return ret; } + +std::vector> TaprootBuilder::GetTreeTuples() const +{ + assert(IsComplete()); + std::vector> tuples; + if (m_branch.size()) { + const auto& leaves = m_branch[0]->leaves; + for (const auto& leaf : leaves) { + assert(leaf.merkle_branch.size() <= TAPROOT_CONTROL_MAX_NODE_COUNT); + uint8_t depth = (uint8_t)leaf.merkle_branch.size(); + uint8_t leaf_ver = (uint8_t)leaf.leaf_version; + tuples.push_back(std::make_tuple(depth, leaf_ver, leaf.script)); + } + } + return tuples; +} diff --git a/src/script/standard.h b/src/script/standard.h index 6a15ba4e3d..448fdff010 100644 --- a/src/script/standard.h +++ b/src/script/standard.h @@ -322,6 +322,8 @@ public: static bool ValidDepths(const std::vector& depths); /** Compute spending data (after Finalize()). */ TaprootSpendData GetSpendData() const; + /** Returns a vector of tuples representing the depth, leaf version, and script */ + std::vector> GetTreeTuples() const; }; /** Given a TaprootSpendData and the output key, reconstruct its script tree. diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 8633e7c62c..1fec82a485 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -2180,6 +2180,19 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& *keys = Merge(*keys, *pk_keys); } } + for (const auto& pk_pair : input.m_tap_bip32_paths) { + const XOnlyPubKey& pubkey = pk_pair.first; + for (unsigned char prefix : {0x02, 0x03}) { + unsigned char b[33] = {prefix}; + std::copy(pubkey.begin(), pubkey.end(), b + 1); + CPubKey fullpubkey; + fullpubkey.Set(b, b + 33); + std::unique_ptr pk_keys = GetSigningProvider(fullpubkey); + if (pk_keys) { + *keys = Merge(*keys, *pk_keys); + } + } + } } SignPSBTInput(HidingSigningProvider(keys.get(), !sign, !bip32derivs), psbtx, i, &txdata, sighash_type, nullptr, finalize); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index d0b093bbb7..041481559b 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2006,6 +2006,35 @@ TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& comp } } + // Only drop non_witness_utxos if sighash_type != SIGHASH_ANYONECANPAY + if ((sighash_type & 0x80) != SIGHASH_ANYONECANPAY) { + // Figure out if any non_witness_utxos should be dropped + std::vector to_drop; + for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) { + const auto& input = psbtx.inputs.at(i); + int wit_ver; + std::vector wit_prog; + if (input.witness_utxo.IsNull() || !input.witness_utxo.scriptPubKey.IsWitnessProgram(wit_ver, wit_prog)) { + // There's a non-segwit input or Segwit v0, so we cannot drop any witness_utxos + to_drop.clear(); + break; + } + if (wit_ver == 0) { + // Segwit v0, so we cannot drop any non_witness_utxos + to_drop.clear(); + break; + } + if (input.non_witness_utxo) { + to_drop.push_back(i); + } + } + + // Drop the non_witness_utxos that we can drop + for (unsigned int i : to_drop) { + psbtx.inputs.at(i).non_witness_utxo = nullptr; + } + } + // Complete if every input is now signed complete = true; for (const auto& input : psbtx.inputs) { diff --git a/test/functional/data/rpc_psbt.json b/test/functional/data/rpc_psbt.json index 8672400a92..430a1802a8 100644 --- a/test/functional/data/rpc_psbt.json +++ b/test/functional/data/rpc_psbt.json @@ -27,7 +27,18 @@ "cHNidP8BADMBAAAAAREREREREREREREREREREREREfrK3hERERERERERERERfwAAAAD/////AAAAAAAAAQQAAQQBagA=", "cHNidP8BADMBAAAAAREREREREREREREREREREREREfrK3hERERERERERERERfwAAAAD/////AAAAAAAAAQEJAOH1BQAAAAAAAQUAAQUBUQA=", "cHNidP8BADMBAAAAAREREREREREREREREREREREREfrK3hERERERERERERERfwAAAAD/////AAAAAAAAAQcAAQcBUQA=", - "cHNidP8BADMBAAAAAREREREREREREREREREREREREfrK3hERERERERERERERfwAAAAD/////AAAAAAAAAQEJAOH1BQAAAAAAAQgBAAEIAwEBUQA=" + "cHNidP8BADMBAAAAAREREREREREREREREREREREREfrK3hERERERERERERERfwAAAAD/////AAAAAAAAAQEJAOH1BQAAAAAAAQgBAAEIAwEBUQA=", + "cHNidP8BAHECAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////Anh8AQAAAAAAFgAUg6fjS9mf8DpJYu+KGhAbspVGHs5gawQqAQAAABYAFHrDad8bIOAz1hFmI5V7CsSfPFLoAAAAAAABASsA8gUqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXARchAv40kGTJjW4qhT+jybEr2LMEoZwZXGDvp+4jkwRtP6IyAAAA", + "cHNidP8BAHECAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////Anh8AQAAAAAAFgAUg6fjS9mf8DpJYu+KGhAbspVGHs5gawQqAQAAABYAFHrDad8bIOAz1hFmI5V7CsSfPFLoAAAAAAABASsA8gUqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXARM/Fzuz02wHSvtxb+xjB6BpouRQuZXzyCeFlFq43w4kJg3NcDsMvzTeOZGEqUgawrNYbbZgHwJqd/fkk4SBvDR1AAAA", + "cHNidP8BAHECAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////Anh8AQAAAAAAFgAUg6fjS9mf8DpJYu+KGhAbspVGHs5gawQqAQAAABYAFHrDad8bIOAz1hFmI5V7CsSfPFLoAAAAAAABASsA8gUqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXARNCFzuz02wHSvtxb+xjB6BpouRQuZXzyCeFlFq43w4kJg3NcDsMvzTeOZGEqUgawrNYbbZgHwJqd/fkk4SBvDR1FwGqAAAA", + "cHNidP8BAHECAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////Anh8AQAAAAAAFgAUg6fjS9mf8DpJYu+KGhAbspVGHs5gawQqAQAAABYAFHrDad8bIOAz1hFmI5V7CsSfPFLoAAAAAAABASsA8gUqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXIhYC/jSQZMmNbiqFP6PJsSvYswShnBlcYO+n7iOTBG0/ojIZAHcrLadWAACAAQAAgAAAAIABAAAAAAAAAAAAAA==", + "cHNidP8BAH0CAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////Aoh7AQAAAAAAFgAUI4KHHH6EIaAAk/dU2RKB5nWHS59gawQqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXAAAAAAABASsA8gUqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXAAABBSEC/jSQZMmNbiqFP6PJsSvYswShnBlcYO+n7iOTBG0/ojIA", + "cHNidP8BAH0CAAAAASd0Srq/MCf+DWzyOpbu4u+xiO9SMBlUWFiD5ptmJLJCAAAAAAD/////Aoh7AQAAAAAAFgAUI4KHHH6EIaAAk/dU2RKB5nWHS59gawQqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXAAAAAAABASsA8gUqAQAAACJRIFosLPW1LPMfg60ujaY/8DGD7Nj2CcdRCuikjgORCgdXAAAiBwL+NJBkyY1uKoU/o8mxK9izBKGcGVxg76fuI5MEbT+iMhkAdystp1YAAIABAACAAAAAgAEAAAAAAAAAAA==", + "cHNidP8BAF4CAAAAAZvUh2UjC/mnLmYgAflyVW5U8Mb5f+tWvLVgDYF/aZUmAQAAAAD/////AUjmBSoBAAAAIlEgAw2k/OT32yjCyylRYx4ANxOFZZf+ljiCy1AOaBEsymMAAAAAAAEBKwDyBSoBAAAAIlEgwiR++/2SrEf29AuNQtFpF1oZ+p+hDkol1/NetN2FtpJCFAIssTrGgkjegGqmo2Wc88A+toIdCcgRSk6Gj+vehlu20s2XDhX1P8DIL5UP1WD/qRm3YXK+AXNoqJkTrwdPQAsJQIl1aqNznMxonsD886NgvjLMC1mxbpOh6LtGBXJrLKej/3BsQXZkljKyzGjh+RK4pXjjcZzncQiFx6lm9JvNQ8sAAA==", + "cHNidP8BAF4CAAAAAZvUh2UjC/mnLmYgAflyVW5U8Mb5f+tWvLVgDYF/aZUmAQAAAAD/////AUjmBSoBAAAAIlEgAw2k/OT32yjCyylRYx4ANxOFZZf+ljiCy1AOaBEsymMAAAAAAAEBKwDyBSoBAAAAIlEgwiR++/2SrEf29AuNQtFpF1oZ+p+hDkol1/NetN2FtpJBFCyxOsaCSN6AaqajZZzzwD62gh0JyBFKToaP696GW7bSzZcOFfU/wMgvlQ/VYP+pGbdhcr4Bc2iomROvB09ACwlCiXVqo3OczGiewPzzo2C+MswLWbFuk6Hou0YFcmssp6P/cGxBdmSWMrLMaOH5ErileONxnOdxCIXHqWb0m81DywEBAAA=", + "cHNidP8BAF4CAAAAAZvUh2UjC/mnLmYgAflyVW5U8Mb5f+tWvLVgDYF/aZUmAQAAAAD/////AUjmBSoBAAAAIlEgAw2k/OT32yjCyylRYx4ANxOFZZf+ljiCy1AOaBEsymMAAAAAAAEBKwDyBSoBAAAAIlEgwiR++/2SrEf29AuNQtFpF1oZ+p+hDkol1/NetN2FtpJBFCyxOsaCSN6AaqajZZzzwD62gh0JyBFKToaP696GW7bSzZcOFfU/wMgvlQ/VYP+pGbdhcr4Bc2iomROvB09ACwk5iXVqo3OczGiewPzzo2C+MswLWbFuk6Hou0YFcmssp6P/cGxBdmSWMrLMaOH5ErileONxnOdxCIXHqWb0m81DywAA", + "cHNidP8BAF4CAAAAAZvUh2UjC/mnLmYgAflyVW5U8Mb5f+tWvLVgDYF/aZUmAQAAAAD/////AUjmBSoBAAAAIlEgAw2k/OT32yjCyylRYx4ANxOFZZf+ljiCy1AOaBEsymMAAAAAAAEBKwDyBSoBAAAAIlEgwiR++/2SrEf29AuNQtFpF1oZ+p+hDkol1/NetN2FtpJjFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wG99YgWelJehpKJnVp2YdtpgEBr/OONSm5uTnOf5GulwEV8uSQr3zEXE94UR82BXzlxaXFYyWin7RN/CA/NW4fgAIyAssTrGgkjegGqmo2Wc88A+toIdCcgRSk6Gj+vehlu20qzAAAA=", + "cHNidP8BAF4CAAAAAZvUh2UjC/mnLmYgAflyVW5U8Mb5f+tWvLVgDYF/aZUmAQAAAAD/////AUjmBSoBAAAAIlEgAw2k/OT32yjCyylRYx4ANxOFZZf+ljiCy1AOaBEsymMAAAAAAAEBKwDyBSoBAAAAIlEgwiR++/2SrEf29AuNQtFpF1oZ+p+hDkol1/NetN2FtpJhFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wG99YgWelJehpKJnVp2YdtpgEBr/OONSm5uTnOf5GulwEV8uSQr3zEXE94UR82BXzlxaXFYyWin7RN/CA/NW4SMgLLE6xoJI3oBqpqNlnPPAPraCHQnIEUpOho/r3oZbttKswAAA" ], "valid" : [ "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAAAA", @@ -43,7 +54,13 @@ "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" ], "creator" : [ { diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index 444e56610e..d2a888fd31 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -9,7 +9,7 @@ from decimal import Decimal from itertools import product from test_framework.descriptors import descsum_create -from test_framework.key import ECKey +from test_framework.key import ECKey, H_POINT from test_framework.messages import ( ser_compact_size, WITNESS_SCALE_FACTOR, @@ -723,5 +723,46 @@ class PSBTTest(BitcoinTestFramework): ) assert_equal(psbt2["fee"], psbt3["fee"]) + self.log.info("Test signing inputs that the wallet has keys for but is not watching the scripts") + self.nodes[1].createwallet(wallet_name="scriptwatchonly", disable_private_keys=True) + watchonly = self.nodes[1].get_wallet_rpc("scriptwatchonly") + + eckey = ECKey() + eckey.generate() + privkey = bytes_to_wif(eckey.get_bytes()) + + desc = descsum_create("wsh(pkh({}))".format(eckey.get_pubkey().get_bytes().hex())) + if self.options.descriptors: + res = watchonly.importdescriptors([{"desc": desc, "timestamp": "now"}]) + else: + res = watchonly.importmulti([{"desc": desc, "timestamp": "now"}]) + assert res[0]["success"] + addr = self.nodes[0].deriveaddresses(desc)[0] + self.nodes[0].sendtoaddress(addr, 10) + self.generate(self.nodes[0], 1) + self.nodes[0].importprivkey(privkey) + + psbt = watchonly.sendall([wallet.getnewaddress()])["psbt"] + psbt = self.nodes[0].walletprocesspsbt(psbt)["psbt"] + self.nodes[0].sendrawtransaction(self.nodes[0].finalizepsbt(psbt)["hex"]) + + # Same test but for taproot + if self.options.descriptors: + eckey = ECKey() + eckey.generate() + privkey = bytes_to_wif(eckey.get_bytes()) + + desc = descsum_create("tr({},pk({}))".format(H_POINT, eckey.get_pubkey().get_bytes().hex())) + res = watchonly.importdescriptors([{"desc": desc, "timestamp": "now"}]) + assert res[0]["success"] + addr = self.nodes[0].deriveaddresses(desc)[0] + self.nodes[0].sendtoaddress(addr, 10) + self.generate(self.nodes[0], 1) + self.nodes[0].importdescriptors([{"desc": descsum_create("tr({})".format(privkey)), "timestamp":"now"}]) + + psbt = watchonly.sendall([wallet.getnewaddress()])["psbt"] + psbt = self.nodes[0].walletprocesspsbt(psbt)["psbt"] + self.nodes[0].sendrawtransaction(self.nodes[0].finalizepsbt(psbt)["hex"]) + if __name__ == '__main__': PSBTTest().main() diff --git a/test/functional/test_framework/key.py b/test/functional/test_framework/key.py index e5dea66963..68afc1383d 100644 --- a/test/functional/test_framework/key.py +++ b/test/functional/test_framework/key.py @@ -15,6 +15,10 @@ import unittest from .util import modinv +# Point with no known discrete log. +H_POINT = "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0" + + def TaggedHash(tag, data): ss = hashlib.sha256(tag.encode('utf-8')).digest() ss += ss diff --git a/test/functional/wallet_taproot.py b/test/functional/wallet_taproot.py index d238c50bca..c8d4a1da45 100755 --- a/test/functional/wallet_taproot.py +++ b/test/functional/wallet_taproot.py @@ -8,6 +8,7 @@ import random from decimal import Decimal from test_framework.address import output_key_to_p2tr +from test_framework.key import H_POINT from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal from test_framework.descriptors import descsum_create @@ -157,9 +158,6 @@ KEYS = [ CHANGE_XPRV = "tprv8ZgxMBicQKsPcyDrWwiecVnTtFmfRwbfFqEfR4ZGWvq5aTTwLBWmAm5zrbMcYtb9gQNFfhRfqhhrBG37U3nhmXxEgeEPBJGHAPrHCrAd1WX" CHANGE_XPUB = "tpubD6NzVbkrYhZ4WSFeQbPF1uSaTHHbbGnZq8qShabZwCdUQwihxaLMMFhs2kidGF2qrRKiQVqw8VoyuTHj1bZqmMXMeciaU1gBjWA1sim2zUB" -# Point with no known discrete log. -H_POINT = "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0" - def key(hex_key): """Construct an x-only pubkey from its hex representation.""" @@ -301,9 +299,21 @@ class WalletTaprootTest(BitcoinTestFramework): test_balance = int(self.psbt_online.getbalance() * 100000000) ret_amnt = random.randrange(100000, test_balance) # Increase fee_rate to compensate for the wallet's inability to estimate fees for script path spends. - psbt = self.psbt_online.walletcreatefundedpsbt([], [{self.boring.getnewaddress(): Decimal(ret_amnt) / 100000000}], None, {"subtractFeeFromOutputs":[0], "fee_rate": 200})['psbt'] - res = self.psbt_offline.walletprocesspsbt(psbt) - assert(res['complete']) + psbt = self.psbt_online.walletcreatefundedpsbt([], [{self.boring.getnewaddress(): Decimal(ret_amnt) / 100000000}], None, {"subtractFeeFromOutputs":[0], "fee_rate": 200, "change_type": "bech32m"})['psbt'] + res = self.psbt_offline.walletprocesspsbt(psbt=psbt, finalize=False) + + decoded = self.psbt_offline.decodepsbt(res["psbt"]) + if pattern.startswith("tr("): + for psbtin in decoded["inputs"]: + assert "non_witness_utxo" not in psbtin + assert "witness_utxo" in psbtin + assert "taproot_internal_key" in psbtin + assert "taproot_bip32_derivs" in psbtin + assert "taproot_key_path_sig" in psbtin or "taproot_script_path_sigs" in psbtin + if "taproot_script_path_sigs" in psbtin: + assert "taproot_merkle_root" in psbtin + assert "taproot_scripts" in psbtin + rawtx = self.nodes[0].finalizepsbt(res['psbt'])['hex'] txid = self.nodes[0].sendrawtransaction(rawtx) self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op) @@ -442,8 +452,7 @@ class WalletTaprootTest(BitcoinTestFramework): assert(self.rpc_online.gettransaction(txid)["confirmations"] > 0) psbt = self.psbt_online.sendall(recipients=[self.boring.getnewaddress()], options={"psbt": True})["psbt"] - res = self.psbt_offline.walletprocesspsbt(psbt) - assert(res['complete']) + res = self.psbt_offline.walletprocesspsbt(psbt=psbt, finalize=False) rawtx = self.nodes[0].finalizepsbt(res['psbt'])['hex'] txid = self.nodes[0].sendrawtransaction(rawtx) self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op)