aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/psbt.cpp15
-rw-r--r--src/psbt.h290
-rw-r--r--src/pubkey.cpp12
-rw-r--r--src/pubkey.h19
-rw-r--r--src/rpc/rawtransaction.cpp108
-rw-r--r--src/script/keyorigin.h19
-rw-r--r--src/script/sign.cpp1
-rw-r--r--src/script/sign.h76
-rw-r--r--src/script/signingprovider.h3
-rw-r--r--src/serialize.h13
-rw-r--r--src/test/fuzz/script_sign.cpp5
-rw-r--r--test/functional/data/rpc_psbt.json9
12 files changed, 469 insertions, 101 deletions
diff --git a/src/psbt.cpp b/src/psbt.cpp
index 6585766807..4c9b439815 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;
@@ -401,3 +408,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..690d1b3bbd 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,13 @@ 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_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 +53,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
{
@@ -57,6 +172,7 @@ struct PSBTInput
std::map<CPubKey, KeyOriginInfo> hd_keypaths;
std::map<CKeyID, SigPair> partial_sigs;
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 +185,61 @@ 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 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 +269,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 +373,20 @@ struct PSBTInput
UnserializeFromVector(s, final_script_witness.stack);
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 +418,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 +430,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 +478,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 +509,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 +552,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 +587,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 +649,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 +666,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 +692,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/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 78c1596219..12ade466da 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"},
@@ -1137,6 +1158,16 @@ static RPCHelpMan decodepsbt()
{
{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 +1199,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 +1235,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 +1373,20 @@ static RPCHelpMan decodepsbt()
in.pushKV("final_scriptwitness", txinwitness);
}
+ // 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 +1431,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 +1858,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/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/test/functional/data/rpc_psbt.json b/test/functional/data/rpc_psbt.json
index 0f6cd97fd8..dd24dce94d 100644
--- a/test/functional/data/rpc_psbt.json
+++ b/test/functional/data/rpc_psbt.json
@@ -19,6 +19,7 @@
"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",
@@ -34,7 +35,11 @@
"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",
- "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"
+ "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"
],
"creator" : [
{
@@ -136,4 +141,4 @@
"result" : "0200000000010258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7500000000da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752aeffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d01000000232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00000000"
}
]
-} \ No newline at end of file
+}