1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
|
// Copyright (c) 2009-2020 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_PSBT_H
#define BITCOIN_PSBT_H
#include <attributes.h>
#include <node/transaction.h>
#include <policy/feerate.h>
#include <primitives/transaction.h>
#include <pubkey.h>
#include <script/sign.h>
#include <script/signingprovider.h>
#include <optional>
// Magic bytes
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_VERSION = 0xFB;
// Input types
static constexpr uint8_t PSBT_IN_NON_WITNESS_UTXO = 0x00;
static constexpr uint8_t PSBT_IN_WITNESS_UTXO = 0x01;
static constexpr uint8_t PSBT_IN_PARTIAL_SIG = 0x02;
static constexpr uint8_t PSBT_IN_SIGHASH = 0x03;
static constexpr uint8_t PSBT_IN_REDEEMSCRIPT = 0x04;
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;
// 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;
// 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.
static constexpr uint8_t PSBT_SEPARATOR = 0x00;
// BIP 174 does not specify a maximum file size, but we set a limit anyway
// 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 PSBTs which contain per-input information */
struct PSBTInput
{
CTransactionRef non_witness_utxo;
CTxOut witness_utxo;
CScript redeem_script;
CScript witness_script;
CScript final_script_sig;
CScriptWitness final_script_witness;
std::map<CPubKey, KeyOriginInfo> hd_keypaths;
std::map<CKeyID, SigPair> partial_sigs;
std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown;
std::optional<int> sighash_type;
bool IsNull() const;
void FillSignatureData(SignatureData& sigdata) const;
void FromSignatureData(const SignatureData& sigdata);
void Merge(const PSBTInput& input);
PSBTInput() {}
template <typename Stream>
inline void Serialize(Stream& s) const {
// Write the utxo
if (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, 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, 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, CompactSizeWriter(PSBT_IN_SIGHASH));
SerializeToVector(s, *sighash_type);
}
// Write the redeem script
if (!redeem_script.empty()) {
SerializeToVector(s, CompactSizeWriter(PSBT_IN_REDEEMSCRIPT));
s << redeem_script;
}
// Write the witness script
if (!witness_script.empty()) {
SerializeToVector(s, CompactSizeWriter(PSBT_IN_WITNESSSCRIPT));
s << witness_script;
}
// Write any hd keypaths
SerializeHDKeypaths(s, hd_keypaths, CompactSizeWriter(PSBT_IN_BIP32_DERIVATION));
}
// Write script sig
if (!final_script_sig.empty()) {
SerializeToVector(s, CompactSizeWriter(PSBT_IN_SCRIPTSIG));
s << final_script_sig;
}
// write script witness
if (!final_script_witness.IsNull()) {
SerializeToVector(s, CompactSizeWriter(PSBT_IN_SCRIPTWITNESS));
SerializeToVector(s, final_script_witness.stack);
}
// Write unknown things
for (auto& entry : unknown) {
s << entry.first;
s << entry.second;
}
s << PSBT_SEPARATOR;
}
template <typename Stream>
inline void Unserialize(Stream& s) {
// Used for duplicate key detection
std::set<std::vector<unsigned char>> key_lookup;
// Read loop
bool found_sep = false;
while(!s.empty()) {
// Read
std::vector<unsigned char> key;
s >> key;
// the key is empty if that was actually a separator byte
// This is a special case for key lengths 0 as those are not allowed (except for separator)
if (key.empty()) {
found_sep = true;
break;
}
// 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) {
case PSBT_IN_NON_WITNESS_UTXO:
{
if (!key_lookup.emplace(key).second) {
throw std::ios_base::failure("Duplicate Key, input non-witness utxo already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Non-witness utxo key is more than one byte type");
}
// Set the stream to unserialize with witness since this is always a valid network transaction
OverrideStream<Stream> os(&s, s.GetType(), s.GetVersion() & ~SERIALIZE_TRANSACTION_NO_WITNESS);
UnserializeFromVector(os, non_witness_utxo);
break;
}
case PSBT_IN_WITNESS_UTXO:
if (!key_lookup.emplace(key).second) {
throw std::ios_base::failure("Duplicate Key, input witness utxo already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Witness utxo key is more than one byte type");
}
UnserializeFromVector(s, witness_utxo);
break;
case PSBT_IN_PARTIAL_SIG:
{
// 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 partial signature pubkey");
}
// Read in the pubkey from key
CPubKey pubkey(key.begin() + 1, key.end());
if (!pubkey.IsFullyValid()) {
throw std::ios_base::failure("Invalid pubkey");
}
if (partial_sigs.count(pubkey.GetID()) > 0) {
throw std::ios_base::failure("Duplicate Key, input partial signature for pubkey already provided");
}
// Read in the signature from value
std::vector<unsigned char> sig;
s >> sig;
// Add to list
partial_sigs.emplace(pubkey.GetID(), SigPair(pubkey, std::move(sig)));
break;
}
case PSBT_IN_SIGHASH:
if (!key_lookup.emplace(key).second) {
throw std::ios_base::failure("Duplicate Key, input sighash type already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Sighash type key is more than one byte type");
}
int sighash;
UnserializeFromVector(s, sighash);
sighash_type = sighash;
break;
case PSBT_IN_REDEEMSCRIPT:
{
if (!key_lookup.emplace(key).second) {
throw std::ios_base::failure("Duplicate Key, input redeemScript already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Input redeemScript key is more than one byte type");
}
s >> redeem_script;
break;
}
case PSBT_IN_WITNESSSCRIPT:
{
if (!key_lookup.emplace(key).second) {
throw std::ios_base::failure("Duplicate Key, input witnessScript already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Input witnessScript key is more than one byte type");
}
s >> witness_script;
break;
}
case PSBT_IN_BIP32_DERIVATION:
{
DeserializeHDKeypaths(s, key, hd_keypaths);
break;
}
case PSBT_IN_SCRIPTSIG:
{
if (!key_lookup.emplace(key).second) {
throw std::ios_base::failure("Duplicate Key, input final scriptSig already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Final scriptSig key is more than one byte type");
}
s >> final_script_sig;
break;
}
case PSBT_IN_SCRIPTWITNESS:
{
if (!key_lookup.emplace(key).second) {
throw std::ios_base::failure("Duplicate Key, input final scriptWitness already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Final scriptWitness key is more than one byte type");
}
UnserializeFromVector(s, final_script_witness.stack);
break;
}
// Unknown stuff
default:
if (unknown.count(key) > 0) {
throw std::ios_base::failure("Duplicate Key, key for unknown value already provided");
}
// Read in the value
std::vector<unsigned char> val_bytes;
s >> val_bytes;
unknown.emplace(std::move(key), std::move(val_bytes));
break;
}
}
if (!found_sep) {
throw std::ios_base::failure("Separator is missing at the end of an input map");
}
}
template <typename Stream>
PSBTInput(deserialize_type, Stream& s) {
Unserialize(s);
}
};
/** A structure for PSBTs which contains per output information */
struct PSBTOutput
{
CScript redeem_script;
CScript witness_script;
std::map<CPubKey, KeyOriginInfo> hd_keypaths;
std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown;
bool IsNull() const;
void FillSignatureData(SignatureData& sigdata) const;
void FromSignatureData(const SignatureData& sigdata);
void Merge(const PSBTOutput& output);
PSBTOutput() {}
template <typename Stream>
inline void Serialize(Stream& s) const {
// Write the redeem script
if (!redeem_script.empty()) {
SerializeToVector(s, CompactSizeWriter(PSBT_OUT_REDEEMSCRIPT));
s << redeem_script;
}
// Write the witness script
if (!witness_script.empty()) {
SerializeToVector(s, CompactSizeWriter(PSBT_OUT_WITNESSSCRIPT));
s << witness_script;
}
// Write any hd keypaths
SerializeHDKeypaths(s, hd_keypaths, CompactSizeWriter(PSBT_OUT_BIP32_DERIVATION));
// Write unknown things
for (auto& entry : unknown) {
s << entry.first;
s << entry.second;
}
s << PSBT_SEPARATOR;
}
template <typename Stream>
inline void Unserialize(Stream& s) {
// Used for duplicate key detection
std::set<std::vector<unsigned char>> key_lookup;
// Read loop
bool found_sep = false;
while(!s.empty()) {
// Read
std::vector<unsigned char> key;
s >> key;
// the key is empty if that was actually a separator byte
// This is a special case for key lengths 0 as those are not allowed (except for separator)
if (key.empty()) {
found_sep = true;
break;
}
// 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) {
case PSBT_OUT_REDEEMSCRIPT:
{
if (!key_lookup.emplace(key).second) {
throw std::ios_base::failure("Duplicate Key, output redeemScript already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Output redeemScript key is more than one byte type");
}
s >> redeem_script;
break;
}
case PSBT_OUT_WITNESSSCRIPT:
{
if (!key_lookup.emplace(key).second) {
throw std::ios_base::failure("Duplicate Key, output witnessScript already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Output witnessScript key is more than one byte type");
}
s >> witness_script;
break;
}
case PSBT_OUT_BIP32_DERIVATION:
{
DeserializeHDKeypaths(s, key, hd_keypaths);
break;
}
// Unknown stuff
default: {
if (unknown.count(key) > 0) {
throw std::ios_base::failure("Duplicate Key, key for unknown value already provided");
}
// Read in the value
std::vector<unsigned char> val_bytes;
s >> val_bytes;
unknown.emplace(std::move(key), std::move(val_bytes));
break;
}
}
}
if (!found_sep) {
throw std::ios_base::failure("Separator is missing at the end of an output map");
}
}
template <typename Stream>
PSBTOutput(deserialize_type, Stream& s) {
Unserialize(s);
}
};
/** A version of CTransaction with the PSBT format*/
struct PartiallySignedTransaction
{
std::optional<CMutableTransaction> tx;
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;
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. */
[[nodiscard]] bool Merge(const PartiallySignedTransaction& psbt);
bool AddInput(const CTxIn& txin, PSBTInput& psbtin);
bool AddOutput(const CTxOut& txout, const PSBTOutput& psbtout);
PartiallySignedTransaction() {}
explicit PartiallySignedTransaction(const CMutableTransaction& tx);
/**
* Finds the UTXO for a given input index
*
* @param[out] utxo The UTXO of the input if found
* @param[in] input_index Index of the input to retrieve the UTXO of
* @return Whether the UTXO for the specified input was found
*/
bool GetInputUTXO(CTxOut& utxo, int input_index) const;
template <typename Stream>
inline void Serialize(Stream& s) const {
// magic bytes
s << PSBT_MAGIC_BYTES;
// unsigned tx flag
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);
// PSBT version
if (GetVersion() > 0) {
SerializeToVector(s, CompactSizeWriter(PSBT_GLOBAL_VERSION));
SerializeToVector(s, *m_version);
}
// Write the unknown things
for (auto& entry : unknown) {
s << entry.first;
s << entry.second;
}
// Separator
s << PSBT_SEPARATOR;
// Write inputs
for (const PSBTInput& input : inputs) {
s << input;
}
// Write outputs
for (const PSBTOutput& output : outputs) {
s << output;
}
}
template <typename Stream>
inline void Unserialize(Stream& s) {
// Read the magic bytes
uint8_t magic[5];
s >> magic;
if (!std::equal(magic, magic + 5, PSBT_MAGIC_BYTES)) {
throw std::ios_base::failure("Invalid PSBT magic bytes");
}
// Used for duplicate key detection
std::set<std::vector<unsigned char>> key_lookup;
// Read global data
bool found_sep = false;
while(!s.empty()) {
// Read
std::vector<unsigned char> key;
s >> key;
// the key is empty if that was actually a separator byte
// This is a special case for key lengths 0 as those are not allowed (except for separator)
if (key.empty()) {
found_sep = true;
break;
}
// 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) {
case PSBT_GLOBAL_UNSIGNED_TX:
{
if (!key_lookup.emplace(key).second) {
throw std::ios_base::failure("Duplicate Key, unsigned tx already provided");
} else if (key.size() != 1) {
throw std::ios_base::failure("Global unsigned tx key is more than one byte type");
}
CMutableTransaction mtx;
// Set the stream to serialize with non-witness since this should always be non-witness
OverrideStream<Stream> os(&s, s.GetType(), s.GetVersion() | SERIALIZE_TRANSACTION_NO_WITNESS);
UnserializeFromVector(os, mtx);
tx = std::move(mtx);
// Make sure that all scriptSigs and scriptWitnesses are empty
for (const CTxIn& txin : tx->vin) {
if (!txin.scriptSig.empty() || !txin.scriptWitness.IsNull()) {
throw std::ios_base::failure("Unsigned tx does not have empty scriptSigs and scriptWitnesses.");
}
}
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;
}
// Unknown stuff
default: {
if (unknown.count(key) > 0) {
throw std::ios_base::failure("Duplicate Key, key for unknown value already provided");
}
// Read in the value
std::vector<unsigned char> val_bytes;
s >> val_bytes;
unknown.emplace(std::move(key), std::move(val_bytes));
}
}
}
if (!found_sep) {
throw std::ios_base::failure("Separator is missing at the end of the global map");
}
// Make sure that we got an unsigned tx
if (!tx) {
throw std::ios_base::failure("No unsigned transcation was provided");
}
// Read input data
unsigned int i = 0;
while (!s.empty() && i < tx->vin.size()) {
PSBTInput input;
s >> input;
inputs.push_back(input);
// Make sure the non-witness utxo matches the outpoint
if (input.non_witness_utxo && input.non_witness_utxo->GetHash() != tx->vin[i].prevout.hash) {
throw std::ios_base::failure("Non-witness UTXO does not match outpoint hash");
}
++i;
}
// Make sure that the number of inputs matches the number of inputs in the transaction
if (inputs.size() != tx->vin.size()) {
throw std::ios_base::failure("Inputs provided does not match the number of inputs in transaction.");
}
// Read output data
i = 0;
while (!s.empty() && i < tx->vout.size()) {
PSBTOutput output;
s >> output;
outputs.push_back(output);
++i;
}
// Make sure that the number of outputs matches the number of outputs in the transaction
if (outputs.size() != tx->vout.size()) {
throw std::ios_base::failure("Outputs provided does not match the number of outputs in transaction.");
}
}
template <typename Stream>
PartiallySignedTransaction(deserialize_type, Stream& s) {
Unserialize(s);
}
};
enum class PSBTRole {
CREATOR,
UPDATER,
SIGNER,
FINALIZER,
EXTRACTOR
};
std::string PSBTRoleName(PSBTRole role);
/** Compute a PrecomputedTransactionData object from a psbt. */
PrecomputedTransactionData PrecomputePSBTData(const PartiallySignedTransaction& psbt);
/** Checks whether a PSBTInput is already signed. */
bool PSBTInputSigned(const PSBTInput& input);
/** Signs a PSBTInput, verifying that all provided data matches what is being signed.
*
* txdata should be the output of PrecomputePSBTData (which can be shared across
* multiple SignPSBTInput calls). If it is nullptr, a dummy signature will be created.
**/
bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, int sighash = SIGHASH_ALL, SignatureData* out_sigdata = nullptr, bool finalize = true);
/** Counts the unsigned inputs of a PSBT. */
size_t CountPSBTUnsignedInputs(const PartiallySignedTransaction& psbt);
/** Updates a PSBTOutput with information from provider.
*
* This fills in the redeem_script, witness_script, and hd_keypaths where possible.
*/
void UpdatePSBTOutput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index);
/**
* Finalizes a PSBT if possible, combining partial signatures.
*
* @param[in,out] psbtx PartiallySignedTransaction to finalize
* return True if the PSBT is now complete, false otherwise
*/
bool FinalizePSBT(PartiallySignedTransaction& psbtx);
/**
* Finalizes a PSBT if possible, and extracts it to a CMutableTransaction if it could be finalized.
*
* @param[in] psbtx PartiallySignedTransaction
* @param[out] result CMutableTransaction representing the complete transaction, if successful
* @return True if we successfully extracted the transaction, false otherwise
*/
bool FinalizeAndExtractPSBT(PartiallySignedTransaction& psbtx, CMutableTransaction& result);
/**
* Combines PSBTs with the same underlying transaction, resulting in a single PSBT with all partial signatures from each input.
*
* @param[out] out the combined PSBT, if successful
* @param[in] psbtxs the PSBTs to combine
* @return error (OK if we successfully combined the transactions, other error if they were not compatible)
*/
[[nodiscard]] TransactionError CombinePSBTs(PartiallySignedTransaction& out, const std::vector<PartiallySignedTransaction>& psbtxs);
//! Decode a base64ed PSBT into a PartiallySignedTransaction
[[nodiscard]] bool DecodeBase64PSBT(PartiallySignedTransaction& decoded_psbt, const std::string& base64_psbt, std::string& error);
//! Decode a raw (binary blob) PSBT into a PartiallySignedTransaction
[[nodiscard]] bool DecodeRawPSBT(PartiallySignedTransaction& decoded_psbt, const std::string& raw_psbt, std::string& error);
#endif // BITCOIN_PSBT_H
|