Skip to content

Commit 20dae1e

Browse files
committed
Fix walletcreatefundedpsbt and converttopsbt RPCs; fix tests.
1 parent e83ca35 commit 20dae1e

File tree

4 files changed

+48
-20
lines changed

4 files changed

+48
-20
lines changed

src/rpc/rawtransaction.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1294,7 +1294,8 @@ UniValue blindpsbt(const JSONRPCRequest& request)
12941294
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
12951295
throw std::runtime_error(
12961296
"blindpsbt \"psbt\" ( ignoreblindfail )\n"
1297-
"\nUse the blinding data from the PSBT inputs to generate the blinding data for the PSBT outputs.\n"
1297+
"\nUse the blinding data from the PSBT inputs to generate the blinding data for the PSBT outputs.\n\n"
1298+
"TODO: Not expected to work on issuance/reissuance/peg transactions yet.\n"
12981299

12991300
"\nArguments:\n"
13001301
"1. \"psbt\" (string, required) The PSBT base64 string\n"
@@ -2018,14 +2019,20 @@ UniValue converttopsbt(const JSONRPCRequest& request)
20182019

20192020
// Make a blank psbt
20202021
PartiallySignedTransaction psbtx;
2021-
psbtx.tx = tx;
20222022
for (unsigned int i = 0; i < tx.vin.size(); ++i) {
20232023
psbtx.inputs.push_back(PSBTInput());
20242024
}
20252025
for (unsigned int i = 0; i < tx.vout.size(); ++i) {
20262026
psbtx.outputs.push_back(PSBTOutput());
2027+
// At this point, if the nonce field is present it should be a smuggled
2028+
// pubkey, and not a real nonce. Convert it back to a pubkey and strip
2029+
// it out.
2030+
psbtx.outputs[i].blinding_pubkey = CPubKey(tx.vout[i].nNonce.vchCommitment);
2031+
tx.vout[i].nNonce.SetNull();
20272032
}
20282033

2034+
psbtx.tx = tx;
2035+
20292036
// Serialize the PSBT
20302037
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
20312038
ssTx << psbtx;

src/wallet/rpcwallet.cpp

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4275,10 +4275,12 @@ void FillPSBTInputsData(const CWallet* pwallet, PartiallySignedTransaction& psbt
42754275

42764276
bool SignPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, int sighash_type, bool sign, bool imbalance_ok)
42774277
{
4278-
// Check that the transaction is not still in need of blinding
4279-
for (const PSBTOutput& o : psbtx.outputs) {
4280-
if (o.blinding_pubkey.IsValid()) {
4281-
throw JSONRPCError(RPC_INVALID_PARAMETER, "PSBT must be blinded before signing; call blindpsbt.");
4278+
// If we're signing, check that the transaction is not still in need of blinding
4279+
if (sign) {
4280+
for (const PSBTOutput& o : psbtx.outputs) {
4281+
if (o.blinding_pubkey.IsValid()) {
4282+
throw JSONRPCError(RPC_INVALID_PARAMETER, "PSBT must be blinded before signing; call blindpsbt.");
4283+
}
42824284
}
42834285
}
42844286

@@ -4661,7 +4663,16 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
46614663

46624664
CAmount fee;
46634665
int change_position;
4664-
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]["replaceable"], NullUniValue /* CA: assets_in */);
4666+
std::vector<CPubKey> output_pubkeys;
4667+
4668+
// Because we use the &output_pubkeys form of ConstructTransaction, it will
4669+
// not use the nonce hack of putting output pubkeys in the nonce fields
4670+
// of the outputs.
4671+
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]["replaceable"], NullUniValue /* CA: assets_in */, &output_pubkeys);
4672+
4673+
// Because our transaction outputs do not have pubkeys in the nonce fields,
4674+
// FundTransaction will not see them as blinded, so no blinding will
4675+
// occur here. (This is what we want, for PSBT usage; we will blind later.)
46654676
FundTransaction(pwallet, rawTx, fee, change_position, request.params[3]);
46664677

46674678
// Make a blank psbt
@@ -4672,6 +4683,7 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
46724683
}
46734684
for (unsigned int i = 0; i < rawTx.vout.size(); ++i) {
46744685
psbtx.outputs.push_back(PSBTOutput());
4686+
psbtx.outputs[i].blinding_pubkey = output_pubkeys[i];
46754687
}
46764688

46774689
// Fill transaction with out data but don't sign

test/functional/rpc_psbt.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import json
1212
import os
1313

14+
import IPython
15+
1416
MAX_BIP125_RBF_SEQUENCE = 0xfffffffd
1517

1618
# Create one-input, one-output, no-fee transaction:
@@ -50,11 +52,13 @@ def run_basic_tests(self, confidential):
5052
psbtx1 = self.nodes[0].walletcreatefundedpsbt([], {self.get_address(confidential, 2):10})['psbt']
5153

5254
# Node 1 should not be able to add anything to it but still return the psbtx same as before
53-
psbtx = self.nodes[1].walletprocesspsbt(psbtx1)['psbt']
55+
psbtx = self.nodes[1].walletfillpsbtdata(psbtx1)['psbt']
5456
assert_equal(psbtx1, psbtx)
5557

5658
# Sign the transaction and send
57-
signed_tx = self.nodes[0].walletprocesspsbt(psbtx)['psbt']
59+
filled_tx = self.nodes[0].walletfillpsbtdata(psbtx)['psbt']
60+
blinded_tx = self.nodes[0].blindpsbt(filled_tx)
61+
signed_tx = self.nodes[0].walletsignpsbt(blinded_tx)['psbt']
5862
final_tx = self.nodes[0].finalizepsbt(signed_tx)['hex']
5963
self.nodes[0].sendrawtransaction(final_tx)
6064

@@ -109,20 +113,26 @@ def run_basic_tests(self, confidential):
109113

110114
# spend single key from node 1
111115
rawtx = self.nodes[1].walletcreatefundedpsbt([{"txid":txid,"vout":p2wpkh_pos},{"txid":txid,"vout":p2sh_p2wpkh_pos},{"txid":txid,"vout":p2pkh_pos}], {self.get_address(confidential, 1):29.99})['psbt']
112-
walletprocesspsbt_out = self.nodes[1].walletprocesspsbt(rawtx)
113-
assert_equal(walletprocesspsbt_out['complete'], True)
114-
self.nodes[1].sendrawtransaction(self.nodes[1].finalizepsbt(walletprocesspsbt_out['psbt'])['hex'])
116+
filled = self.nodes[1].walletfillpsbtdata(rawtx)['psbt']
117+
blinded = self.nodes[1].blindpsbt(filled)
118+
walletsignpsbt_out = self.nodes[1].walletsignpsbt(blinded)
119+
assert_equal(walletsignpsbt_out['complete'], True)
120+
self.nodes[1].sendrawtransaction(self.nodes[1].finalizepsbt(walletsignpsbt_out['psbt'])['hex'])
115121

116122
# partially sign multisig things with node 1
117123
psbtx = self.nodes[1].walletcreatefundedpsbt([{"txid":txid,"vout":p2wsh_pos},{"txid":txid,"vout":p2sh_pos},{"txid":txid,"vout":p2sh_p2wsh_pos}], {self.get_address(confidential, 1):29.99})['psbt']
118-
walletprocesspsbt_out = self.nodes[1].walletprocesspsbt(psbtx)
119-
psbtx = walletprocesspsbt_out['psbt']
120-
assert_equal(walletprocesspsbt_out['complete'], False)
124+
filled = self.nodes[1].walletfillpsbtdata(psbtx)['psbt']
125+
# have both nodes fill before we try to blind and sign
126+
filled = self.nodes[2].walletfillpsbtdata(filled)['psbt']
127+
blinded = self.nodes[1].blindpsbt(filled)
128+
walletsignpsbt_out = self.nodes[1].walletsignpsbt(blinded)
129+
psbtx = walletsignpsbt_out['psbt']
130+
assert_equal(walletsignpsbt_out['complete'], False)
121131

122132
# partially sign with node 2. This should be complete and sendable
123-
walletprocesspsbt_out = self.nodes[2].walletprocesspsbt(psbtx)
124-
assert_equal(walletprocesspsbt_out['complete'], True)
125-
self.nodes[2].sendrawtransaction(self.nodes[2].finalizepsbt(walletprocesspsbt_out['psbt'])['hex'])
133+
walletsignpsbt_out = self.nodes[2].walletsignpsbt(psbtx)
134+
assert_equal(walletsignpsbt_out['complete'], True)
135+
self.nodes[2].sendrawtransaction(self.nodes[2].finalizepsbt(walletsignpsbt_out['psbt'])['hex'])
126136

127137
# check that walletprocesspsbt fails to decode a non-psbt
128138
rawtx = self.nodes[1].createrawtransaction([{"txid":txid,"vout":p2wpkh_pos}], {self.get_address(confidential, 1):9.99})

test/functional/test_runner.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,7 @@
125125
'wallet_disableprivatekeys.py',
126126
'wallet_disableprivatekeys.py --usecli',
127127
'interface_http.py',
128-
# ELEMENTS: hard-coded test vectors don't work with different tx serialization
129-
#'rpc_psbt.py',
128+
'rpc_psbt.py',
130129
'rpc_users.py',
131130
'feature_proxy.py',
132131
'rpc_signrawtransaction.py',

0 commit comments

Comments
 (0)