Skip to content

Commit 7b302eb

Browse files
committed
Merge #663: Add support for blinding pegins
cc49106 Update getnewaddress help message with "blech32" special case (Steven Roose) 56358b8 Add tests for blinding pegin transactions (Steven Roose) a9f589b Add support for blinding pegin transactions (Steven Roose) bf0cc83 Add confidential assets values to listunspent documentation (Steven Roose) 4e84596 Always return blind address for "blech32" type (Steven Roose) Pull request description: Adds pegin handling support for `blindrawtransaction` and `rawblindrawtransaction`. Tree-SHA512: 1138aeedd2d5852108abd53a6afc83524e5f06ad3ab770fd8ab37c97c8dd5c49f78b8d44bdf7cbe2c073e07cd8566bf032526ab6488e99fc02f8b5b154a3fde1
2 parents a409d01 + cc49106 commit 7b302eb

5 files changed

Lines changed: 141 additions & 5 deletions

File tree

src/blind.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,7 @@ int BlindTransaction(std::vector<uint256 >& input_value_blinding_factors, const
536536
// abort and not blind and the math adds up.
537537
// Count as success(to signal caller that nothing wrong) and return early
538538
if (memcmp(diff_zero, &blind[num_blind_attempts-1][0], 32) == 0) {
539-
return ++num_blinded;
539+
return ++num_blinded;
540540
}
541541
}
542542

@@ -545,7 +545,7 @@ int BlindTransaction(std::vector<uint256 >& input_value_blinding_factors, const
545545
out_val_blind_factors[nOut] = uint256(std::vector<unsigned char>(value_blindptrs[value_blindptrs.size()-1], value_blindptrs[value_blindptrs.size()-1]+32));
546546
out_asset_blind_factors[nOut] = uint256(std::vector<unsigned char>(asset_blindptrs[asset_blindptrs.size()-1], asset_blindptrs[asset_blindptrs.size()-1]+32));
547547

548-
//Blind the asset ID
548+
// Blind the asset ID
549549
BlindAsset(conf_asset, asset_gen, asset, asset_blindptrs.back());
550550

551551
// Create value commitment

src/rpc/rawtransaction.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2248,6 +2248,20 @@ UniValue rawblindrawtransaction(const JSONRPCRequest& request)
22482248
std::vector<CAsset> output_assets;
22492249
std::vector<CPubKey> output_pubkeys;
22502250
for (size_t nIn = 0; nIn < tx.vin.size(); nIn++) {
2251+
// Special handling for pegin inputs: no blinds and explicit amount/asset.
2252+
if (tx.vin[nIn].m_is_pegin) {
2253+
std::string err;
2254+
if (tx.witness.vtxinwit.size() != tx.vin.size() || !IsValidPeginWitness(tx.witness.vtxinwit[nIn].m_pegin_witness, tx.vin[nIn].prevout, err, false)) {
2255+
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Transaction contains invalid peg-in input: %s", err));
2256+
}
2257+
CTxOut pegin_output = GetPeginOutputFromWitness(tx.witness.vtxinwit[nIn].m_pegin_witness);
2258+
input_blinds.push_back(uint256());
2259+
input_asset_blinds.push_back(uint256());
2260+
input_assets.push_back(pegin_output.nAsset.GetAsset());
2261+
input_amounts.push_back(pegin_output.nValue.GetAmount());
2262+
continue;
2263+
}
2264+
22512265
if (!inputBlinds[nIn].isStr())
22522266
throw JSONRPCError(RPC_INVALID_PARAMETER, "input blinds must be an array of hex strings");
22532267
if (!inputAssetBlinds[nIn].isStr())

src/wallet/rpcwallet.cpp

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,9 @@ static UniValue getnewaddress(const JSONRPCRequest& request)
180180
RPCHelpMan{"getnewaddress",
181181
"\nReturns a new Bitcoin address for receiving payments.\n"
182182
"If 'label' is specified, it is added to the address book \n"
183-
"so payments received with the address will be associated with 'label'.\n",
183+
"so payments received with the address will be associated with 'label'.\n"
184+
"When the wallet doesn't give blinded addresses by default (-blindedaddresses=0), \n"
185+
"the address type \"blech32\" can still be used to get a blinded address.\n",
184186
{
185187
{"label", RPCArg::Type::STR, /* default */ "\"\"", "The label name for the address to be linked to. It can also be set to the empty string \"\" to represent the default label. The label does not need to exist, it will be created if there is no label by the given name."},
186188
{"address_type", RPCArg::Type::STR, /* default */ "set by -addresstype", "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -addresstype."},
@@ -206,10 +208,15 @@ static UniValue getnewaddress(const JSONRPCRequest& request)
206208
label = LabelFromValue(request.params[0]);
207209

208210
OutputType output_type = pwallet->m_default_address_type;
211+
bool force_blind = false;
209212
if (!request.params[1].isNull()) {
210213
if (!ParseOutputType(request.params[1].get_str(), output_type)) {
211214
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[1].get_str()));
212215
}
216+
// Special case for "blech32" when `-blindedaddresses=0` in the config.
217+
if (request.params[1].get_str() == "blech32") {
218+
force_blind = true;
219+
}
213220
}
214221

215222
if (!pwallet->IsLocked()) {
@@ -223,7 +230,7 @@ static UniValue getnewaddress(const JSONRPCRequest& request)
223230
}
224231
pwallet->LearnRelatedScripts(newKey, output_type);
225232
CTxDestination dest = GetDestinationForKey(newKey, output_type);
226-
if (gArgs.GetBoolArg("-blindedaddresses", g_con_elementsmode)) {
233+
if (gArgs.GetBoolArg("-blindedaddresses", g_con_elementsmode) || force_blind) {
227234
CPubKey blinding_pubkey = pwallet->GetBlindingPubKey(GetScriptForDestination(dest));
228235
dest = GetDestinationForKey(newKey, output_type, blinding_pubkey);
229236
}
@@ -270,10 +277,15 @@ static UniValue getrawchangeaddress(const JSONRPCRequest& request)
270277
}
271278

272279
OutputType output_type = pwallet->m_default_change_type != OutputType::CHANGE_AUTO ? pwallet->m_default_change_type : pwallet->m_default_address_type;
280+
bool force_blind = false;
273281
if (!request.params[0].isNull()) {
274282
if (!ParseOutputType(request.params[0].get_str(), output_type)) {
275283
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str()));
276284
}
285+
// Special case for "blech32" when `-blindedaddresses=0` in the config.
286+
if (request.params[0].get_str() == "blech32") {
287+
force_blind = true;
288+
}
277289
}
278290

279291
CReserveKey reservekey(pwallet);
@@ -285,7 +297,7 @@ static UniValue getrawchangeaddress(const JSONRPCRequest& request)
285297

286298
pwallet->LearnRelatedScripts(vchPubKey, output_type);
287299
CTxDestination dest = GetDestinationForKey(vchPubKey, output_type);
288-
if (gArgs.GetBoolArg("-blindedaddresses", g_con_elementsmode)) {
300+
if (gArgs.GetBoolArg("-blindedaddresses", g_con_elementsmode) || force_blind) {
289301
CPubKey blinding_pubkey = pwallet->GetBlindingPubKey(GetScriptForDestination(dest));
290302
dest = GetDestinationForKey(vchPubKey, output_type, blinding_pubkey);
291303
}
@@ -2997,6 +3009,11 @@ static UniValue listunspent(const JSONRPCRequest& request)
29973009
" \"label\" : \"label\", (string) The associated label, or \"\" for the default label\n"
29983010
" \"scriptPubKey\" : \"key\", (string) the script key\n"
29993011
" \"amount\" : x.xxx, (numeric) the transaction output amount in " + CURRENCY_UNIT + "\n"
3012+
" \"amountcommitment\" : \"hex\", (string) the transaction output commitment in hex\n"
3013+
" \"asset\" : \"hex\", (string) the transaction output asset in hex\n"
3014+
" \"assetcommitment\" : \"hex\", (string) the transaction output asset commitment in hex\n"
3015+
" \"amountblinder\" : \"hex\", (string) the transaction output amount blinding factor in hex\n"
3016+
" \"assetblinder\" : \"hex\", (string) the transaction output asset blinding factor in hex\n"
30003017
" \"confirmations\" : n, (numeric) The number of confirmations\n"
30013018
" \"redeemScript\" : \"script\" (string) The redeemScript if scriptPubKey is P2SH\n"
30023019
" \"witnessScript\" : \"script\" (string) witnessScript if the scriptPubKey is P2WSH or P2SH-P2WSH\n"
@@ -5763,6 +5780,20 @@ UniValue blindrawtransaction(const JSONRPCRequest& request)
57635780
for (size_t nIn = 0; nIn < tx.vin.size(); ++nIn) {
57645781
COutPoint prevout = tx.vin[nIn].prevout;
57655782

5783+
// Special handling for pegin inputs: no blinds and explicit amount/asset.
5784+
if (tx.vin[nIn].m_is_pegin) {
5785+
std::string err;
5786+
if (tx.witness.vtxinwit.size() != tx.vin.size() || !IsValidPeginWitness(tx.witness.vtxinwit[nIn].m_pegin_witness, prevout, err, false)) {
5787+
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Transaction contains invalid peg-in input: %s", err));
5788+
}
5789+
CTxOut pegin_output = GetPeginOutputFromWitness(tx.witness.vtxinwit[nIn].m_pegin_witness);
5790+
input_blinds.push_back(uint256());
5791+
input_asset_blinds.push_back(uint256());
5792+
input_assets.push_back(pegin_output.nAsset.GetAsset());
5793+
input_amounts.push_back(pegin_output.nValue.GetAmount());
5794+
continue;
5795+
}
5796+
57665797
std::map<uint256, CWalletTx>::iterator it = pwallet->mapWallet.find(prevout.hash);
57675798
if (it == pwallet->mapWallet.end() || pwallet->IsMine(tx.vin[nIn]) == ISMINE_NO) {
57685799
// For inputs we don't own, input assetcommitments for the surjection must be supplied.

test/functional/feature_fedpeg.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,28 @@
66
from test_framework.test_framework import BitcoinTestFramework
77
from test_framework.util import (
88
connect_nodes_bi,
9+
disconnect_nodes,
910
get_auth_cookie,
1011
get_datadir_path,
1112
rpc_port,
1213
p2p_port,
1314
assert_raises_rpc_error,
1415
assert_equal,
1516
bytes_to_hex_str,
17+
hex_str_to_bytes,
1618
)
1719
from test_framework import util
1820
from test_framework.messages import (
21+
COIN,
1922
CBlock,
23+
COutPoint,
2024
CTransaction,
25+
CTxIn,
2126
CTxInWitness,
27+
CTxOut,
28+
CTxOutNonce,
2229
FromHex,
30+
ToHex,
2331
)
2432
from test_framework.blocktools import (
2533
add_witness_commitment,
@@ -524,6 +532,79 @@ def run_test(self):
524532
sidechain.generatetoaddress(1, sidechain.getnewaddress())
525533
assert_equal(sidechain.gettransaction(claim_txid)["confirmations"], 1)
526534

535+
# Test a confidential pegin.
536+
print("Performing a confidential pegin.")
537+
# start pegin
538+
pegin_addrs = sidechain.getpeginaddress()
539+
assert_equal(sidechain.decodescript(pegin_addrs["claim_script"])["type"], "witness_v0_keyhash")
540+
pegin_addr = addrs["mainchain_address"]
541+
txid_fund = parent.sendtoaddress(pegin_addr, 10)
542+
# 10+2 confirms required to get into mempool and confirm
543+
parent.generate(11)
544+
proof = parent.gettxoutproof([txid_fund])
545+
raw = parent.gettransaction(txid_fund)["hex"]
546+
raw_pegin = sidechain.createrawpegin(raw, proof)['hex']
547+
pegin = FromHex(CTransaction(), raw_pegin)
548+
# add new blinding pubkey for the pegin output
549+
pegin.vout[0].nNonce = CTxOutNonce(hex_str_to_bytes(sidechain.getaddressinfo(sidechain.getnewaddress("", "blech32"))["confidential_key"]))
550+
# now add an extra input and output from listunspent; we need a blinded output for this
551+
blind_addr = sidechain.getnewaddress("", "blech32")
552+
sidechain.sendtoaddress(blind_addr, 15)
553+
sidechain.generate(6)
554+
unspent = [u for u in sidechain.listunspent(6, 6) if u["amount"] == 15][0]
555+
assert(unspent["spendable"])
556+
assert("amountcommitment" in unspent)
557+
pegin.vin.append(CTxIn(COutPoint(int(unspent["txid"], 16), unspent["vout"])))
558+
# insert corresponding output before fee output
559+
new_destination = sidechain.getaddressinfo(sidechain.getnewaddress("", "blech32"))
560+
new_dest_script_pk = hex_str_to_bytes(new_destination["scriptPubKey"])
561+
new_dest_nonce = CTxOutNonce(hex_str_to_bytes(new_destination["confidential_key"]))
562+
new_dest_asset = pegin.vout[0].nAsset
563+
pegin.vout.insert(1, CTxOut(int(unspent["amount"]*COIN) - 10000, new_dest_script_pk, new_dest_asset, new_dest_nonce))
564+
# add the 10 ksat fee
565+
pegin.vout[2].nValue.setToAmount(pegin.vout[2].nValue.getAmount() + 10000)
566+
pegin_hex = ToHex(pegin)
567+
# test with both blindraw and rawblindraw
568+
raw_pegin_blinded1 = sidechain.blindrawtransaction(pegin_hex)
569+
raw_pegin_blinded2 = sidechain.rawblindrawtransaction(pegin_hex, ["", unspent["amountblinder"]], [10, 15], [unspent["asset"]]*2, ["", unspent["assetblinder"]], "", False)
570+
pegin_signed1 = sidechain.signrawtransactionwithwallet(raw_pegin_blinded1)
571+
pegin_signed2 = sidechain.signrawtransactionwithwallet(raw_pegin_blinded2)
572+
for pegin_signed in [pegin_signed1, pegin_signed2]:
573+
final_decoded = sidechain.decoderawtransaction(pegin_signed["hex"])
574+
assert(final_decoded["vin"][0]["is_pegin"])
575+
assert(not final_decoded["vin"][1]["is_pegin"])
576+
assert("assetcommitment" in final_decoded["vout"][0])
577+
assert("valuecommitment" in final_decoded["vout"][0])
578+
assert("commitmentnonce" in final_decoded["vout"][0])
579+
assert("value" not in final_decoded["vout"][0])
580+
assert("asset" not in final_decoded["vout"][0])
581+
assert(final_decoded["vout"][0]["commitmentnonce_fully_valid"])
582+
assert("assetcommitment" in final_decoded["vout"][1])
583+
assert("valuecommitment" in final_decoded["vout"][1])
584+
assert("commitmentnonce" in final_decoded["vout"][1])
585+
assert("value" not in final_decoded["vout"][1])
586+
assert("asset" not in final_decoded["vout"][1])
587+
assert(final_decoded["vout"][1]["commitmentnonce_fully_valid"])
588+
assert("value" in final_decoded["vout"][2])
589+
assert("asset" in final_decoded["vout"][2])
590+
# check that it is accepted in the mempool
591+
accepted = sidechain.testmempoolaccept([pegin_signed["hex"]])[0]
592+
if not accepted["allowed"]:
593+
raise Exception(accepted["reject-reason"])
594+
print("Blinded transaction looks ok!") # need this print to distinguish failures in for loop
595+
# check if they get mined; since we're trying to mine two double spends, disconnect the nodes
596+
disconnect_nodes(sidechain, 3)
597+
disconnect_nodes(sidechain2, 2)
598+
txid1 = sidechain.sendrawtransaction(pegin_signed1["hex"])
599+
blocks = sidechain.generate(3)
600+
assert_equal(sidechain.getrawtransaction(txid1, True, blocks[0])["confirmations"], 3)
601+
txid2 = sidechain2.sendrawtransaction(pegin_signed2["hex"])
602+
blocks = sidechain2.generate(3)
603+
assert_equal(sidechain2.getrawtransaction(txid2, True, blocks[0])["confirmations"], 3)
604+
# reconnect in case we extend the test
605+
connect_nodes_bi(self.nodes, 2, 3)
606+
sidechain.generate(10)
607+
527608
print('Success!')
528609

529610
# Manually stop sidechains first, then the parent chains.

test/functional/wallet_address_types.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,5 +369,15 @@ def run_test(self):
369369
self.test_address(4, self.nodes[4].getrawchangeaddress(), multisig=False, typ='p2sh-segwit')
370370
self.test_address(4, self.nodes[4].getrawchangeaddress('bech32'), multisig=False, typ='bech32')
371371

372+
# test blech32 addresses
373+
info_unblinded = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress("", "bech32"))
374+
assert(len(info_unblinded["confidential_key"]) == 0)
375+
# getnewaddress
376+
info1 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress("", "blech32"))
377+
assert(len(info1["confidential_key"]) > 0)
378+
# getrawchangeaddress
379+
info2 = self.nodes[0].getaddressinfo(self.nodes[0].getrawchangeaddress("blech32"))
380+
assert(len(info2["confidential_key"]) > 0)
381+
372382
if __name__ == '__main__':
373383
AddressTypeTest().main()

0 commit comments

Comments
 (0)