Skip to content

Commit

Permalink
Make best-effort to blind desired outputs, fail or return depending o…
Browse files Browse the repository at this point in the history
…n `ignoreblindfail` arguments.
  • Loading branch information
instagibbs committed May 10, 2017
1 parent 2aa6e1a commit a3f68d0
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 58 deletions.
11 changes: 5 additions & 6 deletions qa/rpc-tests/confidential_transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,11 @@ def run_test(self):
"nValue": unspent[0]["amount"]}],
{unconfidential_address: unspent[0]["amount"] - fee, "fee":fee});

# Test that blindrawtransaction returns an exception
try:
tx = self.nodes[0].blindrawtransaction(tx)
raise AssertionError("blindrawtransaction RPC should fail, but it doesn't")
except JSONRPCException:
pass
# Test that blindrawtransaction adds an OP_RETURN output to balance blinders
temptx = self.nodes[0].blindrawtransaction(tx)
decodedtx = self.nodes[0].decoderawtransaction(temptx)
assert_equal(decodedtx["vout"][-1]["scriptPubKey"]["asm"], "OP_RETURN")
assert_equal(len(decodedtx["vout"]), 3)

# Create same transaction but with a change/dummy output.
# It should pass the blinding step.
Expand Down
2 changes: 2 additions & 0 deletions src/rpc/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "getnetworkhashps", 1 },
{ "sendtoaddress", 1 },
{ "sendtoaddress", 4 },
{ "sendtoaddress", 6 },
{ "destroyamount", 1 },
{ "settxfee", 0 },
{ "getreceivedbyaddress", 1 },
Expand Down Expand Up @@ -78,6 +79,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "createrawtransaction", 2 },
{ "createrawtransaction", 3},
{ "blindrawtransaction", 1 },
{ "blindrawtransaction", 2 },
{ "dumpissuanceblindingkey", 1},
{ "importissuanceblindingkey", 1},
{ "rawblindrawtransaction", 1 },
Expand Down
118 changes: 96 additions & 22 deletions src/rpc/rawtransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -709,14 +709,14 @@ void FillBlinds(CMutableTransaction& tx, bool fUseWallet, std::vector<uint256>&

UniValue rawblindrawtransaction(const UniValue& params, bool fHelp)
{
if (fHelp || (params.size() < 3 || params.size() > 6))
if (fHelp || (params.size() < 5 || params.size() > 7))
throw runtime_error(
"rawblindrawtransaction \"hexstring\" [\"inputblinder\",...] [\"totalblinder\"]\n"
"rawblindrawtransaction \"hexstring\" [\"inputblinder\",...] [\"totalblinder\"] ignoreblindfail\n"
"\nConvert one or more outputs of a raw transaction into confidential ones.\n"
"Returns the hex-encoded raw transaction.\n"
"If at least one of the inputs is confidential, at least one of the outputs must be.\n"
"The input raw transaction cannot have already-blinded outputs.\n"
"The output keys used can be specified by using a confidential address in createrawtransaction.\n"
"If an additional blinded output is required to make a balanced blinding, a 0-value unspendable output will be added. Since there is no access to the wallet the blinding pubkey from the last output with blinding key will be repeated.\n"

"\nArguments:\n"
"1. \"hexstring\", (string, required) A hex-encoded raw transaction.\n"
Expand All @@ -734,15 +734,18 @@ UniValue rawblindrawtransaction(const UniValue& params, bool fHelp)
" \"inputassetblinder\" (string, required) A hex-encoded asset blinding factor, one for each input.\n"
" ],\n"
"6. \"totalblinder\" (string, optional) Ignored for now.\n"
"7. \"ignoreblindfail\"\" (bool, default=true) Return a transaction even when a blinding attempt fails due to number of blinded inputs/outputs.\n"

"\nResult:\n"
"\"transaction\" (string) hex string of the transaction\n"
);

if (params.size() == 3) {
if (params.size() == 5) {
RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)(UniValue::VARR)(UniValue::VARR)(UniValue::VARR)(UniValue::VARR));
} else {
} else if (params.size() == 6) {
RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)(UniValue::VARR)(UniValue::VARR)(UniValue::VARR)(UniValue::VARR)(UniValue::VSTR));
} else {
RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)(UniValue::VARR)(UniValue::VARR)(UniValue::VARR)(UniValue::VARR)(UniValue::VSTR)(UniValue::VBOOL));
}

vector<unsigned char> txData(ParseHexV(params[0], "argument 1"));
Expand All @@ -759,6 +762,13 @@ UniValue rawblindrawtransaction(const UniValue& params, bool fHelp)
UniValue inputAssets = params[3].get_array();
UniValue inputAssetBlinds = params[4].get_array();

bool fIgnoreBlindFail = true;
if (params.size() > 6) {
fIgnoreBlindFail = params[6].get_bool();
}

int n_blinded_ins = 0;

if (inputBlinds.size() != tx.vin.size()) throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter: one (potentially empty) input blind for each input must be provided"));
if (inputAmounts.size() != tx.vin.size()) throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter: one (potentially empty) input blind for each input must be provided"));
if (inputAssets.size() != tx.vin.size()) throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter: one (potentially empty) input asset id for each input must be provided"));
Expand Down Expand Up @@ -796,6 +806,10 @@ UniValue rawblindrawtransaction(const UniValue& params, bool fHelp)
input_asset_blinds.push_back(uint256S(assetblind));
input_assets.push_back(CAsset(uint256S(asset)));
input_amounts.push_back(inputAmounts[nIn].get_int64());

if (!input_blinds.back().IsNull()) {
n_blinded_ins++;
}
}

std::vector<CKey> asset_keys;
Expand All @@ -804,14 +818,40 @@ UniValue rawblindrawtransaction(const UniValue& params, bool fHelp)

// How many are we trying to blind?
int numPubKeys = 0;
for (auto&& key : output_pubkeys) {
unsigned int keyIndex = 0;
for (unsigned int i = 0; i < output_pubkeys.size(); i++) {
const CPubKey& key = output_pubkeys[i];
if (key.IsValid()) {
numPubKeys++;
keyIndex = i;
}
}

if (numPubKeys == 0 && n_blinded_ins == 0) {
// Vacuous, just return the transaction
return EncodeHexTx(tx);
} else if (n_blinded_ins > 0 && numPubKeys == 0) {
// Blinded inputs need to balanced with something to be valid, make a dummy.
// No privacy lost because all outputs are explicit anyways.
CTxOut newTxOut(tx.vout.back().nAsset.GetAsset(), 0, CScript() << OP_RETURN);
tx.vout.push_back(newTxOut);
numPubKeys++;
// Just copy some non-zero key
output_pubkeys.push_back(output_pubkeys[keyIndex]);
} else if (n_blinded_ins == 0 && numPubKeys == 1) {
if (fIgnoreBlindFail) {
// Just get rid of the ECDH key in the nonce field and return
tx.vout[keyIndex].nNonce.SetNull();
return EncodeHexTx(tx);
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Unable to blind transaction: Add another output to blind in order to complete the blinding."));
}
}

if (numPubKeys == 0 || BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_value_blinds, output_asset_blinds, output_pubkeys, std::vector<CKey>(), std::vector<CKey>(), tx) != numPubKeys) {
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Unable to blind transaction: add an additional output with a blinding pubkey"));
if (BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_value_blinds, output_asset_blinds, output_pubkeys, std::vector<CKey>(), std::vector<CKey>(), tx) != numPubKeys) {
// 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, string("Unable to blind transaction: Are you sure each asset type to blind is represented in the inputs?"));
}

return EncodeHexTx(tx);
Expand All @@ -820,21 +860,22 @@ UniValue rawblindrawtransaction(const UniValue& params, bool fHelp)
#ifdef ENABLE_WALLET
UniValue blindrawtransaction(const UniValue& params, bool fHelp)
{
if (fHelp || (params.size() != 1 && params.size() != 2))
if (fHelp || (params.size() < 1 || params.size() > 4))
throw runtime_error(
"blindrawtransaction \"hexstring\" [\"totalblinder\"]\n"
"blindrawtransaction \"hexstring\" [\"assetcommitments\"] [\"totalblinder\"] ignoreblindfail\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"
"If at least one of the inputs is confidential, at least one of the outputs must be.\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. [ (array, optional) An array of input asset generators. If provided, this list must 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"
"2. \"ignoreblindfail\"\" (bool, default=true) Return a transaction even when a blinding attempt fails due to number of blinded inputs/outputs.\n"
"3. [ (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"
" \"assetcommitments\" (string, optional) A hex-encoded asset commitment, one for each input.\n"
" Null commitments must be \"\".\n"
" ],\n"
"3. \"totalblinder\" (string, optional) Ignored for now.\n"
"4. \"totalblinder\" (string, optional) Ignored for now.\n"

"\nResult:\n"
"\"transaction\" (string) hex string of the transaction\n"
Expand All @@ -843,9 +884,11 @@ UniValue blindrawtransaction(const UniValue& params, bool fHelp)
if (params.size() == 1) {
RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR));
} else if (params.size() == 2){
RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)(UniValue::VARR));
RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)(UniValue::VBOOL));
} else if (params.size() == 3){
RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)(UniValue::VARR)(UniValue::VSTR));
RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)(UniValue::VBOOL)(UniValue::VARR));
} else {
RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)(UniValue::VBOOL)(UniValue::VARR)(UniValue::VSTR));
}

vector<unsigned char> txData(ParseHexV(params[0], "argument 1"));
Expand All @@ -857,10 +900,15 @@ UniValue blindrawtransaction(const UniValue& params, bool fHelp)
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
}

std::vector<std::vector<unsigned char> > auxiliary_generators;
bool fIgnoreBlindFail = true;
if (params.size() > 1) {
UniValue assetCommitments = params[1].get_array();
if (assetCommitments.size() < tx.vin.size()) {
fIgnoreBlindFail = params[1].get_bool();
}

std::vector<std::vector<unsigned char> > auxiliary_generators;
if (params.size() > 2) {
UniValue assetCommitments = params[2].get_array();
if (assetCommitments.size() != 0 && assetCommitments.size() < tx.vin.size()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Asset commitment array must have at least as many entries as transaction inputs.");
}
for (size_t nIn = 0; nIn < assetCommitments.size(); nIn++) {
Expand All @@ -885,6 +933,7 @@ UniValue blindrawtransaction(const UniValue& params, bool fHelp)
std::vector<uint256> output_asset_blinds;
std::vector<CAsset> output_assets;
std::vector<CPubKey> output_pubkeys;
int n_blinded_ins = 0;
for (size_t nIn = 0; nIn < tx.vin.size(); nIn++) {

std::map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.find(tx.vin[nIn].prevout.hash);
Expand Down Expand Up @@ -916,6 +965,7 @@ UniValue blindrawtransaction(const UniValue& params, bool fHelp)
}
else {
input_amounts.push_back(it->second.GetOutputValueOut(tx.vin[nIn].prevout.n));
n_blinded_ins += 1;
}
}

Expand All @@ -925,15 +975,39 @@ UniValue blindrawtransaction(const UniValue& params, bool fHelp)

// How many are we trying to blind?
int numPubKeys = 0;
for (auto&& key : output_pubkeys) {
unsigned int keyIndex = 0;
for (unsigned int i = 0; i < output_pubkeys.size(); i++) {
const CPubKey& key = output_pubkeys[i];
if (key.IsValid()) {
numPubKeys++;
keyIndex = i;
}
}

if (numPubKeys == 0 && n_blinded_ins == 0) {
// Vacuous, just return the transaction
return EncodeHexTx(tx);
} else if (n_blinded_ins > 0 && numPubKeys == 0) {
// Blinded inputs need to balanced with something to be valid, make a dummy.
// No privacy lost because all outputs are explicit anyways.
CTxOut newTxOut(tx.vout.back().nAsset.GetAsset(), 0, CScript() << OP_RETURN);
tx.vout.push_back(newTxOut);
numPubKeys++;
output_pubkeys.push_back(pwalletMain->GetBlindingPubKey(newTxOut.scriptPubKey));
} else if (n_blinded_ins == 0 && numPubKeys == 1) {
if (fIgnoreBlindFail) {
// Just get rid of the ECDH key in the nonce field and return
tx.vout[keyIndex].nNonce.SetNull();
return EncodeHexTx(tx);
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Unable to blind transaction: Add another output to blind in order to complete the blinding."));
}
}

// Something must become blinded, and all attempts must work
if (numPubKeys == 0 || BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, std::vector<CKey>(), std::vector<CKey>(), tx, (auxiliary_generators.size() ? &auxiliary_generators : NULL)) != numPubKeys) {
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Unable to blind transaction: add an additional output with a blinding pubkey"));
if (BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, std::vector<CKey>(), std::vector<CKey>(), tx, (auxiliary_generators.size() ? &auxiliary_generators : NULL)) != numPubKeys) {
// 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, string("Unable to blind transaction: Are you sure each asset type to blind is represented in the inputs?"));
}

return EncodeHexTx(tx);
Expand Down
Loading

0 comments on commit a3f68d0

Please sign in to comment.