Skip to content

Commit

Permalink
Merge #663: Add support for blinding pegins
Browse files Browse the repository at this point in the history
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
  • Loading branch information
instagibbs committed Jun 21, 2019
2 parents a409d01 + cc49106 commit 7b302eb
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 5 deletions.
4 changes: 2 additions & 2 deletions src/blind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,7 @@ int BlindTransaction(std::vector<uint256 >& input_value_blinding_factors, const
// abort and not blind and the math adds up.
// Count as success(to signal caller that nothing wrong) and return early
if (memcmp(diff_zero, &blind[num_blind_attempts-1][0], 32) == 0) {
return ++num_blinded;
return ++num_blinded;
}
}

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

//Blind the asset ID
// Blind the asset ID
BlindAsset(conf_asset, asset_gen, asset, asset_blindptrs.back());

// Create value commitment
Expand Down
14 changes: 14 additions & 0 deletions src/rpc/rawtransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2248,6 +2248,20 @@ UniValue rawblindrawtransaction(const JSONRPCRequest& request)
std::vector<CAsset> output_assets;
std::vector<CPubKey> output_pubkeys;
for (size_t nIn = 0; nIn < tx.vin.size(); nIn++) {
// Special handling for pegin inputs: no blinds and explicit amount/asset.
if (tx.vin[nIn].m_is_pegin) {
std::string err;
if (tx.witness.vtxinwit.size() != tx.vin.size() || !IsValidPeginWitness(tx.witness.vtxinwit[nIn].m_pegin_witness, tx.vin[nIn].prevout, err, false)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Transaction contains invalid peg-in input: %s", err));
}
CTxOut pegin_output = GetPeginOutputFromWitness(tx.witness.vtxinwit[nIn].m_pegin_witness);
input_blinds.push_back(uint256());
input_asset_blinds.push_back(uint256());
input_assets.push_back(pegin_output.nAsset.GetAsset());
input_amounts.push_back(pegin_output.nValue.GetAmount());
continue;
}

if (!inputBlinds[nIn].isStr())
throw JSONRPCError(RPC_INVALID_PARAMETER, "input blinds must be an array of hex strings");
if (!inputAssetBlinds[nIn].isStr())
Expand Down
37 changes: 34 additions & 3 deletions src/wallet/rpcwallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,9 @@ static UniValue getnewaddress(const JSONRPCRequest& request)
RPCHelpMan{"getnewaddress",
"\nReturns a new Bitcoin address for receiving payments.\n"
"If 'label' is specified, it is added to the address book \n"
"so payments received with the address will be associated with 'label'.\n",
"so payments received with the address will be associated with 'label'.\n"
"When the wallet doesn't give blinded addresses by default (-blindedaddresses=0), \n"
"the address type \"blech32\" can still be used to get a blinded address.\n",
{
{"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."},
{"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."},
Expand All @@ -206,10 +208,15 @@ static UniValue getnewaddress(const JSONRPCRequest& request)
label = LabelFromValue(request.params[0]);

OutputType output_type = pwallet->m_default_address_type;
bool force_blind = false;
if (!request.params[1].isNull()) {
if (!ParseOutputType(request.params[1].get_str(), output_type)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[1].get_str()));
}
// Special case for "blech32" when `-blindedaddresses=0` in the config.
if (request.params[1].get_str() == "blech32") {
force_blind = true;
}
}

if (!pwallet->IsLocked()) {
Expand All @@ -223,7 +230,7 @@ static UniValue getnewaddress(const JSONRPCRequest& request)
}
pwallet->LearnRelatedScripts(newKey, output_type);
CTxDestination dest = GetDestinationForKey(newKey, output_type);
if (gArgs.GetBoolArg("-blindedaddresses", g_con_elementsmode)) {
if (gArgs.GetBoolArg("-blindedaddresses", g_con_elementsmode) || force_blind) {
CPubKey blinding_pubkey = pwallet->GetBlindingPubKey(GetScriptForDestination(dest));
dest = GetDestinationForKey(newKey, output_type, blinding_pubkey);
}
Expand Down Expand Up @@ -270,10 +277,15 @@ static UniValue getrawchangeaddress(const JSONRPCRequest& request)
}

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

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

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

// Special handling for pegin inputs: no blinds and explicit amount/asset.
if (tx.vin[nIn].m_is_pegin) {
std::string err;
if (tx.witness.vtxinwit.size() != tx.vin.size() || !IsValidPeginWitness(tx.witness.vtxinwit[nIn].m_pegin_witness, prevout, err, false)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Transaction contains invalid peg-in input: %s", err));
}
CTxOut pegin_output = GetPeginOutputFromWitness(tx.witness.vtxinwit[nIn].m_pegin_witness);
input_blinds.push_back(uint256());
input_asset_blinds.push_back(uint256());
input_assets.push_back(pegin_output.nAsset.GetAsset());
input_amounts.push_back(pegin_output.nValue.GetAmount());
continue;
}

std::map<uint256, CWalletTx>::iterator it = pwallet->mapWallet.find(prevout.hash);
if (it == pwallet->mapWallet.end() || pwallet->IsMine(tx.vin[nIn]) == ISMINE_NO) {
// For inputs we don't own, input assetcommitments for the surjection must be supplied.
Expand Down
81 changes: 81 additions & 0 deletions test/functional/feature_fedpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,28 @@
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
connect_nodes_bi,
disconnect_nodes,
get_auth_cookie,
get_datadir_path,
rpc_port,
p2p_port,
assert_raises_rpc_error,
assert_equal,
bytes_to_hex_str,
hex_str_to_bytes,
)
from test_framework import util
from test_framework.messages import (
COIN,
CBlock,
COutPoint,
CTransaction,
CTxIn,
CTxInWitness,
CTxOut,
CTxOutNonce,
FromHex,
ToHex,
)
from test_framework.blocktools import (
add_witness_commitment,
Expand Down Expand Up @@ -524,6 +532,79 @@ def run_test(self):
sidechain.generatetoaddress(1, sidechain.getnewaddress())
assert_equal(sidechain.gettransaction(claim_txid)["confirmations"], 1)

# Test a confidential pegin.
print("Performing a confidential pegin.")
# start pegin
pegin_addrs = sidechain.getpeginaddress()
assert_equal(sidechain.decodescript(pegin_addrs["claim_script"])["type"], "witness_v0_keyhash")
pegin_addr = addrs["mainchain_address"]
txid_fund = parent.sendtoaddress(pegin_addr, 10)
# 10+2 confirms required to get into mempool and confirm
parent.generate(11)
proof = parent.gettxoutproof([txid_fund])
raw = parent.gettransaction(txid_fund)["hex"]
raw_pegin = sidechain.createrawpegin(raw, proof)['hex']
pegin = FromHex(CTransaction(), raw_pegin)
# add new blinding pubkey for the pegin output
pegin.vout[0].nNonce = CTxOutNonce(hex_str_to_bytes(sidechain.getaddressinfo(sidechain.getnewaddress("", "blech32"))["confidential_key"]))
# now add an extra input and output from listunspent; we need a blinded output for this
blind_addr = sidechain.getnewaddress("", "blech32")
sidechain.sendtoaddress(blind_addr, 15)
sidechain.generate(6)
unspent = [u for u in sidechain.listunspent(6, 6) if u["amount"] == 15][0]
assert(unspent["spendable"])
assert("amountcommitment" in unspent)
pegin.vin.append(CTxIn(COutPoint(int(unspent["txid"], 16), unspent["vout"])))
# insert corresponding output before fee output
new_destination = sidechain.getaddressinfo(sidechain.getnewaddress("", "blech32"))
new_dest_script_pk = hex_str_to_bytes(new_destination["scriptPubKey"])
new_dest_nonce = CTxOutNonce(hex_str_to_bytes(new_destination["confidential_key"]))
new_dest_asset = pegin.vout[0].nAsset
pegin.vout.insert(1, CTxOut(int(unspent["amount"]*COIN) - 10000, new_dest_script_pk, new_dest_asset, new_dest_nonce))
# add the 10 ksat fee
pegin.vout[2].nValue.setToAmount(pegin.vout[2].nValue.getAmount() + 10000)
pegin_hex = ToHex(pegin)
# test with both blindraw and rawblindraw
raw_pegin_blinded1 = sidechain.blindrawtransaction(pegin_hex)
raw_pegin_blinded2 = sidechain.rawblindrawtransaction(pegin_hex, ["", unspent["amountblinder"]], [10, 15], [unspent["asset"]]*2, ["", unspent["assetblinder"]], "", False)
pegin_signed1 = sidechain.signrawtransactionwithwallet(raw_pegin_blinded1)
pegin_signed2 = sidechain.signrawtransactionwithwallet(raw_pegin_blinded2)
for pegin_signed in [pegin_signed1, pegin_signed2]:
final_decoded = sidechain.decoderawtransaction(pegin_signed["hex"])
assert(final_decoded["vin"][0]["is_pegin"])
assert(not final_decoded["vin"][1]["is_pegin"])
assert("assetcommitment" in final_decoded["vout"][0])
assert("valuecommitment" in final_decoded["vout"][0])
assert("commitmentnonce" in final_decoded["vout"][0])
assert("value" not in final_decoded["vout"][0])
assert("asset" not in final_decoded["vout"][0])
assert(final_decoded["vout"][0]["commitmentnonce_fully_valid"])
assert("assetcommitment" in final_decoded["vout"][1])
assert("valuecommitment" in final_decoded["vout"][1])
assert("commitmentnonce" in final_decoded["vout"][1])
assert("value" not in final_decoded["vout"][1])
assert("asset" not in final_decoded["vout"][1])
assert(final_decoded["vout"][1]["commitmentnonce_fully_valid"])
assert("value" in final_decoded["vout"][2])
assert("asset" in final_decoded["vout"][2])
# check that it is accepted in the mempool
accepted = sidechain.testmempoolaccept([pegin_signed["hex"]])[0]
if not accepted["allowed"]:
raise Exception(accepted["reject-reason"])
print("Blinded transaction looks ok!") # need this print to distinguish failures in for loop
# check if they get mined; since we're trying to mine two double spends, disconnect the nodes
disconnect_nodes(sidechain, 3)
disconnect_nodes(sidechain2, 2)
txid1 = sidechain.sendrawtransaction(pegin_signed1["hex"])
blocks = sidechain.generate(3)
assert_equal(sidechain.getrawtransaction(txid1, True, blocks[0])["confirmations"], 3)
txid2 = sidechain2.sendrawtransaction(pegin_signed2["hex"])
blocks = sidechain2.generate(3)
assert_equal(sidechain2.getrawtransaction(txid2, True, blocks[0])["confirmations"], 3)
# reconnect in case we extend the test
connect_nodes_bi(self.nodes, 2, 3)
sidechain.generate(10)

print('Success!')

# Manually stop sidechains first, then the parent chains.
Expand Down
10 changes: 10 additions & 0 deletions test/functional/wallet_address_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,5 +369,15 @@ def run_test(self):
self.test_address(4, self.nodes[4].getrawchangeaddress(), multisig=False, typ='p2sh-segwit')
self.test_address(4, self.nodes[4].getrawchangeaddress('bech32'), multisig=False, typ='bech32')

# test blech32 addresses
info_unblinded = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress("", "bech32"))
assert(len(info_unblinded["confidential_key"]) == 0)
# getnewaddress
info1 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress("", "blech32"))
assert(len(info1["confidential_key"]) > 0)
# getrawchangeaddress
info2 = self.nodes[0].getaddressinfo(self.nodes[0].getrawchangeaddress("blech32"))
assert(len(info2["confidential_key"]) > 0)

if __name__ == '__main__':
AddressTypeTest().main()

0 comments on commit 7b302eb

Please sign in to comment.