mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-14 12:43:40 +02:00
Merge #663: Add support for blinding pegins
cc49106Update getnewaddress help message with "blech32" special case (Steven Roose)56358b8Add tests for blinding pegin transactions (Steven Roose)a9f589bAdd support for blinding pegin transactions (Steven Roose)bf0cc83Add confidential assets values to listunspent documentation (Steven Roose)4e84596Always 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:
commit
7b302eb99d
5 changed files with 141 additions and 5 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue