[BROKEN] Add CA wallet RPCs

This commit is contained in:
Steven Roose 2019-03-20 11:07:57 +00:00
parent b6397d6959
commit a361dda7a3
No known key found for this signature in database
GPG key ID: 7FC91380BB4CE800
3 changed files with 435 additions and 1 deletions

View file

@ -174,9 +174,13 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "rawblindrawtransaction", 3, "inputasset" },
{ "rawblindrawtransaction", 4, "inputassetblinder" },
{ "rawblindrawtransaction", 6, "ignoreblindfail" },
{ "blindrawtransaction", 1, "assetcommitments" },
{ "blindrawtransaction", 2, "ignoreblindfail" },
{ "blindrawtransaction", 3, "blind_issuances" },
{ "sendmany", 7 , "output_assets" },
{ "sendmany", 8 , "ignoreblindfail" },
{ "sendtoaddress", 9 , "ignoreblindfail" },
{ "importblindingkey", 2, "key_is_master"},
{ "createrawtransaction", 4, "output_assets" },
};

View file

@ -1333,3 +1333,113 @@ UniValue getwalletpakinfo(const JSONRPCRequest& request)
ret.pushKV("address_lookahead", address_list);
return ret;
}
UniValue importblindingkey(const JSONRPCRequest& request)
{
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
CWallet* const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() < 2 || request.params.size() > 3)
throw std::runtime_error(
"importblindingkey \"address\" \"blindinghex\"\n"
"\nImports a private blinding key in hex for a CT address."
"\nArguments:\n"
"1. \"address\" (string, required) The CT address\n"
"2. \"hexkey\" (string, required) The blinding key in hex\n"
"3. \"key_is_master\" (bool, optional, default=false) If the `hexkey` is a master blinding key. Note: wallets can only have one master blinding key at a time. Funds could be permanently lost if user doesn't know what they are doing. Recommended use is only for wallet recovery using this in conjunction with `sethdseed`.\n"
"\nExample:\n"
+ HelpExampleCli("importblindingkey", "\"my blinded CT address\" <blindinghex>")
);
LOCK2(cs_main, pwallet->cs_wallet);
CTxDestination dest = DecodeDestination(request.params[0].get_str());
if (!IsValidDestination(dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address.");
}
if (!IsBlindDestination(dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Address is not confidential.");
}
if (!IsHex(request.params[1].get_str())) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid hexadecimal for key");
}
std::vector<unsigned char> keydata = ParseHex(request.params[1].get_str());
if (keydata.size() != 32) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid hexadecimal key length");
}
bool key_is_master = false;
if (!request.params[2].isNull()) {
key_is_master = request.params[2].get_bool();
}
CKey key;
key.Set(keydata.begin(), keydata.end(), true);
if (!key.IsValid() || key.GetPubKey() != GetDestinationBlindingKey(dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Address and key do not match");
}
uint256 keyval;
memcpy(keyval.begin(), &keydata[0], 32);
if (key_is_master) {
if (pwallet->SetMasterBlindingKey(keyval)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Failed to import master blinding key");
}
} else {
if (!pwallet->AddSpecificBlindingKey(CScriptID(GetScriptForDestination(dest)), keyval)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Failed to import blinding key");
}
}
pwallet->MarkDirty();
return NullUniValue;
}
UniValue dumpblindingkey(const JSONRPCRequest& request)
{
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
CWallet* const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() != 1)
throw std::runtime_error(
"dumpblindingkey \"address\"\n"
"\nDumps the private blinding key for a CT address in hex."
"\nArguments:\n"
"1. \"address\" (string, required) The CT address\n"
"\nResult:\n"
"\"blindingkey\" (string) The blinding key\n"
"\nExample:\n"
+ HelpExampleCli("dumpblindingkey", "\"my address\"")
);
LOCK2(cs_main, pwallet->cs_wallet);
CTxDestination dest = DecodeDestination(request.params[0].get_str());
if (!IsValidDestination(dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");
}
if (!IsBlindDestination(dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Not a CT address");
}
CScript script = GetScriptForDestination(dest);
CKey key;
key = pwallet->GetBlindingKey(&script);
if (key.IsValid()) {
CPubKey pubkey(key.GetPubKey());
if (pubkey == GetDestinationBlindingKey(dest)) {
return HexStr(key.begin(), key.end());
}
}
throw JSONRPCError(RPC_WALLET_ERROR, "Blinding key for address is unknown");
}

View file

@ -5334,11 +5334,328 @@ UniValue claimpegin(const JSONRPCRequest& request)
return mtx.GetHash().GetHex();
}
// Rewind the outputs to unblinded, and push placeholders for blinding info. Not exported.
void FillBlinds(CWallet* pwallet, CMutableTransaction& tx, std::vector<uint256>& output_value_blinds, std::vector<uint256>& output_asset_blinds, std::vector<CPubKey>& output_pubkeys, std::vector<CKey>& asset_keys, std::vector<CKey>& token_keys) {
// Resize witness before doing anything
tx.witness.vtxinwit.resize(tx.vin.size());
tx.witness.vtxoutwit.resize(tx.vout.size());
for (size_t nOut = 0; nOut < tx.vout.size(); ++nOut) {
CTxOut& out = tx.vout[nOut];
if (out.nValue.IsExplicit()) {
CPubKey pubkey(out.nNonce.vchCommitment);
if (!pubkey.IsFullyValid()) {
output_pubkeys.push_back(CPubKey());
} else {
output_pubkeys.push_back(pubkey);
}
output_value_blinds.push_back(uint256());
output_asset_blinds.push_back(uint256());
} else if (out.nValue.IsCommitment()) {
CTxOutWitness* ptxoutwit = &tx.witness.vtxoutwit[nOut];
uint256 blinding_factor;
uint256 asset_blinding_factor;
CAsset asset;
CAmount amount;
// This can only be used to recover things like change addresses and self-sends.
if (UnblindConfidentialPair(pwallet->GetBlindingKey(&out.scriptPubKey), out.nValue, out.nAsset, out.nNonce, out.scriptPubKey, ptxoutwit->vchRangeproof, amount, blinding_factor, asset, asset_blinding_factor) != 0) {
// Wipe out confidential info from output and output witness
CScript scriptPubKey = tx.vout[nOut].scriptPubKey;
CTxOut newOut(asset, amount, scriptPubKey);
tx.vout[nOut] = newOut;
ptxoutwit->SetNull();
// Mark for re-blinding with same key that deblinded it
CPubKey pubkey(pwallet->GetBlindingKey(&out.scriptPubKey).GetPubKey());
output_pubkeys.push_back(pubkey);
output_value_blinds.push_back(uint256());
output_asset_blinds.push_back(uint256());
} else {
output_pubkeys.push_back(CPubKey());
output_value_blinds.push_back(uint256());
output_asset_blinds.push_back(uint256());
}
} else {
// Null or invalid, do nothing for that output
output_pubkeys.push_back(CPubKey());
output_value_blinds.push_back(uint256());
output_asset_blinds.push_back(uint256());
}
}
// Fill out issuance blinding keys to be used directly as nonce for rangeproof
for (size_t nIn = 0; nIn < tx.vin.size(); ++nIn) {
CAssetIssuance& issuance = tx.vin[nIn].assetIssuance;
if (issuance.IsNull()) {
asset_keys.push_back(CKey());
token_keys.push_back(CKey());
continue;
}
// Calculate underlying asset for use as blinding key script
CAsset asset;
// New issuance, compute the asset ids
if (issuance.assetBlindingNonce.IsNull()) {
uint256 entropy;
GenerateAssetEntropy(entropy, tx.vin[nIn].prevout, issuance.assetEntropy);
CalculateAsset(asset, entropy);
}
// Re-issuance
else {
// TODO Give option to skip blinding when the reissuance token was derived without blinding ability. For now we assume blinded
// hashAssetIdentifier doubles as the entropy on reissuance
CalculateAsset(asset, issuance.assetEntropy);
}
// Special format for issuance blinding keys, unique for each transaction
CScript blindingScript = CScript() << OP_RETURN << std::vector<unsigned char>(tx.vin[nIn].prevout.hash.begin(), tx.vin[nIn].prevout.hash.end()) << tx.vin[nIn].prevout.n;
for (size_t nPseudo = 0; nPseudo < 2; nPseudo++) {
bool issuance_asset = (nPseudo == 0);
std::vector<CKey>& issuance_blinding_keys = issuance_asset ? asset_keys : token_keys;
CConfidentialValue& conf_value = issuance_asset ? issuance.nAmount : issuance.nInflationKeys;
if (conf_value.IsCommitment()) {
// Rangeproof must exist
if (tx.witness.vtxinwit.size() <= nIn) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Transaction issuance is already blinded but has no attached rangeproof.");
}
CTxInWitness& txinwit = tx.witness.vtxinwit[nIn];
std::vector<unsigned char>& vchRangeproof = issuance_asset ? txinwit.vchIssuanceAmountRangeproof : txinwit.vchInflationKeysRangeproof;
uint256 blinding_factor;
uint256 asset_blinding_factor;
CAmount amount;
if (UnblindConfidentialPair(pwallet->GetBlindingKey(&blindingScript), conf_value, CConfidentialAsset(asset), CConfidentialNonce(), CScript(), vchRangeproof, amount, blinding_factor, asset, asset_blinding_factor) != 0) {
// Wipe out confidential info from issuance
vchRangeproof.clear();
conf_value = CConfidentialValue(amount);
// One key blinds both values, single key needed for issuance reveal
issuance_blinding_keys.push_back(pwallet->GetBlindingKey(&blindingScript));
} else {
// If unable to unblind, leave it alone in next blinding step
issuance_blinding_keys.push_back(CKey());
}
} else if (conf_value.IsExplicit()) {
// Use wallet to generate blindingkey used directly as nonce
// as user is not "sending" to anyone.
// Always assumed we want to blind here.
// TODO Signal intent for all blinding via API including replacing nonce commitment
issuance_blinding_keys.push_back(pwallet->GetBlindingKey(&blindingScript));
} else {
// Null or invalid, don't try anything but append an empty key
issuance_blinding_keys.push_back(CKey());
}
}
}
}
UniValue blindrawtransaction(const JSONRPCRequest& request)
{
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
CWallet* const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp))
return NullUniValue;
if (request.fHelp || (request.params.size() < 1 || request.params.size() > 5))
throw std::runtime_error(
"blindrawtransaction \"hexstring\" ( ignoreblindfail [\"assetcommitment,...\"] blind_issuances \"totalblinder\" )\n"
"\nConvert one or more outputs of a raw transaction into confidential ones using only wallet inputs.\n"
"Returns the hex-encoded raw transaction.\n"
"The output keys used can be specified by using a confidential address in createrawtransaction.\n"
"This call may add an additional 0-value unspendable output in order to balance the blinders.\n"
"\nArguments:\n"
"1. \"hexstring\", (string, required) A hex-encoded raw transaction.\n"
"2. \"ignoreblindfail\"\" (bool, optional, default=true) Return a transaction even when a blinding attempt fails due to number of blinded inputs/outputs.\n"
"3. \"asset_commitments\" \n"
" [ (array, optional) An array of input asset generators. If provided, this list must be empty, or match the final input commitment list, including ordering, to make a valid surjection proof. This list does not include generators for issuances, as these assets are inherently unblinded.\n"
" \"assetcommitment\" (string, optional) A hex-encoded asset commitment, one for each input.\n"
" Null commitments must be \"\".\n"
" ],\n"
"4. \"blind_issuances\" (bool, optional, default=true) Blind the issuances found in the raw transaction or not. All issuances will be blinded if true. \n"
"5. \"totalblinder\" (string, optional) Ignored for now.\n"
"\nResult:\n"
"\"transaction\" (string) hex string of the transaction\n"
);
std::vector<unsigned char> txData(ParseHexV(request.params[0], "argument 1"));
CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION);
CMutableTransaction tx;
try {
ssData >> tx;
} catch (const std::exception &) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
}
bool ignore_blind_fail = true;
if (request.params.size() > 1) {
ignore_blind_fail = request.params[1].get_bool();
}
std::vector<std::vector<unsigned char> > auxiliary_generators;
if (request.params.size() > 2) {
UniValue assetCommitments = request.params[2].get_array();
if (assetCommitments.size() != 0 && assetCommitments.size() != tx.vin.size()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Asset commitment array must have exactly as many entries as transaction inputs.");
}
for (size_t nIn = 0; nIn < assetCommitments.size(); nIn++) {
if (assetCommitments[nIn].isStr()) {
std::string assetcommitment = assetCommitments[nIn].get_str();
if (IsHex(assetcommitment) && assetcommitment.size() == 66) {
auxiliary_generators.push_back(ParseHex(assetcommitment));
continue;
}
}
throw JSONRPCError(RPC_INVALID_PARAMETER, "Asset commitments must be a hex encoded string of length 66.");
}
}
bool blind_issuances = request.params[3].isNull() || request.params[3].get_bool();
LOCK(pwallet->cs_wallet);
std::vector<uint256> input_blinds;
std::vector<uint256> input_asset_blinds;
std::vector<CAsset> input_assets;
std::vector<CAmount> input_amounts;
int n_blinded_ins = 0;
for (size_t nIn = 0; nIn < tx.vin.size(); ++nIn) {
COutPoint prevout = tx.vin[nIn].prevout;
std::map<uint256, CWalletTx>::iterator it = pwallet->mapWallet.find(prevout.hash);
if (it == pwallet->mapWallet.end()) {
// For inputs we don't own input assetcommitments for the surjection must be supplied
if (auxiliary_generators.size() > 0) {
input_blinds.push_back(uint256());
input_asset_blinds.push_back(uint256());
input_assets.push_back(CAsset());
input_amounts.push_back(-1);
continue;
}
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter: transaction spends from non-wallet output and no assetcommitment list was given.");
}
if (prevout.n >= it->second.tx->vout.size()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter: transaction spends non-existing output");
}
input_blinds.push_back(it->second.GetOutputAmountBlindingFactor(prevout.n));
input_asset_blinds.push_back(it->second.GetOutputAssetBlindingFactor(prevout.n));
input_assets.push_back(it->second.GetOutputAsset(prevout.n));
input_amounts.push_back(it->second.GetOutputValueOut(prevout.n));
if (it->second.tx->vout[prevout.n].nValue.IsCommitment()) {
n_blinded_ins += 1;
}
}
std::vector<uint256> output_blinds;
std::vector<uint256> output_asset_blinds;
std::vector<CPubKey> output_pubkeys;
std::vector<CKey> asset_keys;
std::vector<CKey> token_keys;
// This fills out issuance blinding data for you from the wallet itself
FillBlinds(pwallet, tx, output_blinds, output_asset_blinds, output_pubkeys, asset_keys, token_keys);
if (!blind_issuances) {
asset_keys.clear();
token_keys.clear();
}
// How many are we trying to blind?
int num_pubkeys = 0;
unsigned int key_index = 0;
for (unsigned int i = 0; i < output_pubkeys.size(); i++) {
const CPubKey& key = output_pubkeys[i];
if (key.IsValid()) {
num_pubkeys++;
key_index = i;
}
}
for (const CKey& key : asset_keys) {
if (key.IsValid()) num_pubkeys++;
}
for (const CKey& key : token_keys) {
if (key.IsValid()) num_pubkeys++;
}
if (num_pubkeys == 0 && n_blinded_ins == 0) {
// Vacuous, just return the transaction
return EncodeHexTx(tx);
} else if (n_blinded_ins > 0 && num_pubkeys == 0) {
// Blinded inputs need to balanced with something to be valid, make a dummy.
CTxOut newTxOut(tx.vout.back().nAsset.GetAsset(), 0, CScript() << OP_RETURN);
tx.vout.push_back(newTxOut);
num_pubkeys++;
output_pubkeys.push_back(pwallet->GetBlindingPubKey(newTxOut.scriptPubKey));
} else if (n_blinded_ins == 0 && num_pubkeys == 1) {
if (ignore_blind_fail) {
// Just get rid of the ECDH key in the nonce field and return
tx.vout[key_index].nNonce.SetNull();
return EncodeHexTx(tx);
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Unable to blind transaction: Add another output to blind in order to complete the blinding.");
}
}
if (BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, asset_keys, token_keys, tx, (auxiliary_generators.size() ? &auxiliary_generators : NULL)) != num_pubkeys) {
// TODO Have more rich return values, communicating to user what has been blinded
// User may be ok not blinding something that for instance has no corresponding type on input
throw JSONRPCError(RPC_INVALID_PARAMETER, "Unable to blind transaction: Are you sure each asset type to blind is represented in the inputs?");
}
return EncodeHexTx(tx);
}
static UniValue unblindrawtransaction(const JSONRPCRequest& request)
{
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
CWallet* const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() != 1)
throw std::runtime_error(
"unblindrawtransaction \"hex\"\n"
"\nRecovers unblinded transaction outputs from blinded outputs and issuance inputs when possible using wallet's known blinding keys, and strips related witness data.\n"
"\nArguments:\n"
"1. \"hex\" (string, required) The hex string of the raw transaction\n"
"\nResult:\n"
"{\n"
" \"hex\": \"value\", (string) The resulting unblinded raw transaction (hex-encoded string)\n"
"}\n"
"\nExamples:\n"
+ HelpExampleCli("unblindrawtransaction", "\"blindedtransactionhex\"")
);
RPCTypeCheck(request.params, {UniValue::VSTR});
CMutableTransaction tx;
if (!DecodeHexTx(tx, request.params[0].get_str()))
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
std::vector<uint256> output_value_blinds;
std::vector<uint256> output_asset_blinds;
std::vector<CPubKey> output_pubkeys;
std::vector<CKey> asset_keys;
std::vector<CKey> token_keys;
FillBlinds(pwallet, tx, output_value_blinds, output_asset_blinds, output_pubkeys, asset_keys, token_keys);
UniValue result(UniValue::VOBJ);
result.pushKV("hex", EncodeHexTx(tx));
return result;
}
// END ELEMENTS commands
//
UniValue abortrescan(const JSONRPCRequest& request); // in rpcdump.cpp
UniValue dumpprivkey(const JSONRPCRequest& request); // in rpcdump.cpp
UniValue importblindingkey(const JSONRPCRequest& request); // in rpcdump.cpp
UniValue dumpblindingkey(const JSONRPCRequest& request); // in rpcdump.cpp
UniValue importprivkey(const JSONRPCRequest& request);
UniValue importaddress(const JSONRPCRequest& request);
UniValue importpubkey(const JSONRPCRequest& request);
@ -5413,10 +5730,13 @@ static const CRPCCommand commands[] =
{ "wallet", "getpeginaddress", &getpeginaddress, {} },
{ "wallet", "claimpegin", &claimpegin, {"bitcoin_tx", "txoutproof", "claim_script"} },
{ "wallet", "createrawpegin", &createrawpegin, {"bitcoin_tx", "txoutproof", "claim_script"} },
{ "wallet", "blindrawtransaction", &blindrawtransaction, {"hexstring", "ignoreblindfail", "assetcommitment", "blind_issuances", "totalblinder"} },
{ "wallet", "unblindrawtransaction", &unblindrawtransaction, {"hex"} },
{ "wallet", "sendtomainchain", &sendtomainchain, {"address", "amount", "subtractfeefromamount"} },
{ "wallet", "initpegoutwallet", &initpegoutwallet, {"bitcoin_descriptor", "bip32_counter", "liquid_pak"} },
{ "wallet", "getwalletpakinfo", &getwalletpakinfo, {} },
{ "wallet", "importblindingkey", &importblindingkey, {"address", "hexkey", "key_is_master"}},
{ "wallet", "dumpblindingkey", &dumpblindingkey, {"address"}},
{ "wallet", "signblock", &signblock, {"blockhex"}},
};
// clang-format on