Skip to content

Commit b654723

Browse files
committed
Merge #13557: BIP 174 PSBT Serializations and RPCs
020628e Tests for PSBT (Andrew Chow) a4b06fb Create wallet RPCs for PSBT (Andrew Chow) c27fe41 Create utility RPCs for PSBT (Andrew Chow) 8b5ef27 SignPSBTInput wrapper function (Andrew Chow) 58a8e28 Refactor transaction creation and transaction funding logic (Andrew Chow) e9d86a4 Methods for interacting with PSBT structs (Andrew Chow) 12bcc64 Add pubkeys and whether input was witness to SignatureData (Andrew Chow) 41c607f Implement PSBT Structures and un/serialization methods per BIP 174 (Andrew Chow) Pull request description: This Pull Request fully implements the [updated](bitcoin/bips#694) BIP 174 specification. It is based upon #13425 which implements the majority of the signing logic. BIP 174 specifies a binary transaction format which contains the information necessary for a signer to produce signatures for the transaction and holds the signatures for an input while the input does not have a complete set of signatures. This PR contains structs for PSBT, serialization, and deserialzation code. Some changes to `SignatureData` have been made to support detection of UTXO type and storing public keys. *** Many RPCs have been added to handle PSBTs. `walletprocesspsbt` takes a PSBT format transaction, updates the PSBT with any inputs related to this wallet, signs, and finalizes the transaction. There is also an option to not sign and just update. `walletcreatefundedpsbt` creates a PSBT from user provided data in the same form as createrawtransaction. It also funds the transaction and takes an options argument in the same form as `fundrawtransaction`. The resulting PSBT is blank with no input or output data filled in. It is analogous to a combination of `createrawtransaction` and `fundrawtransaction` `decodepsbt` takes a PSBT and decodes it to JSON. It is analogous to `decoderawtransaction` `combinepsbt` takes multiple PSBTs for the same tx and combines them. It is analogous to `combinerawtransaction` `finalizepsbt` takes a PSBT and finalizes the inputs. If all inputs are final, it extracts the network serialized transaction and returns that instead of a PSBT unless instructed otherwise. `createpsbt` is like `createrawtransaction` but for PSBTs instead of raw transactions. `convertpsbt` takes a network serialized transaction and converts it into a psbt. The resulting psbt will lose all signature data and an explicit flag must be set to allow transactions with signature data to be converted. *** This supersedes #12136 Tree-SHA512: 1ac7a79e5bc669933f0a6fcc93ded55263fdde9e8c144a30266b13ef9f62aacf43edd4cbca1ffbe003090b067e9643c9298c79be69d7c1b10231b32acafb6338
2 parents 585db54 + 020628e commit b654723

16 files changed

+2247
-174
lines changed

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ BITCOIN_TESTS =\
9494
if ENABLE_WALLET
9595
BITCOIN_TESTS += \
9696
wallet/test/accounting_tests.cpp \
97+
wallet/test/psbt_wallet_tests.cpp \
9798
wallet/test/wallet_tests.cpp \
9899
wallet/test/wallet_crypto_tests.cpp \
99100
wallet/test/coinselector_tests.cpp

src/core_io.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class CBlock;
1414
class CScript;
1515
class CTransaction;
1616
struct CMutableTransaction;
17+
struct PartiallySignedTransaction;
1718
class uint256;
1819
class UniValue;
1920

@@ -24,12 +25,16 @@ bool DecodeHexTx(CMutableTransaction& tx, const std::string& hex_tx, bool try_no
2425
bool DecodeHexBlk(CBlock&, const std::string& strHexBlk);
2526
uint256 ParseHashStr(const std::string&, const std::string& strName);
2627
std::vector<unsigned char> ParseHexUV(const UniValue& v, const std::string& strName);
28+
bool DecodePSBT(PartiallySignedTransaction& psbt, const std::string& base64_tx, std::string& error);
29+
int ParseSighashString(const UniValue& sighash);
2730

2831
// core_write.cpp
2932
UniValue ValueFromAmount(const CAmount& amount);
3033
std::string FormatScript(const CScript& script);
3134
std::string EncodeHexTx(const CTransaction& tx, const int serializeFlags = 0);
35+
std::string SighashToStr(unsigned char sighash_type);
3236
void ScriptPubKeyToUniv(const CScript& scriptPubKey, UniValue& out, bool fIncludeHex);
37+
void ScriptToUniv(const CScript& script, UniValue& out, bool include_address);
3338
void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry, bool include_hex = true, int serialize_flags = 0);
3439

3540
#endif // BITCOIN_CORE_IO_H

src/core_read.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <primitives/block.h>
88
#include <primitives/transaction.h>
99
#include <script/script.h>
10+
#include <script/sign.h>
1011
#include <serialize.h>
1112
#include <streams.h>
1213
#include <univalue.h>
@@ -160,6 +161,23 @@ bool DecodeHexBlk(CBlock& block, const std::string& strHexBlk)
160161
return true;
161162
}
162163

164+
bool DecodePSBT(PartiallySignedTransaction& psbt, const std::string& base64_tx, std::string& error)
165+
{
166+
std::vector<unsigned char> tx_data = DecodeBase64(base64_tx.c_str());
167+
CDataStream ss_data(tx_data, SER_NETWORK, PROTOCOL_VERSION);
168+
try {
169+
ss_data >> psbt;
170+
if (!ss_data.empty()) {
171+
error = "extra data after PSBT";
172+
return false;
173+
}
174+
} catch (const std::exception& e) {
175+
error = e.what();
176+
return false;
177+
}
178+
return true;
179+
}
180+
163181
uint256 ParseHashStr(const std::string& strHex, const std::string& strName)
164182
{
165183
if (!IsHex(strHex)) // Note: IsHex("") is false
@@ -179,3 +197,26 @@ std::vector<unsigned char> ParseHexUV(const UniValue& v, const std::string& strN
179197
throw std::runtime_error(strName + " must be hexadecimal string (not '" + strHex + "')");
180198
return ParseHex(strHex);
181199
}
200+
201+
int ParseSighashString(const UniValue& sighash)
202+
{
203+
int hash_type = SIGHASH_ALL;
204+
if (!sighash.isNull()) {
205+
static std::map<std::string, int> map_sighash_values = {
206+
{std::string("ALL"), int(SIGHASH_ALL)},
207+
{std::string("ALL|ANYONECANPAY"), int(SIGHASH_ALL|SIGHASH_ANYONECANPAY)},
208+
{std::string("NONE"), int(SIGHASH_NONE)},
209+
{std::string("NONE|ANYONECANPAY"), int(SIGHASH_NONE|SIGHASH_ANYONECANPAY)},
210+
{std::string("SINGLE"), int(SIGHASH_SINGLE)},
211+
{std::string("SINGLE|ANYONECANPAY"), int(SIGHASH_SINGLE|SIGHASH_ANYONECANPAY)},
212+
};
213+
std::string strHashType = sighash.get_str();
214+
const auto& it = map_sighash_values.find(strHashType);
215+
if (it != map_sighash_values.end()) {
216+
hash_type = it->second;
217+
} else {
218+
throw std::runtime_error(strHashType + " is not a valid sighash parameter.");
219+
}
220+
}
221+
return hash_type;
222+
}

src/core_write.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,13 @@ const std::map<unsigned char, std::string> mapSigHashTypes = {
7070
{static_cast<unsigned char>(SIGHASH_SINGLE|SIGHASH_ANYONECANPAY), std::string("SINGLE|ANYONECANPAY")},
7171
};
7272

73+
std::string SighashToStr(unsigned char sighash_type)
74+
{
75+
const auto& it = mapSigHashTypes.find(sighash_type);
76+
if (it == mapSigHashTypes.end()) return "";
77+
return it->second;
78+
}
79+
7380
/**
7481
* Create the assembly string representation of a CScript object.
7582
* @param[in] script CScript object to convert into the asm string representation.
@@ -128,6 +135,22 @@ std::string EncodeHexTx(const CTransaction& tx, const int serializeFlags)
128135
return HexStr(ssTx.begin(), ssTx.end());
129136
}
130137

138+
void ScriptToUniv(const CScript& script, UniValue& out, bool include_address)
139+
{
140+
out.pushKV("asm", ScriptToAsmStr(script));
141+
out.pushKV("hex", HexStr(script.begin(), script.end()));
142+
143+
std::vector<std::vector<unsigned char>> solns;
144+
txnouttype type;
145+
Solver(script, type, solns);
146+
out.pushKV("type", GetTxnOutputType(type));
147+
148+
CTxDestination address;
149+
if (include_address && ExtractDestination(script, address)) {
150+
out.pushKV("address", EncodeDestination(address));
151+
}
152+
}
153+
131154
void ScriptPubKeyToUniv(const CScript& scriptPubKey,
132155
UniValue& out, bool fIncludeHex)
133156
{

src/pubkey.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ class CPubKey
107107

108108
//! Simple read-only vector-like interface to the pubkey data.
109109
unsigned int size() const { return GetLen(vch[0]); }
110+
const unsigned char* data() const { return vch; }
110111
const unsigned char* begin() const { return vch; }
111112
const unsigned char* end() const { return vch + size(); }
112113
const unsigned char& operator[](unsigned int pos) const { return vch[pos]; }

src/rpc/client.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,22 @@ static const CRPCConvertParam vRPCConvertParams[] =
110110
{ "combinerawtransaction", 0, "txs" },
111111
{ "fundrawtransaction", 1, "options" },
112112
{ "fundrawtransaction", 2, "iswitness" },
113+
{ "walletcreatefundedpsbt", 0, "inputs" },
114+
{ "walletcreatefundedpsbt", 1, "outputs" },
115+
{ "walletcreatefundedpsbt", 2, "locktime" },
116+
{ "walletcreatefundedpsbt", 3, "replaceable" },
117+
{ "walletcreatefundedpsbt", 4, "options" },
118+
{ "walletcreatefundedpsbt", 5, "bip32derivs" },
119+
{ "walletprocesspsbt", 1, "sign" },
120+
{ "walletprocesspsbt", 3, "bip32derivs" },
121+
{ "createpsbt", 0, "inputs" },
122+
{ "createpsbt", 1, "outputs" },
123+
{ "createpsbt", 2, "locktime" },
124+
{ "createpsbt", 3, "replaceable" },
125+
{ "combinepsbt", 0, "txs"},
126+
{ "finalizepsbt", 1, "extract"},
127+
{ "converttopsbt", 1, "permitsigdata"},
128+
{ "converttopsbt", 2, "iswitness"},
113129
{ "gettxout", 1, "n" },
114130
{ "gettxout", 2, "include_mempool" },
115131
{ "gettxoutproof", 0, "txids" },

0 commit comments

Comments
 (0)