diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/index/base.cpp | 3 | ||||
-rw-r--r-- | src/net.cpp | 1 | ||||
-rw-r--r-- | src/psbt.cpp | 19 | ||||
-rw-r--r-- | src/psbt.h | 406 | ||||
-rw-r--r-- | src/pubkey.cpp | 12 | ||||
-rw-r--r-- | src/pubkey.h | 19 | ||||
-rw-r--r-- | src/rpc/misc.cpp | 11 | ||||
-rw-r--r-- | src/rpc/rawtransaction.cpp | 162 | ||||
-rw-r--r-- | src/script/keyorigin.h | 19 | ||||
-rw-r--r-- | src/script/sign.cpp | 1 | ||||
-rw-r--r-- | src/script/sign.h | 76 | ||||
-rw-r--r-- | src/script/signingprovider.h | 3 | ||||
-rw-r--r-- | src/serialize.h | 13 | ||||
-rw-r--r-- | src/test/fuzz/integer.cpp | 4 | ||||
-rw-r--r-- | src/test/fuzz/parse_iso8601.cpp | 1 | ||||
-rw-r--r-- | src/test/fuzz/script_sign.cpp | 5 | ||||
-rw-r--r-- | src/wallet/rpc/addresses.cpp | 12 |
17 files changed, 661 insertions, 106 deletions
diff --git a/src/index/base.cpp b/src/index/base.cpp index fc6dd77a72..8525dcbfa0 100644 --- a/src/index/base.cpp +++ b/src/index/base.cpp @@ -91,11 +91,12 @@ bool BaseIndex::Init() const CBlockIndex* block = active_chain.Tip(); prune_violation = true; // check backwards from the tip if we have all block data until we reach the indexes bestblock - while (block_to_test && block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA)) { + while (block_to_test && block && (block->nStatus & BLOCK_HAVE_DATA)) { if (block_to_test == block) { prune_violation = false; break; } + assert(block->pprev); block = block->pprev; } } diff --git a/src/net.cpp b/src/net.cpp index 2adccce11e..2e2aa9dcef 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -2899,7 +2899,6 @@ void CConnman::RecordBytesSent(uint64_t bytes) nMaxOutboundTotalBytesSentInCycle = 0; } - // TODO, exclude peers with download permission nMaxOutboundTotalBytesSentInCycle += bytes; } diff --git a/src/psbt.cpp b/src/psbt.cpp index 6585766807..203e0a0bd3 100644 --- a/src/psbt.cpp +++ b/src/psbt.cpp @@ -32,6 +32,13 @@ bool PartiallySignedTransaction::Merge(const PartiallySignedTransaction& psbt) for (unsigned int i = 0; i < outputs.size(); ++i) { outputs[i].Merge(psbt.outputs[i]); } + for (auto& xpub_pair : psbt.m_xpubs) { + if (m_xpubs.count(xpub_pair.first) == 0) { + m_xpubs[xpub_pair.first] = xpub_pair.second; + } else { + m_xpubs[xpub_pair.first].insert(xpub_pair.second.begin(), xpub_pair.second.end()); + } + } unknown.insert(psbt.unknown.begin(), psbt.unknown.end()); return true; @@ -146,6 +153,10 @@ void PSBTInput::Merge(const PSBTInput& input) } partial_sigs.insert(input.partial_sigs.begin(), input.partial_sigs.end()); + ripemd160_preimages.insert(input.ripemd160_preimages.begin(), input.ripemd160_preimages.end()); + sha256_preimages.insert(input.sha256_preimages.begin(), input.sha256_preimages.end()); + hash160_preimages.insert(input.hash160_preimages.begin(), input.hash160_preimages.end()); + 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()); @@ -401,3 +412,11 @@ bool DecodeRawPSBT(PartiallySignedTransaction& psbt, const std::string& tx_data, } return true; } + +uint32_t PartiallySignedTransaction::GetVersion() const +{ + if (m_version != std::nullopt) { + return *m_version; + } + return 0; +} diff --git a/src/psbt.h b/src/psbt.h index 21daa050ea..43b1b249c5 100644 --- a/src/psbt.h +++ b/src/psbt.h @@ -10,8 +10,11 @@ #include <policy/feerate.h> #include <primitives/transaction.h> #include <pubkey.h> +#include <script/keyorigin.h> #include <script/sign.h> #include <script/signingprovider.h> +#include <span.h> +#include <streams.h> #include <optional> @@ -20,6 +23,9 @@ static constexpr uint8_t PSBT_MAGIC_BYTES[5] = {'p', 's', 'b', 't', 0xff}; // Global types static constexpr uint8_t PSBT_GLOBAL_UNSIGNED_TX = 0x00; +static constexpr uint8_t PSBT_GLOBAL_XPUB = 0x01; +static constexpr uint8_t PSBT_GLOBAL_VERSION = 0xFB; +static constexpr uint8_t PSBT_GLOBAL_PROPRIETARY = 0xFC; // Input types static constexpr uint8_t PSBT_IN_NON_WITNESS_UTXO = 0x00; @@ -31,11 +37,17 @@ static constexpr uint8_t PSBT_IN_WITNESSSCRIPT = 0x05; static constexpr uint8_t PSBT_IN_BIP32_DERIVATION = 0x06; static constexpr uint8_t PSBT_IN_SCRIPTSIG = 0x07; static constexpr uint8_t PSBT_IN_SCRIPTWITNESS = 0x08; +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_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_PROPRIETARY = 0xFC; // The separator is 0x00. Reading this in means that the unserializer can interpret it // as a 0 length key which indicates that this is the separator. The separator has no value. @@ -45,6 +57,113 @@ static constexpr uint8_t PSBT_SEPARATOR = 0x00; // to prevent reading a stream indefinitely and running out of memory. const std::streamsize MAX_FILE_SIZE_PSBT = 100000000; // 100 MiB +// PSBT version number +static constexpr uint32_t PSBT_HIGHEST_VERSION = 0; + +/** A structure for PSBT proprietary types */ +struct PSBTProprietary +{ + uint64_t subtype; + std::vector<unsigned char> identifier; + std::vector<unsigned char> key; + std::vector<unsigned char> value; + + bool operator<(const PSBTProprietary &b) const { + return key < b.key; + } + bool operator==(const PSBTProprietary &b) const { + return key == b.key; + } +}; + +// Takes a stream and multiple arguments and serializes them as if first serialized into a vector and then into the stream +// The resulting output into the stream has the total serialized length of all of the objects followed by all objects concatenated with each other. +template<typename Stream, typename... X> +void SerializeToVector(Stream& s, const X&... args) +{ + WriteCompactSize(s, GetSerializeSizeMany(s.GetVersion(), args...)); + SerializeMany(s, args...); +} + +// Takes a stream and multiple arguments and unserializes them first as a vector then each object individually in the order provided in the arguments +template<typename Stream, typename... X> +void UnserializeFromVector(Stream& s, X&... args) +{ + size_t expected_size = ReadCompactSize(s); + size_t remaining_before = s.size(); + UnserializeMany(s, args...); + size_t remaining_after = s.size(); + if (remaining_after + expected_size != remaining_before) { + throw std::ios_base::failure("Size of value was not the stated size"); + } +} + +// Deserialize an individual HD keypath to a stream +template<typename Stream> +void DeserializeHDKeypath(Stream& s, KeyOriginInfo& hd_keypath) +{ + // Read in key path + uint64_t value_len = ReadCompactSize(s); + if (value_len % 4 || value_len == 0) { + throw std::ios_base::failure("Invalid length for HD key path"); + } + + s >> hd_keypath.fingerprint; + for (unsigned int i = 4; i < value_len; i += sizeof(uint32_t)) { + uint32_t index; + s >> index; + hd_keypath.path.push_back(index); + } +} + +// Deserialize HD keypaths into a map +template<typename Stream> +void DeserializeHDKeypaths(Stream& s, const std::vector<unsigned char>& key, std::map<CPubKey, KeyOriginInfo>& hd_keypaths) +{ + // Make sure that the key is the size of pubkey + 1 + if (key.size() != CPubKey::SIZE + 1 && key.size() != CPubKey::COMPRESSED_SIZE + 1) { + throw std::ios_base::failure("Size of key was not the expected size for the type BIP32 keypath"); + } + // Read in the pubkey from key + CPubKey pubkey(key.begin() + 1, key.end()); + if (!pubkey.IsFullyValid()) { + throw std::ios_base::failure("Invalid pubkey"); + } + if (hd_keypaths.count(pubkey) > 0) { + throw std::ios_base::failure("Duplicate Key, pubkey derivation path already provided"); + } + + KeyOriginInfo keypath; + DeserializeHDKeypath(s, keypath); + + // Add to map + hd_keypaths.emplace(pubkey, std::move(keypath)); +} + +// Serialize an individual HD keypath to a stream +template<typename Stream> +void SerializeHDKeypath(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 HD keypaths to a stream from a map +template<typename Stream> +void SerializeHDKeypaths(Stream& s, const std::map<CPubKey, KeyOriginInfo>& hd_keypaths, CompactSizeWriter type) +{ + for (auto keypath_pair : hd_keypaths) { + if (!keypath_pair.first.IsValid()) { + throw std::ios_base::failure("Invalid CPubKey being serialized"); + } + SerializeToVector(s, type, Span{keypath_pair.first}); + SerializeHDKeypath(s, keypath_pair.second); + } +} + /** A structure for PSBTs which contain per-input information */ struct PSBTInput { @@ -56,7 +175,12 @@ struct PSBTInput CScriptWitness final_script_witness; std::map<CPubKey, KeyOriginInfo> hd_keypaths; std::map<CKeyID, SigPair> partial_sigs; + std::map<uint160, std::vector<unsigned char>> ripemd160_preimages; + std::map<uint256, std::vector<unsigned char>> sha256_preimages; + std::map<uint160, std::vector<unsigned char>> hash160_preimages; + std::map<uint256, std::vector<unsigned char>> hash256_preimages; std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown; + std::set<PSBTProprietary> m_proprietary; std::optional<int> sighash_type; bool IsNull() const; @@ -69,55 +193,85 @@ struct PSBTInput inline void Serialize(Stream& s) const { // Write the utxo if (non_witness_utxo) { - SerializeToVector(s, PSBT_IN_NON_WITNESS_UTXO); + SerializeToVector(s, CompactSizeWriter(PSBT_IN_NON_WITNESS_UTXO)); OverrideStream<Stream> os(&s, s.GetType(), s.GetVersion() | SERIALIZE_TRANSACTION_NO_WITNESS); SerializeToVector(os, non_witness_utxo); } if (!witness_utxo.IsNull()) { - SerializeToVector(s, PSBT_IN_WITNESS_UTXO); + SerializeToVector(s, CompactSizeWriter(PSBT_IN_WITNESS_UTXO)); SerializeToVector(s, witness_utxo); } if (final_script_sig.empty() && final_script_witness.IsNull()) { // Write any partial signatures for (auto sig_pair : partial_sigs) { - SerializeToVector(s, PSBT_IN_PARTIAL_SIG, Span{sig_pair.second.first}); + SerializeToVector(s, CompactSizeWriter(PSBT_IN_PARTIAL_SIG), Span{sig_pair.second.first}); s << sig_pair.second.second; } // Write the sighash type if (sighash_type != std::nullopt) { - SerializeToVector(s, PSBT_IN_SIGHASH); + SerializeToVector(s, CompactSizeWriter(PSBT_IN_SIGHASH)); SerializeToVector(s, *sighash_type); } // Write the redeem script if (!redeem_script.empty()) { - SerializeToVector(s, PSBT_IN_REDEEMSCRIPT); + SerializeToVector(s, CompactSizeWriter(PSBT_IN_REDEEMSCRIPT)); s << redeem_script; } // Write the witness script if (!witness_script.empty()) { - SerializeToVector(s, PSBT_IN_WITNESSSCRIPT); + SerializeToVector(s, CompactSizeWriter(PSBT_IN_WITNESSSCRIPT)); s << witness_script; } // Write any hd keypaths - SerializeHDKeypaths(s, hd_keypaths, PSBT_IN_BIP32_DERIVATION); + SerializeHDKeypaths(s, hd_keypaths, CompactSizeWriter(PSBT_IN_BIP32_DERIVATION)); + + // Write any ripemd160 preimage + for (const auto& [hash, preimage] : ripemd160_preimages) { + SerializeToVector(s, CompactSizeWriter(PSBT_IN_RIPEMD160), Span{hash}); + s << preimage; + } + + // Write any sha256 preimage + for (const auto& [hash, preimage] : sha256_preimages) { + SerializeToVector(s, CompactSizeWriter(PSBT_IN_SHA256), Span{hash}); + s << preimage; + } + + // Write any hash160 preimage + for (const auto& [hash, preimage] : hash160_preimages) { + SerializeToVector(s, CompactSizeWriter(PSBT_IN_HASH160), Span{hash}); + s << preimage; + } + + // Write any hash256 preimage + for (const auto& [hash, preimage] : hash256_preimages) { + SerializeToVector(s, CompactSizeWriter(PSBT_IN_HASH256), Span{hash}); + s << preimage; + } } // Write script sig if (!final_script_sig.empty()) { - SerializeToVector(s, PSBT_IN_SCRIPTSIG); + SerializeToVector(s, CompactSizeWriter(PSBT_IN_SCRIPTSIG)); s << final_script_sig; } // write script witness if (!final_script_witness.IsNull()) { - SerializeToVector(s, PSBT_IN_SCRIPTWITNESS); + SerializeToVector(s, CompactSizeWriter(PSBT_IN_SCRIPTWITNESS)); SerializeToVector(s, final_script_witness.stack); } + // Write proprietary things + for (const auto& entry : m_proprietary) { + s << entry.key; + s << entry.value; + } + // Write unknown things for (auto& entry : unknown) { s << entry.first; @@ -147,8 +301,9 @@ struct PSBTInput break; } - // First byte of key is the type - unsigned char type = key[0]; + // Type is compact size uint at beginning of key + SpanReader skey(s.GetType(), s.GetVersion(), key); + uint64_t type = ReadCompactSize(skey); // Do stuff based on type switch(type) { @@ -250,6 +405,104 @@ struct PSBTInput UnserializeFromVector(s, final_script_witness.stack); break; } + case PSBT_IN_RIPEMD160: + { + // Make sure that the key is the size of a ripemd160 hash + 1 + if (key.size() != CRIPEMD160::OUTPUT_SIZE + 1) { + throw std::ios_base::failure("Size of key was not the expected size for the type ripemd160 preimage"); + } + // Read in the hash from key + std::vector<unsigned char> hash_vec(key.begin() + 1, key.end()); + uint160 hash(hash_vec); + if (ripemd160_preimages.count(hash) > 0) { + throw std::ios_base::failure("Duplicate Key, input ripemd160 preimage already provided"); + } + + // Read in the preimage from value + std::vector<unsigned char> preimage; + s >> preimage; + + // Add to preimages list + ripemd160_preimages.emplace(hash, std::move(preimage)); + break; + } + case PSBT_IN_SHA256: + { + // Make sure that the key is the size of a sha256 hash + 1 + if (key.size() != CSHA256::OUTPUT_SIZE + 1) { + throw std::ios_base::failure("Size of key was not the expected size for the type sha256 preimage"); + } + // Read in the hash from key + std::vector<unsigned char> hash_vec(key.begin() + 1, key.end()); + uint256 hash(hash_vec); + if (sha256_preimages.count(hash) > 0) { + throw std::ios_base::failure("Duplicate Key, input sha256 preimage already provided"); + } + + // Read in the preimage from value + std::vector<unsigned char> preimage; + s >> preimage; + + // Add to preimages list + sha256_preimages.emplace(hash, std::move(preimage)); + break; + } + case PSBT_IN_HASH160: + { + // Make sure that the key is the size of a hash160 hash + 1 + if (key.size() != CHash160::OUTPUT_SIZE + 1) { + throw std::ios_base::failure("Size of key was not the expected size for the type hash160 preimage"); + } + // Read in the hash from key + std::vector<unsigned char> hash_vec(key.begin() + 1, key.end()); + uint160 hash(hash_vec); + if (hash160_preimages.count(hash) > 0) { + throw std::ios_base::failure("Duplicate Key, input hash160 preimage already provided"); + } + + // Read in the preimage from value + std::vector<unsigned char> preimage; + s >> preimage; + + // Add to preimages list + hash160_preimages.emplace(hash, std::move(preimage)); + break; + } + case PSBT_IN_HASH256: + { + // Make sure that the key is the size of a hash256 hash + 1 + if (key.size() != CHash256::OUTPUT_SIZE + 1) { + throw std::ios_base::failure("Size of key was not the expected size for the type hash256 preimage"); + } + // Read in the hash from key + std::vector<unsigned char> hash_vec(key.begin() + 1, key.end()); + uint256 hash(hash_vec); + if (hash256_preimages.count(hash) > 0) { + throw std::ios_base::failure("Duplicate Key, input hash256 preimage already provided"); + } + + // Read in the preimage from value + std::vector<unsigned char> preimage; + s >> preimage; + + // Add to preimages list + hash256_preimages.emplace(hash, std::move(preimage)); + break; + } + case PSBT_IN_PROPRIETARY: + { + PSBTProprietary this_prop; + skey >> this_prop.identifier; + this_prop.subtype = ReadCompactSize(skey); + this_prop.key = key; + + if (m_proprietary.count(this_prop) > 0) { + throw std::ios_base::failure("Duplicate Key, proprietary key already found"); + } + s >> this_prop.value; + m_proprietary.insert(this_prop); + break; + } // Unknown stuff default: if (unknown.count(key) > 0) { @@ -281,6 +534,7 @@ struct PSBTOutput CScript witness_script; std::map<CPubKey, KeyOriginInfo> hd_keypaths; std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown; + std::set<PSBTProprietary> m_proprietary; bool IsNull() const; void FillSignatureData(SignatureData& sigdata) const; @@ -292,18 +546,24 @@ struct PSBTOutput inline void Serialize(Stream& s) const { // Write the redeem script if (!redeem_script.empty()) { - SerializeToVector(s, PSBT_OUT_REDEEMSCRIPT); + SerializeToVector(s, CompactSizeWriter(PSBT_OUT_REDEEMSCRIPT)); s << redeem_script; } // Write the witness script if (!witness_script.empty()) { - SerializeToVector(s, PSBT_OUT_WITNESSSCRIPT); + SerializeToVector(s, CompactSizeWriter(PSBT_OUT_WITNESSSCRIPT)); s << witness_script; } // Write any hd keypaths - SerializeHDKeypaths(s, hd_keypaths, PSBT_OUT_BIP32_DERIVATION); + SerializeHDKeypaths(s, hd_keypaths, CompactSizeWriter(PSBT_OUT_BIP32_DERIVATION)); + + // Write proprietary things + for (const auto& entry : m_proprietary) { + s << entry.key; + s << entry.value; + } // Write unknown things for (auto& entry : unknown) { @@ -334,8 +594,9 @@ struct PSBTOutput break; } - // First byte of key is the type - unsigned char type = key[0]; + // Type is compact size uint at beginning of key + SpanReader skey(s.GetType(), s.GetVersion(), key); + uint64_t type = ReadCompactSize(skey); // Do stuff based on type switch(type) { @@ -364,6 +625,20 @@ struct PSBTOutput DeserializeHDKeypaths(s, key, hd_keypaths); break; } + case PSBT_OUT_PROPRIETARY: + { + PSBTProprietary this_prop; + skey >> this_prop.identifier; + this_prop.subtype = ReadCompactSize(skey); + this_prop.key = key; + + if (m_proprietary.count(this_prop) > 0) { + throw std::ios_base::failure("Duplicate Key, proprietary key already found"); + } + s >> this_prop.value; + m_proprietary.insert(this_prop); + break; + } // Unknown stuff default: { if (unknown.count(key) > 0) { @@ -393,11 +668,17 @@ struct PSBTOutput struct PartiallySignedTransaction { std::optional<CMutableTransaction> tx; + // We use a vector of CExtPubKey in the event that there happens to be the same KeyOriginInfos for different CExtPubKeys + // Note that this map swaps the key and values from the serialization + std::map<KeyOriginInfo, std::set<CExtPubKey>> m_xpubs; std::vector<PSBTInput> inputs; std::vector<PSBTOutput> outputs; std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown; + std::optional<uint32_t> m_version; + std::set<PSBTProprietary> m_proprietary; bool IsNull() const; + uint32_t GetVersion() const; /** Merge psbt into this. The two psbts must have the same underlying CTransaction (i.e. the * same actual Bitcoin transaction.) Returns true if the merge succeeded, false otherwise. */ @@ -422,12 +703,36 @@ struct PartiallySignedTransaction s << PSBT_MAGIC_BYTES; // unsigned tx flag - SerializeToVector(s, PSBT_GLOBAL_UNSIGNED_TX); + SerializeToVector(s, CompactSizeWriter(PSBT_GLOBAL_UNSIGNED_TX)); // Write serialized tx to a stream OverrideStream<Stream> os(&s, s.GetType(), s.GetVersion() | SERIALIZE_TRANSACTION_NO_WITNESS); SerializeToVector(os, *tx); + // Write xpubs + for (const auto& xpub_pair : m_xpubs) { + for (const auto& xpub : xpub_pair.second) { + unsigned char ser_xpub[BIP32_EXTKEY_WITH_VERSION_SIZE]; + xpub.EncodeWithVersion(ser_xpub); + // Note that the serialization swaps the key and value + // The xpub is the key (for uniqueness) while the path is the value + SerializeToVector(s, PSBT_GLOBAL_XPUB, ser_xpub); + SerializeHDKeypath(s, xpub_pair.first); + } + } + + // PSBT version + if (GetVersion() > 0) { + SerializeToVector(s, CompactSizeWriter(PSBT_GLOBAL_VERSION)); + SerializeToVector(s, *m_version); + } + + // Write proprietary things + for (const auto& entry : m_proprietary) { + s << entry.key; + s << entry.value; + } + // Write the unknown things for (auto& entry : unknown) { s << entry.first; @@ -460,6 +765,9 @@ struct PartiallySignedTransaction // Used for duplicate key detection std::set<std::vector<unsigned char>> key_lookup; + // Track the global xpubs we have already seen. Just for sanity checking + std::set<CExtPubKey> global_xpubs; + // Read global data bool found_sep = false; while(!s.empty()) { @@ -474,8 +782,9 @@ struct PartiallySignedTransaction break; } - // First byte of key is the type - unsigned char type = key[0]; + // Type is compact size uint at beginning of key + SpanReader skey(s.GetType(), s.GetVersion(), key); + uint64_t type = ReadCompactSize(skey); // Do stuff based on type switch(type) { @@ -499,6 +808,65 @@ struct PartiallySignedTransaction } break; } + case PSBT_GLOBAL_XPUB: + { + if (key.size() != BIP32_EXTKEY_WITH_VERSION_SIZE + 1) { + throw std::ios_base::failure("Size of key was not the expected size for the type global xpub"); + } + // Read in the xpub from key + CExtPubKey xpub; + xpub.DecodeWithVersion(&key.data()[1]); + if (!xpub.pubkey.IsFullyValid()) { + throw std::ios_base::failure("Invalid pubkey"); + } + if (global_xpubs.count(xpub) > 0) { + throw std::ios_base::failure("Duplicate key, global xpub already provided"); + } + global_xpubs.insert(xpub); + // Read in the keypath from stream + KeyOriginInfo keypath; + DeserializeHDKeypath(s, keypath); + + // Note that we store these swapped to make searches faster. + // Serialization uses xpub -> keypath to enqure key uniqueness + if (m_xpubs.count(keypath) == 0) { + // Make a new set to put the xpub in + m_xpubs[keypath] = {xpub}; + } else { + // Insert xpub into existing set + m_xpubs[keypath].insert(xpub); + } + break; + } + case PSBT_GLOBAL_VERSION: + { + if (m_version) { + throw std::ios_base::failure("Duplicate Key, version already provided"); + } else if (key.size() != 1) { + throw std::ios_base::failure("Global version key is more than one byte type"); + } + uint32_t v; + UnserializeFromVector(s, v); + m_version = v; + if (*m_version > PSBT_HIGHEST_VERSION) { + throw std::ios_base::failure("Unsupported version number"); + } + break; + } + case PSBT_GLOBAL_PROPRIETARY: + { + PSBTProprietary this_prop; + skey >> this_prop.identifier; + this_prop.subtype = ReadCompactSize(skey); + this_prop.key = key; + + if (m_proprietary.count(this_prop) > 0) { + throw std::ios_base::failure("Duplicate Key, proprietary key already found"); + } + s >> this_prop.value; + m_proprietary.insert(this_prop); + break; + } // Unknown stuff default: { if (unknown.count(key) > 0) { diff --git a/src/pubkey.cpp b/src/pubkey.cpp index 956ff2b34a..b7dfb6d83f 100644 --- a/src/pubkey.cpp +++ b/src/pubkey.cpp @@ -352,6 +352,18 @@ void CExtPubKey::Decode(const unsigned char code[BIP32_EXTKEY_SIZE]) { if ((nDepth == 0 && (nChild != 0 || ReadLE32(vchFingerprint) != 0)) || !pubkey.IsFullyValid()) pubkey = CPubKey(); } +void CExtPubKey::EncodeWithVersion(unsigned char code[BIP32_EXTKEY_WITH_VERSION_SIZE]) const +{ + memcpy(code, version, 4); + Encode(&code[4]); +} + +void CExtPubKey::DecodeWithVersion(const unsigned char code[BIP32_EXTKEY_WITH_VERSION_SIZE]) +{ + memcpy(version, code, 4); + Decode(&code[4]); +} + bool CExtPubKey::Derive(CExtPubKey &out, unsigned int _nChild) const { out.nDepth = nDepth + 1; CKeyID id = pubkey.GetID(); diff --git a/src/pubkey.h b/src/pubkey.h index 2453c92d92..204e96f49e 100644 --- a/src/pubkey.h +++ b/src/pubkey.h @@ -17,6 +17,7 @@ #include <vector> const unsigned int BIP32_EXTKEY_SIZE = 74; +const unsigned int BIP32_EXTKEY_WITH_VERSION_SIZE = 78; /** A reference to a CKey: the Hash160 of its serialized public key */ class CKeyID : public uint160 @@ -129,6 +130,11 @@ public: return a.vch[0] < b.vch[0] || (a.vch[0] == b.vch[0] && memcmp(a.vch, b.vch, a.size()) < 0); } + friend bool operator>(const CPubKey& a, const CPubKey& b) + { + return a.vch[0] > b.vch[0] || + (a.vch[0] == b.vch[0] && memcmp(a.vch, b.vch, a.size()) > 0); + } //! Implement serialization, as if this was a byte vector. template <typename Stream> @@ -283,6 +289,7 @@ public: }; struct CExtPubKey { + unsigned char version[4]; unsigned char nDepth; unsigned char vchFingerprint[4]; unsigned int nChild; @@ -303,8 +310,20 @@ struct CExtPubKey { return !(a == b); } + friend bool operator<(const CExtPubKey &a, const CExtPubKey &b) + { + if (a.pubkey < b.pubkey) { + return true; + } else if (a.pubkey > b.pubkey) { + return false; + } + return a.chaincode < b.chaincode; + } + void Encode(unsigned char code[BIP32_EXTKEY_SIZE]) const; void Decode(const unsigned char code[BIP32_EXTKEY_SIZE]); + void EncodeWithVersion(unsigned char code[BIP32_EXTKEY_WITH_VERSION_SIZE]) const; + void DecodeWithVersion(const unsigned char code[BIP32_EXTKEY_WITH_VERSION_SIZE]); bool Derive(CExtPubKey& out, unsigned int nChild) const; }; diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index a5ee538f67..2761c69167 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -114,6 +114,10 @@ static RPCHelpMan createmultisig() {RPCResult::Type::STR, "address", "The value of the new multisig address."}, {RPCResult::Type::STR_HEX, "redeemScript", "The string value of the hex-encoded redemption script."}, {RPCResult::Type::STR, "descriptor", "The descriptor for this multisig"}, + {RPCResult::Type::ARR, "warnings", /* optional */ true, "Any warnings resulting from the creation of this multisig", + { + {RPCResult::Type::STR, "", ""}, + }}, } }, RPCExamples{ @@ -162,6 +166,13 @@ static RPCHelpMan createmultisig() result.pushKV("redeemScript", HexStr(inner)); result.pushKV("descriptor", descriptor->ToString()); + UniValue warnings(UniValue::VARR); + if (!request.params[2].isNull() && OutputTypeFromDestination(dest) != output_type) { + // Only warns if the user has explicitly chosen an address type we cannot generate + warnings.push_back("Unable to make chosen address type, please ensure no uncompressed public keys are present."); + } + if (warnings.size()) result.pushKV("warnings", warnings); + return result; }, }; diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 78c1596219..f2e8104e14 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -3,6 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <base58.h> #include <chain.h> #include <coins.h> #include <consensus/amount.h> @@ -1075,6 +1076,26 @@ static RPCHelpMan decodepsbt() { {RPCResult::Type::ELISION, "", "The layout is the same as the output of decoderawtransaction."}, }}, + {RPCResult::Type::ARR, "global_xpubs", "", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR, "xpub", "The extended public key this path corresponds to"}, + {RPCResult::Type::STR_HEX, "master_fingerprint", "The fingerprint of the master key"}, + {RPCResult::Type::STR, "path", "The path"}, + }}, + }}, + {RPCResult::Type::NUM, "psbt_version", "The PSBT version number. Not to be confused with the unsigned transaction version"}, + {RPCResult::Type::ARR, "proprietary", "The global proprietary map", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR_HEX, "identifier", "The hex string for the proprietary identifier"}, + {RPCResult::Type::NUM, "subtype", "The number for the subtype"}, + {RPCResult::Type::STR_HEX, "key", "The hex for the key"}, + {RPCResult::Type::STR_HEX, "value", "The hex for the value"}, + }}, + }}, {RPCResult::Type::OBJ_DYN, "unknown", "The unknown global fields", { {RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"}, @@ -1133,10 +1154,36 @@ static RPCHelpMan decodepsbt() { {RPCResult::Type::STR_HEX, "", "hex-encoded witness data (if any)"}, }}, - {RPCResult::Type::OBJ_DYN, "unknown", /*optional=*/true, "The unknown global fields", + {RPCResult::Type::OBJ_DYN, "ripemd160_preimages", /*optional=*/ true, "", + { + {RPCResult::Type::STR, "hash", "The hash and preimage that corresponds to it."}, + }}, + {RPCResult::Type::OBJ_DYN, "sha256_preimages", /*optional=*/ true, "", + { + {RPCResult::Type::STR, "hash", "The hash and preimage that corresponds to it."}, + }}, + {RPCResult::Type::OBJ_DYN, "hash160_preimages", /*optional=*/ true, "", + { + {RPCResult::Type::STR, "hash", "The hash and preimage that corresponds to it."}, + }}, + {RPCResult::Type::OBJ_DYN, "hash256_preimages", /*optional=*/ true, "", + { + {RPCResult::Type::STR, "hash", "The hash and preimage that corresponds to it."}, + }}, + {RPCResult::Type::OBJ_DYN, "unknown", /*optional=*/ true, "The unknown input fields", { {RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"}, }}, + {RPCResult::Type::ARR, "proprietary", "The input proprietary map", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR_HEX, "identifier", "The hex string for the proprietary identifier"}, + {RPCResult::Type::NUM, "subtype", "The number for the subtype"}, + {RPCResult::Type::STR_HEX, "key", "The hex for the key"}, + {RPCResult::Type::STR_HEX, "value", "The hex for the value"}, + }}, + }}, }}, }}, {RPCResult::Type::ARR, "outputs", "", @@ -1168,6 +1215,16 @@ static RPCHelpMan decodepsbt() { {RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"}, }}, + {RPCResult::Type::ARR, "proprietary", "The output proprietary map", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR_HEX, "identifier", "The hex string for the proprietary identifier"}, + {RPCResult::Type::NUM, "subtype", "The number for the subtype"}, + {RPCResult::Type::STR_HEX, "key", "The hex for the key"}, + {RPCResult::Type::STR_HEX, "value", "The hex for the value"}, + }}, + }}, }}, }}, {RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true, "The transaction fee paid if all UTXOs slots in the PSBT have been filled."}, @@ -1194,6 +1251,38 @@ static RPCHelpMan decodepsbt() TxToUniv(CTransaction(*psbtx.tx), uint256(), tx_univ, false); result.pushKV("tx", tx_univ); + // Add the global xpubs + UniValue global_xpubs(UniValue::VARR); + for (std::pair<KeyOriginInfo, std::set<CExtPubKey>> xpub_pair : psbtx.m_xpubs) { + for (auto& xpub : xpub_pair.second) { + std::vector<unsigned char> ser_xpub; + ser_xpub.assign(BIP32_EXTKEY_WITH_VERSION_SIZE, 0); + xpub.EncodeWithVersion(ser_xpub.data()); + + UniValue keypath(UniValue::VOBJ); + keypath.pushKV("xpub", EncodeBase58Check(ser_xpub)); + keypath.pushKV("master_fingerprint", HexStr(Span<unsigned char>(xpub_pair.first.fingerprint, xpub_pair.first.fingerprint + 4))); + keypath.pushKV("path", WriteHDKeypath(xpub_pair.first.path)); + global_xpubs.push_back(keypath); + } + } + result.pushKV("global_xpubs", global_xpubs); + + // PSBT version + result.pushKV("psbt_version", static_cast<uint64_t>(psbtx.GetVersion())); + + // Proprietary + UniValue proprietary(UniValue::VARR); + for (const auto& entry : psbtx.m_proprietary) { + UniValue this_prop(UniValue::VOBJ); + this_prop.pushKV("identifier", HexStr(entry.identifier)); + this_prop.pushKV("subtype", entry.subtype); + this_prop.pushKV("key", HexStr(entry.key)); + this_prop.pushKV("value", HexStr(entry.value)); + proprietary.push_back(this_prop); + } + result.pushKV("proprietary", proprietary); + // Unknown data UniValue unknowns(UniValue::VOBJ); for (auto entry : psbtx.unknown) { @@ -1300,6 +1389,56 @@ static RPCHelpMan decodepsbt() in.pushKV("final_scriptwitness", txinwitness); } + // Ripemd160 hash preimages + if (!input.ripemd160_preimages.empty()) { + UniValue ripemd160_preimages(UniValue::VOBJ); + for (const auto& [hash, preimage] : input.ripemd160_preimages) { + ripemd160_preimages.pushKV(HexStr(hash), HexStr(preimage)); + } + in.pushKV("ripemd160_preimages", ripemd160_preimages); + } + + // Sha256 hash preimages + if (!input.sha256_preimages.empty()) { + UniValue sha256_preimages(UniValue::VOBJ); + for (const auto& [hash, preimage] : input.sha256_preimages) { + sha256_preimages.pushKV(HexStr(hash), HexStr(preimage)); + } + in.pushKV("sha256_preimages", sha256_preimages); + } + + // Hash160 hash preimages + if (!input.hash160_preimages.empty()) { + UniValue hash160_preimages(UniValue::VOBJ); + for (const auto& [hash, preimage] : input.hash160_preimages) { + hash160_preimages.pushKV(HexStr(hash), HexStr(preimage)); + } + in.pushKV("hash160_preimages", hash160_preimages); + } + + // Hash256 hash preimages + if (!input.hash256_preimages.empty()) { + UniValue hash256_preimages(UniValue::VOBJ); + for (const auto& [hash, preimage] : input.hash256_preimages) { + hash256_preimages.pushKV(HexStr(hash), HexStr(preimage)); + } + in.pushKV("hash256_preimages", hash256_preimages); + } + + // Proprietary + if (!input.m_proprietary.empty()) { + UniValue proprietary(UniValue::VARR); + for (const auto& entry : input.m_proprietary) { + UniValue this_prop(UniValue::VOBJ); + this_prop.pushKV("identifier", HexStr(entry.identifier)); + this_prop.pushKV("subtype", entry.subtype); + this_prop.pushKV("key", HexStr(entry.key)); + this_prop.pushKV("value", HexStr(entry.value)); + proprietary.push_back(this_prop); + } + in.pushKV("proprietary", proprietary); + } + // Unknown data if (input.unknown.size() > 0) { UniValue unknowns(UniValue::VOBJ); @@ -1344,6 +1483,20 @@ static RPCHelpMan decodepsbt() out.pushKV("bip32_derivs", keypaths); } + // Proprietary + if (!output.m_proprietary.empty()) { + UniValue proprietary(UniValue::VARR); + for (const auto& entry : output.m_proprietary) { + UniValue this_prop(UniValue::VOBJ); + this_prop.pushKV("identifier", HexStr(entry.identifier)); + this_prop.pushKV("subtype", entry.subtype); + this_prop.pushKV("key", HexStr(entry.key)); + this_prop.pushKV("value", HexStr(entry.value)); + proprietary.push_back(this_prop); + } + out.pushKV("proprietary", proprietary); + } + // Unknown data if (output.unknown.size() > 0) { UniValue unknowns(UniValue::VOBJ); @@ -1757,6 +1910,13 @@ static RPCHelpMan joinpsbts() for (unsigned int i = 0; i < psbt.tx->vout.size(); ++i) { merged_psbt.AddOutput(psbt.tx->vout[i], psbt.outputs[i]); } + for (auto& xpub_pair : psbt.m_xpubs) { + if (merged_psbt.m_xpubs.count(xpub_pair.first) == 0) { + merged_psbt.m_xpubs[xpub_pair.first] = xpub_pair.second; + } else { + merged_psbt.m_xpubs[xpub_pair.first].insert(xpub_pair.second.begin(), xpub_pair.second.end()); + } + } merged_psbt.unknown.insert(psbt.unknown.begin(), psbt.unknown.end()); } diff --git a/src/script/keyorigin.h b/src/script/keyorigin.h index 210395d177..c779872be2 100644 --- a/src/script/keyorigin.h +++ b/src/script/keyorigin.h @@ -18,6 +18,25 @@ struct KeyOriginInfo return std::equal(std::begin(a.fingerprint), std::end(a.fingerprint), std::begin(b.fingerprint)) && a.path == b.path; } + friend bool operator<(const KeyOriginInfo& a, const KeyOriginInfo& b) + { + // Compare the fingerprints lexicographically + int fpr_cmp = memcmp(a.fingerprint, b.fingerprint, 4); + if (fpr_cmp < 0) { + return true; + } else if (fpr_cmp > 0) { + return false; + } + // Compare the sizes of the paths, shorter is "less than" + if (a.path.size() < b.path.size()) { + return true; + } else if (a.path.size() > b.path.size()) { + return false; + } + // Paths same length, compare them lexicographically + return a.path < b.path; + } + SERIALIZE_METHODS(KeyOriginInfo, obj) { READWRITE(obj.fingerprint, obj.path); } void clear() diff --git a/src/script/sign.cpp b/src/script/sign.cpp index 3f7060879c..d33c847d98 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -9,6 +9,7 @@ #include <key.h> #include <policy/policy.h> #include <primitives/transaction.h> +#include <script/keyorigin.h> #include <script/signingprovider.h> #include <script/standard.h> #include <uint256.h> diff --git a/src/script/sign.h b/src/script/sign.h index 50525af332..7e3d5e80e4 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -12,8 +12,6 @@ #include <script/interpreter.h> #include <script/keyorigin.h> #include <script/standard.h> -#include <span.h> -#include <streams.h> class CKey; class CKeyID; @@ -84,80 +82,6 @@ struct SignatureData { void MergeSignatureData(SignatureData sigdata); }; -// Takes a stream and multiple arguments and serializes them as if first serialized into a vector and then into the stream -// The resulting output into the stream has the total serialized length of all of the objects followed by all objects concatenated with each other. -template<typename Stream, typename... X> -void SerializeToVector(Stream& s, const X&... args) -{ - WriteCompactSize(s, GetSerializeSizeMany(s.GetVersion(), args...)); - SerializeMany(s, args...); -} - -// Takes a stream and multiple arguments and unserializes them first as a vector then each object individually in the order provided in the arguments -template<typename Stream, typename... X> -void UnserializeFromVector(Stream& s, X&... args) -{ - size_t expected_size = ReadCompactSize(s); - size_t remaining_before = s.size(); - UnserializeMany(s, args...); - size_t remaining_after = s.size(); - if (remaining_after + expected_size != remaining_before) { - throw std::ios_base::failure("Size of value was not the stated size"); - } -} - -// Deserialize HD keypaths into a map -template<typename Stream> -void DeserializeHDKeypaths(Stream& s, const std::vector<unsigned char>& key, std::map<CPubKey, KeyOriginInfo>& hd_keypaths) -{ - // Make sure that the key is the size of pubkey + 1 - if (key.size() != CPubKey::SIZE + 1 && key.size() != CPubKey::COMPRESSED_SIZE + 1) { - throw std::ios_base::failure("Size of key was not the expected size for the type BIP32 keypath"); - } - // Read in the pubkey from key - CPubKey pubkey(key.begin() + 1, key.end()); - if (!pubkey.IsFullyValid()) { - throw std::ios_base::failure("Invalid pubkey"); - } - if (hd_keypaths.count(pubkey) > 0) { - throw std::ios_base::failure("Duplicate Key, pubkey derivation path already provided"); - } - - // Read in key path - uint64_t value_len = ReadCompactSize(s); - if (value_len % 4 || value_len == 0) { - throw std::ios_base::failure("Invalid length for HD key path"); - } - - KeyOriginInfo keypath; - s >> keypath.fingerprint; - for (unsigned int i = 4; i < value_len; i += sizeof(uint32_t)) { - uint32_t index; - s >> index; - keypath.path.push_back(index); - } - - // Add to map - hd_keypaths.emplace(pubkey, std::move(keypath)); -} - -// Serialize HD keypaths to a stream from a map -template<typename Stream> -void SerializeHDKeypaths(Stream& s, const std::map<CPubKey, KeyOriginInfo>& hd_keypaths, uint8_t type) -{ - for (auto keypath_pair : hd_keypaths) { - if (!keypath_pair.first.IsValid()) { - throw std::ios_base::failure("Invalid CPubKey being serialized"); - } - SerializeToVector(s, type, Span{keypath_pair.first}); - WriteCompactSize(s, (keypath_pair.second.path.size() + 1) * sizeof(uint32_t)); - s << keypath_pair.second.fingerprint; - for (const auto& path : keypath_pair.second.path) { - s << path; - } - } -} - /** Produce a script signature using a generic signature creator. */ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreator& creator, const CScript& scriptPubKey, SignatureData& sigdata); diff --git a/src/script/signingprovider.h b/src/script/signingprovider.h index fbce61c6a9..b8b3e03dd3 100644 --- a/src/script/signingprovider.h +++ b/src/script/signingprovider.h @@ -8,12 +8,11 @@ #include <key.h> #include <pubkey.h> +#include <script/keyorigin.h> #include <script/script.h> #include <script/standard.h> #include <sync.h> -struct KeyOriginInfo; - /** An interface to be implemented by keystores that support signing. */ class SigningProvider { diff --git a/src/serialize.h b/src/serialize.h index edf10440c6..873361fe9e 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -527,6 +527,19 @@ struct CompactSizeFormatter } }; +class CompactSizeWriter +{ +protected: + uint64_t n; +public: + explicit CompactSizeWriter(uint64_t n_in) : n(n_in) { } + + template<typename Stream> + void Serialize(Stream &s) const { + WriteCompactSize<Stream>(s, n); + } +}; + template<size_t Limit> struct LimitedStringFormatter { diff --git a/src/test/fuzz/integer.cpp b/src/test/fuzz/integer.cpp index b6c40809e3..ce424c443e 100644 --- a/src/test/fuzz/integer.cpp +++ b/src/test/fuzz/integer.cpp @@ -29,12 +29,10 @@ #include <util/strencodings.h> #include <util/string.h> #include <util/system.h> -#include <util/time.h> #include <version.h> #include <cassert> #include <chrono> -#include <ctime> #include <limits> #include <set> #include <vector> @@ -81,8 +79,6 @@ FUZZ_TARGET_INIT(integer, initialize_integer) (void)ComputeMerkleRoot(v256); (void)CountBits(u64); (void)DecompressAmount(u64); - (void)FormatISO8601Date(i64); - (void)FormatISO8601DateTime(i64); { if (std::optional<CAmount> parsed = ParseMoney(FormatMoney(i64))) { assert(parsed.value() == i64); diff --git a/src/test/fuzz/parse_iso8601.cpp b/src/test/fuzz/parse_iso8601.cpp index a56f2aa48d..3c56fa49ee 100644 --- a/src/test/fuzz/parse_iso8601.cpp +++ b/src/test/fuzz/parse_iso8601.cpp @@ -19,6 +19,7 @@ FUZZ_TARGET(parse_iso8601) const std::string random_string = fuzzed_data_provider.ConsumeRemainingBytesAsString(); const std::string iso8601_datetime = FormatISO8601DateTime(random_time); + (void)FormatISO8601Date(random_time); const int64_t parsed_time_1 = ParseISO8601DateTime(iso8601_datetime); if (random_time >= 0) { assert(parsed_time_1 >= 0); diff --git a/src/test/fuzz/script_sign.cpp b/src/test/fuzz/script_sign.cpp index 79380bd9c9..1a42179724 100644 --- a/src/test/fuzz/script_sign.cpp +++ b/src/test/fuzz/script_sign.cpp @@ -5,6 +5,7 @@ #include <chainparams.h> #include <chainparamsbase.h> #include <key.h> +#include <psbt.h> #include <pubkey.h> #include <script/keyorigin.h> #include <script/sign.h> @@ -43,7 +44,7 @@ FUZZ_TARGET_INIT(script_sign, initialize_script_sign) } catch (const std::ios_base::failure&) { } CDataStream serialized{SER_NETWORK, PROTOCOL_VERSION}; - SerializeHDKeypaths(serialized, hd_keypaths, fuzzed_data_provider.ConsumeIntegral<uint8_t>()); + SerializeHDKeypaths(serialized, hd_keypaths, CompactSizeWriter(fuzzed_data_provider.ConsumeIntegral<uint8_t>())); } { @@ -61,7 +62,7 @@ FUZZ_TARGET_INIT(script_sign, initialize_script_sign) } CDataStream serialized{SER_NETWORK, PROTOCOL_VERSION}; try { - SerializeHDKeypaths(serialized, hd_keypaths, fuzzed_data_provider.ConsumeIntegral<uint8_t>()); + SerializeHDKeypaths(serialized, hd_keypaths, CompactSizeWriter(fuzzed_data_provider.ConsumeIntegral<uint8_t>())); } catch (const std::ios_base::failure&) { } std::map<CPubKey, KeyOriginInfo> deserialized_hd_keypaths; diff --git a/src/wallet/rpc/addresses.cpp b/src/wallet/rpc/addresses.cpp index e570c18099..c7400cafe7 100644 --- a/src/wallet/rpc/addresses.cpp +++ b/src/wallet/rpc/addresses.cpp @@ -238,6 +238,10 @@ RPCHelpMan addmultisigaddress() {RPCResult::Type::STR, "address", "The value of the new multisig address"}, {RPCResult::Type::STR_HEX, "redeemScript", "The string value of the hex-encoded redemption script"}, {RPCResult::Type::STR, "descriptor", "The descriptor for this multisig"}, + {RPCResult::Type::ARR, "warnings", /* optional */ true, "Any warnings resulting from the creation of this multisig", + { + {RPCResult::Type::STR, "", ""}, + }}, } }, RPCExamples{ @@ -295,6 +299,14 @@ RPCHelpMan addmultisigaddress() result.pushKV("address", EncodeDestination(dest)); result.pushKV("redeemScript", HexStr(inner)); result.pushKV("descriptor", descriptor->ToString()); + + UniValue warnings(UniValue::VARR); + if (!request.params[3].isNull() && OutputTypeFromDestination(dest) != output_type) { + // Only warns if the user has explicitly chosen an address type we cannot generate + warnings.push_back("Unable to make chosen address type, please ensure no uncompressed public keys are present."); + } + if (warnings.size()) result.pushKV("warnings", warnings); + return result; }, }; |