Skip to content

Commit 5becc1e

Browse files
committed
Verify blinding in sendraw. Fix createfundedpsbt & tests.
1 parent 6b9af4f commit 5becc1e

File tree

3 files changed

+43
-22
lines changed

3 files changed

+43
-22
lines changed

src/rpc/rawtransaction.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1184,6 +1184,16 @@ UniValue sendrawtransaction(const JSONRPCRequest& request)
11841184
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
11851185
CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
11861186

1187+
for (const auto& out : tx->vout) {
1188+
// If we have a nonce, it could be a smuggled pubkey, or it could be a
1189+
// proper nonce produced by blinding. In the latter case, the value
1190+
// will always be blinded and not explicit. In the former case, we
1191+
// error out because the transaction is not blinded properly.
1192+
if (!out.nNonce.IsNull() && out.nValue.IsExplicit()) {
1193+
throw JSONRPCError(RPC_TRANSACTION_ERROR, "Transaction output has nonce, but is not blinded. Did you forget to call blindpsbt, blindrawtranssaction, or rawblindrawtransaction?");
1194+
}
1195+
}
1196+
11871197
bool allowhighfees = false;
11881198
if (!request.params[1].isNull()) allowhighfees = request.params[1].get_bool();
11891199
const CAmount highfee{allowhighfees ? 0 : ::maxTxFee};
@@ -1309,7 +1319,7 @@ UniValue blindpsbt(const JSONRPCRequest& request)
13091319
throw std::runtime_error(
13101320
"blindpsbt \"psbt\" ( ignoreblindfail )\n"
13111321
"\nUse the blinding data from the PSBT inputs to generate the blinding data for the PSBT outputs.\n\n"
1312-
"NOTE: Not expected to work on issuance/reissuance/peg transactions yet.\n"
1322+
"NOTE: Does not work on issuance/reissuance/peg transactions yet.\n"
13131323

13141324
"\nArguments:\n"
13151325
"1. \"psbt\" (string, required) The PSBT base64 string\n"

src/wallet/rpcwallet.cpp

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4664,14 +4664,10 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
46644664
int change_position;
46654665
std::vector<CPubKey> output_pubkeys;
46664666

4667-
// Because we use the &output_pubkeys form of ConstructTransaction, it will
4668-
// not use the nonce hack of putting output pubkeys in the nonce fields
4669-
// of the outputs.
4670-
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]["replaceable"], NullUniValue /* CA: assets_in */, &output_pubkeys);
4671-
4672-
// Because our transaction outputs do not have pubkeys in the nonce fields,
4673-
// FundTransaction will not see them as blinded, so no blinding will
4674-
// occur here. (This is what we want, for PSBT usage; we will blind later.)
4667+
// It's hard to control the behavior of FundTransaction, so we will wait
4668+
// until after it's done, then extract the blinding keys from the output
4669+
// nonces.
4670+
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]["replaceable"], NullUniValue /* CA: assets_in */);
46754671
FundTransaction(pwallet, rawTx, fee, change_position, request.params[3]);
46764672

46774673
// Make a blank psbt
@@ -4682,7 +4678,11 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
46824678
}
46834679
for (unsigned int i = 0; i < rawTx.vout.size(); ++i) {
46844680
psbtx.outputs.push_back(PSBTOutput());
4685-
psbtx.outputs[i].blinding_pubkey = output_pubkeys[i];
4681+
if (!psbtx.tx->vout[i].nNonce.IsNull()) {
4682+
// Extract blinding key and clear the nonce
4683+
psbtx.outputs[i].blinding_pubkey = CPubKey(psbtx.tx->vout[i].nNonce.vchCommitment);
4684+
psbtx.tx->vout[i].nNonce.SetNull();
4685+
}
46864686
}
46874687

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

test/functional/rpc_psbt.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,6 @@ def get_address(self, confidential, node_num, addr_mode=None):
8080
def to_unconf_addr(self, node_num, addr):
8181
return self.nodes[node_num].getaddressinfo(addr)['unconfidential']
8282

83-
def find_output_in_tx(self, tx, amount):
84-
for i in range(len(tx["vout"])):
85-
if tx["vout"][i]["value"] == amount:
86-
return i
87-
raise RuntimeError("find_output tx %s : %s not found" % (tx, str(amount)))
88-
8983
def run_basic_tests(self, confidential):
9084
# Create and fund a raw tx for sending 10 BTC
9185
psbtx1 = self.nodes[0].walletcreatefundedpsbt([], {self.get_address(confidential, 2):10})['psbt']
@@ -121,8 +115,10 @@ def run_basic_tests(self, confidential):
121115
# fund those addresses
122116
rawtx = self.nodes[0].createrawtransaction([], {p2sh:10, p2wsh:10, p2wpkh:10, p2sh_p2wsh:10, p2sh_p2wpkh:10, p2pkh:10})
123117
rawtx = self.nodes[0].fundrawtransaction(rawtx, {"changePosition":3})
124-
signed_tx = self.nodes[0].signrawtransactionwithwallet(rawtx['hex'])['hex']
118+
rawtx = self.nodes[0].blindrawtransaction(rawtx['hex'])
119+
signed_tx = self.nodes[0].signrawtransactionwithwallet(rawtx)['hex']
125120
txid = self.nodes[0].sendrawtransaction(signed_tx)
121+
126122
self.nodes[0].generate(6)
127123
self.sync_all()
128124

@@ -198,23 +194,37 @@ def run_basic_tests(self, confidential):
198194
# Create outputs to nodes 1 and 2
199195
# We do a whole song-and-dance here (instead of calling sendtoaddress) to get access to the unblinded transaction data to find our outputs
200196
node1_addr = self.get_address(confidential, 1)
197+
node1_unconf_addr = self.to_unconf_addr(1, node1_addr)
201198
node2_addr = self.get_address(confidential, 2)
199+
node2_unconf_addr = self.to_unconf_addr(2, node2_addr)
202200
rt1 = self.nodes[0].createrawtransaction([], {node1_addr:13})
203201
rt1 = self.nodes[0].fundrawtransaction(rt1)
204-
rt1 = self.nodes[0].signrawtransactionwithwallet(rt1['hex'])
202+
rt1 = self.nodes[0].blindrawtransaction(rt1['hex'])
203+
rt1 = self.nodes[0].signrawtransactionwithwallet(rt1)
205204
txid1 = self.nodes[0].sendrawtransaction(rt1['hex'])
206205
rt1 = self.nodes[0].decoderawtransaction(rt1['hex'])
207206

208207
rt2 = self.nodes[0].createrawtransaction([], {node2_addr:13})
209208
rt2 = self.nodes[0].fundrawtransaction(rt2)
210-
rt2 = self.nodes[0].signrawtransactionwithwallet(rt2['hex'])
209+
rt2 = self.nodes[0].blindrawtransaction(rt2['hex'])
210+
rt2 = self.nodes[0].signrawtransactionwithwallet(rt2)
211211
txid2 = self.nodes[0].sendrawtransaction(rt2['hex'])
212212
rt2 = self.nodes[0].decoderawtransaction(rt2['hex'])
213213

214214
self.nodes[0].generate(6)
215215
self.sync_all()
216-
vout1 = self.find_output_in_tx(rt1, 13)
217-
vout2 = self.find_output_in_tx(rt2, 13)
216+
217+
for out in rt1['vout']:
218+
if out['scriptPubKey']['type'] == "fee":
219+
next
220+
elif out['scriptPubKey']['addresses'][0] == node1_unconf_addr:
221+
vout1 = out['n']
222+
223+
for out in rt2['vout']:
224+
if out['scriptPubKey']['type'] == "fee":
225+
next
226+
elif out['scriptPubKey']['addresses'][0] == node2_unconf_addr:
227+
vout2 = out['n']
218228

219229
# This test doesn't work with Confidential Assets yet.
220230
if not confidential:
@@ -350,7 +360,8 @@ def run_ca_tests(self):
350360
unconf_addr_4 = self.get_address(False, 0)
351361
rawtx = self.nodes[0].createrawtransaction([], {unconf_addr_0:50, unconf_addr_1:50, unconf_addr_4:50})
352362
rawtx = self.nodes[0].fundrawtransaction(rawtx, {"changePosition":3}) # our outputs will be 0, 1, 2
353-
signed_tx = self.nodes[0].signrawtransactionwithwallet(rawtx['hex'])['hex']
363+
rawtx = self.nodes[0].blindrawtransaction(rawtx['hex'])
364+
signed_tx = self.nodes[0].signrawtransactionwithwallet(rawtx)['hex']
354365
txid_nonconf = self.nodes[0].sendrawtransaction(signed_tx)
355366
self.nodes[0].generate(1)
356367
self.sync_all()

0 commit comments

Comments
 (0)