Skip to content

Commit

Permalink
Fix walletcreatefundedpsbt and converttopsbt RPCs; fix tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
gwillen committed Jun 25, 2019
1 parent e83ca35 commit 20dae1e
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 20 deletions.
11 changes: 9 additions & 2 deletions src/rpc/rawtransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1294,7 +1294,8 @@ UniValue blindpsbt(const JSONRPCRequest& request)
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
throw std::runtime_error(
"blindpsbt \"psbt\" ( ignoreblindfail )\n"
"\nUse the blinding data from the PSBT inputs to generate the blinding data for the PSBT outputs.\n"
"\nUse the blinding data from the PSBT inputs to generate the blinding data for the PSBT outputs.\n\n"
"TODO: Not expected to work on issuance/reissuance/peg transactions yet.\n"

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

// Make a blank psbt
PartiallySignedTransaction psbtx;
psbtx.tx = tx;
for (unsigned int i = 0; i < tx.vin.size(); ++i) {
psbtx.inputs.push_back(PSBTInput());
}
for (unsigned int i = 0; i < tx.vout.size(); ++i) {
psbtx.outputs.push_back(PSBTOutput());
// At this point, if the nonce field is present it should be a smuggled
// pubkey, and not a real nonce. Convert it back to a pubkey and strip
// it out.
psbtx.outputs[i].blinding_pubkey = CPubKey(tx.vout[i].nNonce.vchCommitment);
tx.vout[i].nNonce.SetNull();
}

psbtx.tx = tx;

// Serialize the PSBT
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << psbtx;
Expand Down
22 changes: 17 additions & 5 deletions src/wallet/rpcwallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4275,10 +4275,12 @@ void FillPSBTInputsData(const CWallet* pwallet, PartiallySignedTransaction& psbt

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

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

CAmount fee;
int change_position;
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]["replaceable"], NullUniValue /* CA: assets_in */);
std::vector<CPubKey> output_pubkeys;

// Because we use the &output_pubkeys form of ConstructTransaction, it will
// not use the nonce hack of putting output pubkeys in the nonce fields
// of the outputs.
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]["replaceable"], NullUniValue /* CA: assets_in */, &output_pubkeys);

// Because our transaction outputs do not have pubkeys in the nonce fields,
// FundTransaction will not see them as blinded, so no blinding will
// occur here. (This is what we want, for PSBT usage; we will blind later.)
FundTransaction(pwallet, rawTx, fee, change_position, request.params[3]);

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

// Fill transaction with out data but don't sign
Expand Down
32 changes: 21 additions & 11 deletions test/functional/rpc_psbt.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import json
import os

import IPython

MAX_BIP125_RBF_SEQUENCE = 0xfffffffd

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

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

# Sign the transaction and send
signed_tx = self.nodes[0].walletprocesspsbt(psbtx)['psbt']
filled_tx = self.nodes[0].walletfillpsbtdata(psbtx)['psbt']
blinded_tx = self.nodes[0].blindpsbt(filled_tx)
signed_tx = self.nodes[0].walletsignpsbt(blinded_tx)['psbt']
final_tx = self.nodes[0].finalizepsbt(signed_tx)['hex']
self.nodes[0].sendrawtransaction(final_tx)

Expand Down Expand Up @@ -109,20 +113,26 @@ def run_basic_tests(self, confidential):

# spend single key from node 1
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']
walletprocesspsbt_out = self.nodes[1].walletprocesspsbt(rawtx)
assert_equal(walletprocesspsbt_out['complete'], True)
self.nodes[1].sendrawtransaction(self.nodes[1].finalizepsbt(walletprocesspsbt_out['psbt'])['hex'])
filled = self.nodes[1].walletfillpsbtdata(rawtx)['psbt']
blinded = self.nodes[1].blindpsbt(filled)
walletsignpsbt_out = self.nodes[1].walletsignpsbt(blinded)
assert_equal(walletsignpsbt_out['complete'], True)
self.nodes[1].sendrawtransaction(self.nodes[1].finalizepsbt(walletsignpsbt_out['psbt'])['hex'])

# partially sign multisig things with node 1
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']
walletprocesspsbt_out = self.nodes[1].walletprocesspsbt(psbtx)
psbtx = walletprocesspsbt_out['psbt']
assert_equal(walletprocesspsbt_out['complete'], False)
filled = self.nodes[1].walletfillpsbtdata(psbtx)['psbt']
# have both nodes fill before we try to blind and sign
filled = self.nodes[2].walletfillpsbtdata(filled)['psbt']
blinded = self.nodes[1].blindpsbt(filled)
walletsignpsbt_out = self.nodes[1].walletsignpsbt(blinded)
psbtx = walletsignpsbt_out['psbt']
assert_equal(walletsignpsbt_out['complete'], False)

# partially sign with node 2. This should be complete and sendable
walletprocesspsbt_out = self.nodes[2].walletprocesspsbt(psbtx)
assert_equal(walletprocesspsbt_out['complete'], True)
self.nodes[2].sendrawtransaction(self.nodes[2].finalizepsbt(walletprocesspsbt_out['psbt'])['hex'])
walletsignpsbt_out = self.nodes[2].walletsignpsbt(psbtx)
assert_equal(walletsignpsbt_out['complete'], True)
self.nodes[2].sendrawtransaction(self.nodes[2].finalizepsbt(walletsignpsbt_out['psbt'])['hex'])

# check that walletprocesspsbt fails to decode a non-psbt
rawtx = self.nodes[1].createrawtransaction([{"txid":txid,"vout":p2wpkh_pos}], {self.get_address(confidential, 1):9.99})
Expand Down
3 changes: 1 addition & 2 deletions test/functional/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,7 @@
'wallet_disableprivatekeys.py',
'wallet_disableprivatekeys.py --usecli',
'interface_http.py',
# ELEMENTS: hard-coded test vectors don't work with different tx serialization
#'rpc_psbt.py',
'rpc_psbt.py',
'rpc_users.py',
'feature_proxy.py',
'rpc_signrawtransaction.py',
Expand Down

0 comments on commit 20dae1e

Please sign in to comment.