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
This commit is contained in:
Gregory Sanders 2019-06-21 11:05:11 -04:00
commit 7b302eb99d
No known key found for this signature in database
GPG key ID: F3F68E2D86A48FDB
5 changed files with 141 additions and 5 deletions

View file

@ -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;
}
}
@ -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

View file

@ -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())

View file

@ -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."},
@ -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()) {
@ -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);
}
@ -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);
@ -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);
}
@ -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"
@ -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.

View file

@ -6,6 +6,7 @@ from test_framework.authproxy import JSONRPCException
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,
@ -13,13 +14,20 @@ from test_framework.util import (
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,
@ -524,6 +532,79 @@ class FedPegTest(BitcoinTestFramework):
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.

View file

@ -369,5 +369,15 @@ class AddressTypeTest(BitcoinTestFramework):
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()