Make best-effort to blind desired outputs, fail or return depending on ignoreblindfail arguments.

This commit is contained in:
Gregory Sanders 2017-04-25 11:55:41 -04:00
parent 2aa6e1ac11
commit a3f68d0db7
6 changed files with 189 additions and 58 deletions

View file

@ -145,12 +145,11 @@ class CTTest (BitcoinTestFramework):
"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.

View file

@ -33,6 +33,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "getnetworkhashps", 1 },
{ "sendtoaddress", 1 },
{ "sendtoaddress", 4 },
{ "sendtoaddress", 6 },
{ "destroyamount", 1 },
{ "settxfee", 0 },
{ "getreceivedbyaddress", 1 },
@ -78,6 +79,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "createrawtransaction", 2 },
{ "createrawtransaction", 3},
{ "blindrawtransaction", 1 },
{ "blindrawtransaction", 2 },
{ "dumpissuanceblindingkey", 1},
{ "importissuanceblindingkey", 1},
{ "rawblindrawtransaction", 1 },

View file

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

View file

@ -388,7 +388,7 @@ UniValue getaddressesbyaccount(const UniValue& params, bool fHelp)
return ret;
}
static void SendMoney(const CScript& scriptPubKey, CAmount nValue, CAsset asset, bool fSubtractFeeFromAmount, const CPubKey &confidentiality_key, CWalletTx& wtxNew)
static void SendMoney(const CScript& scriptPubKey, CAmount nValue, CAsset asset, bool fSubtractFeeFromAmount, const CPubKey &confidentiality_key, CWalletTx& wtxNew, bool fIgnoreBlindFail)
{
CAmount curBalance = pwalletMain->GetBalance()[asset];
@ -417,7 +417,7 @@ static void SendMoney(const CScript& scriptPubKey, CAmount nValue, CAsset asset,
int nChangePosRet = -1;
CRecipient recipient = {scriptPubKey, nValue, asset, confidentiality_key, fSubtractFeeFromAmount};
vecSend.push_back(recipient);
if (!pwalletMain->CreateTransaction(vecSend, wtxNew, vpChangeKey, nFeeRequired, nChangePosRet, strError, NULL, true, NULL, true, NULL, NULL, NULL)) {
if (!pwalletMain->CreateTransaction(vecSend, wtxNew, vpChangeKey, nFeeRequired, nChangePosRet, strError, NULL, true, NULL, true, NULL, NULL, NULL, fIgnoreBlindFail)) {
if (!fSubtractFeeFromAmount && nValue + nFeeRequired > curBalance)
strError = strprintf("Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds!", FormatMoney(nFeeRequired));
throw JSONRPCError(RPC_WALLET_ERROR, strError);
@ -426,9 +426,9 @@ static void SendMoney(const CScript& scriptPubKey, CAmount nValue, CAsset asset,
throw JSONRPCError(RPC_WALLET_ERROR, "Error: The transaction was rejected! This might happen if some of the coins in your wallet were already spent, such as if you used a copy of the wallet and coins were spent in the copy but not marked as spent here.");
}
static void SendMoney(const CTxDestination &address, CAmount nValue, CAsset asset, bool fSubtractFeeFromAmount, const CPubKey &confidentiality_key, CWalletTx& wtxNew)
static void SendMoney(const CTxDestination &address, CAmount nValue, CAsset asset, bool fSubtractFeeFromAmount, const CPubKey &confidentiality_key, CWalletTx& wtxNew, bool fIgnoreBlindFail)
{
SendMoney(GetScriptForDestination(address), nValue, asset, fSubtractFeeFromAmount, confidentiality_key, wtxNew);
SendMoney(GetScriptForDestination(address), nValue, asset, fSubtractFeeFromAmount, confidentiality_key, wtxNew, fIgnoreBlindFail);
}
static void SendGenerationTransaction(const CScript& assetScriptPubKey, const CPubKey &assetKey, const CScript& tokenScriptPubKey, const CPubKey &tokenKey, CAmount nAmountAsset, CAmount nTokens, bool fBlindIssuances, uint256& entropy, CAsset& reissuanceAsset, CAsset& reissuanceToken, CWalletTx& wtxNew)
@ -486,9 +486,9 @@ UniValue sendtoaddress(const UniValue& params, bool fHelp)
if (!EnsureWalletIsAvailable(fHelp))
return NullUniValue;
if (fHelp || params.size() < 2 || params.size() > 6)
if (fHelp || params.size() < 2 || params.size() > 7)
throw runtime_error(
"sendtoaddress \"bitcoinaddress\" amount ( \"comment\" \"comment-to\" subtractfeefromamount assetlabel )\n"
"sendtoaddress \"bitcoinaddress\" amount ( \"comment\" \"comment-to\" subtractfeefromamount assetlabel ignoreblindfail )\n"
"\nSend an amount to a given address.\n"
+ HelpRequiringPassphrase() +
"\nArguments:\n"
@ -500,8 +500,9 @@ UniValue sendtoaddress(const UniValue& params, bool fHelp)
" to which you're sending the transaction. This is not part of the \n"
" transaction, just kept in your wallet.\n"
"5. subtractfeefromamount (boolean, optional, default=false) The fee will be deducted from the amount being sent.\n"
"6. \"assetlabel\" (string, optional) Hex asset id or asset label for balance.\n"
" The recipient will receive less bitcoins than you enter in the amount field.\n"
"6. \"assetlabel\" (string, optional) Hex asset id or asset label for balance.\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"
"\"transactionid\" (string) The transaction id.\n"
"\nExamples:\n"
@ -544,11 +545,15 @@ UniValue sendtoaddress(const UniValue& params, bool fHelp)
strasset = params[5].get_str();
}
bool fIgnoreBlindFail = true;
if (params.size() > 6)
fIgnoreBlindFail = params[6].get_bool();
CAsset asset = GetAssetFromString(strasset);
EnsureWalletIsUnlocked();
SendMoney(address.Get(), nAmount, asset, fSubtractFeeFromAmount, confidentiality_pubkey, wtx);
SendMoney(address.Get(), nAmount, asset, fSubtractFeeFromAmount, confidentiality_pubkey, wtx, fIgnoreBlindFail);
std::string blinds;
for (unsigned int i=0; i<wtx.vout.size(); i++) {
@ -601,7 +606,7 @@ UniValue destroyamount(const UniValue& params, bool fHelp)
CPubKey confidentiality_pubkey;
EnsureWalletIsUnlocked();
SendMoney(destroyScript, nAmount, asset, false, confidentiality_pubkey, wtx);
SendMoney(destroyScript, nAmount, asset, false, confidentiality_pubkey, wtx, true);
std::string blinds;
for (unsigned int i=0; i<wtx.vout.size(); i++) {
@ -1062,6 +1067,7 @@ UniValue sendmany(const UniValue& params, bool fHelp)
" \"address\": \"hex\" \n"
" ...\n"
" }\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"
"\"transactionid\" (string) The transaction id for the send. Only 1 transaction is created regardless of \n"
" the number of addresses.\n"
@ -1100,6 +1106,10 @@ UniValue sendmany(const UniValue& params, bool fHelp)
assets = params[5].get_obj();
}
bool fIgnoreBlindFail = true;
if (params.size() > 6)
fIgnoreBlindFail = params[6].get_bool();
set<CBitcoinAddress> setAddress;
vector<CRecipient> vecSend;
@ -1173,7 +1183,7 @@ UniValue sendmany(const UniValue& params, bool fHelp)
CAmount nFeeRequired = 0;
int nChangePosRet = -1;
string strFailReason;
bool fCreated = pwalletMain->CreateTransaction(vecSend, wtx, vpChangeKey, nFeeRequired, nChangePosRet, strFailReason);
bool fCreated = pwalletMain->CreateTransaction(vecSend, wtx, vpChangeKey, nFeeRequired, nChangePosRet, strFailReason, NULL, true, NULL, false, NULL, NULL, NULL, fIgnoreBlindFail);
if (!fCreated)
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason);
if (!pwalletMain->CommitTransaction(wtx, vpChangeKey))
@ -2792,6 +2802,7 @@ UniValue fundrawtransaction(const UniValue& params, bool fHelp)
"in the wallet using importaddress or addmultisigaddress (to calculate fees).\n"
"You can see whether this is the case by checking the \"solvable\" field in the listunspent output.\n"
"Only pay-to-pubkey, multisig, and P2SH versions thereof are currently supported for watch-only\n"
"Note: Existing fee outputs will be dropped to aid fee estimation\n"
"\nArguments:\n"
"1. \"hexstring\" (string, required) The hex string of the raw transaction\n"
"2. options (object, optional)\n"
@ -3150,7 +3161,7 @@ UniValue sendtomainchain(const UniValue& params, bool fHelp)
EnsureWalletIsUnlocked();
CWalletTx wtxNew;
SendMoney(scriptPubKey, nAmount, BITCOINID, false, CPubKey(), wtxNew);
SendMoney(scriptPubKey, nAmount, BITCOINID, false, CPubKey(), wtxNew, true);
std::string blinds;
for (unsigned int i=0; i<wtxNew.vout.size(); i++) {

View file

@ -2438,7 +2438,7 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, bool ov
}
bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wtxNew, std::vector<CReserveKey*>& vpChangeKey, CAmount& nFeeRet,
int& nChangePosInOut, std::string& strFailReason, const CCoinControl* coinControl, bool sign, std::vector<CAmount> *outAmounts, bool fBlindIssuances, const uint256* issuanceEntropy, const CAsset* reissuanceAsset, const CAsset* reissuanceToken)
int& nChangePosInOut, std::string& strFailReason, const CCoinControl* coinControl, bool sign, std::vector<CAmount> *outAmounts, bool fBlindIssuances, const uint256* issuanceEntropy, const CAsset* reissuanceAsset, const CAsset* reissuanceToken, bool fIgnoreBlindFail)
{
// TODO re-enable to support multiple assets in a logical fashion, since the number of possible
// change positions are number of assets being spent.
@ -2522,6 +2522,12 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
nChangePosInOut = nChangePosRequest;
std::vector<CPubKey> output_pubkeys;
int numToBlind = 0;
int changeToBlind = 0;
int numInputsBlinded = 0;
// Needed in case of one blinded output that is change and no blind inputs
int onlyChangePos = -1;
// Only used to strip blinding if its the only blind output in certain situations
int onlyRecipientBlindIndex = -1;
txNew.vin.clear();
txNew.vout.clear();
@ -2571,6 +2577,7 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
output_pubkeys.push_back(recipient.confidentiality_key);
if (recipient.confidentiality_key != CPubKey()) {
numToBlind++;
onlyRecipientBlindIndex = txNew.vout.size()-1;
}
}
@ -2682,7 +2689,9 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
output_pubkeys.insert(output_pubkeys.begin() + nChangePosInOut, pubkey);
if (pubkey != CPubKey()) {
numToBlind++;
changeToBlind++;
}
onlyChangePos = nChangePosInOut;
// reset nChangePosInOut for next asset
nChangePosInOut = -1;
}
@ -2816,6 +2825,9 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
input_assets.push_back(asset);
CAmount amount = coin.first->GetOutputValueOut(coin.second);
input_amounts.push_back(amount);
if (coin.first->vout[coin.second].nValue.IsCommitment() || coin.first->vout[coin.second].nAsset.IsCommitment()) {
numInputsBlinded++;
}
}
if(outAmounts)
outAmounts->clear();
@ -2828,30 +2840,63 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
output_assets.push_back(txNew.vout[nOut].nAsset.GetAsset());
}
// Keep a backup of transaction in case re-blinding necessary
CMutableTransaction txUnblindedAndUnsigned(txNew);
CMutableTransaction txBackup(txNew);
int ret = BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, vassetKeys, vtokenKeys, txNew);
// TODO remove?
assert(ret != -1);
if (ret != numToBlind) {
// We need a dummy output to put a non-zero blinding factor.
// TODO: if fBlindedOutputs, don't use an OP_RETURN but create an (extra) change output
// instead, as this does not actually provide better privacy.
// There are a few edge-cases of blinding we need to take care of
//
// First, if there are blinded inputs but not outputs to blind
// We need this to go through, even though no privacy is gained.
if (numInputsBlinded > 0 && numToBlind == 0) {
// We need to make sure to dupe an asset that is in input set
// TODO Have blinding do some extremely minimal rangeproof
CTxOut newTxOut(output_assets.back(), 0, CScript() << OP_RETURN);
txBackup.vout.push_back(newTxOut);
txNew.vout.push_back(newTxOut);
output_pubkeys.push_back(GetBlindingPubKey(newTxOut.scriptPubKey));
output_blinds.push_back(uint256());
output_asset_blinds.push_back(uint256());
output_assets.push_back(output_assets.back());
vAmounts.push_back(0);
numToBlind++;
// Now it has to succeed
int ret = BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, vassetKeys, vtokenKeys, txBackup);
assert(ret == numToBlind);
txNew = txBackup;
// No blinded inputs, but 1 blinded output
} else if (numInputsBlinded == 0 && numToBlind == 1) {
if (changeToBlind == 1) {
// Only 1 blinded change, unblinded the change
// TODO Split up change instead if possible
if (fIgnoreBlindFail) {
numToBlind--;
changeToBlind--;
txNew.vout[onlyChangePos].nNonce.SetNull();
output_pubkeys[onlyChangePos] = CPubKey();
output_blinds[onlyChangePos] = uint256();
output_asset_blinds[onlyChangePos] = uint256();
} else {
strFailReason = _("Change output could not be blinded as there are no blinded inputs and no other blinded outputs.");
return false;
}
} else {
// 1 blinded destination
// TODO Attempt to get a blinded input, OR add unblinded coin to make blinded change
assert(onlyRecipientBlindIndex != -1);
if (fIgnoreBlindFail) {
numToBlind--;
txNew.vout[onlyRecipientBlindIndex].nNonce.SetNull();
output_pubkeys[onlyRecipientBlindIndex] = CPubKey();
output_blinds[onlyRecipientBlindIndex] = uint256();
output_asset_blinds[onlyRecipientBlindIndex] = uint256();
} else {
strFailReason = _("Transaction output could not be blinded as there are no blinded inputs and no other blinded outputs.");
return false;
}
}
}
// All other combinations should work.
// Keep a backup of transaction in case re-blinding necessary
CMutableTransaction txUnblindedAndUnsigned(txNew);
int ret = BlindTransaction(input_blinds, input_asset_blinds, input_assets, input_amounts, output_blinds, output_asset_blinds, output_pubkeys, vassetKeys, vtokenKeys, txNew);
assert(ret != -1);
if (ret != numToBlind) {
strFailReason = _("Unable to blind the transaction properly. This should not happen.");
return false;
}
// Sign
@ -2880,7 +2925,7 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
unsigned int nBytes = GetVirtualTransactionSize(txNew);
// Remove scriptSigs if we used dummy signatures for fee calculation
// Revert scriptSigs and blinding if we used dummy signatures for fee calculation
if (!sign) {
txNew = txUnblindedAndUnsigned;
}

View file

@ -816,7 +816,7 @@ public:
* @note passing nChangePosInOut as -1 will result in setting a random position
*/
bool CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletTx& wtxNew, std::vector<CReserveKey*>& vpChangeKey, CAmount& nFeeRet, int& nChangePosInOut,
std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true, std::vector<CAmount> *outAmounts = NULL, bool fBlindIssuances = true, const uint256* issuanceEntropy = NULL, const CAsset* reissuanceAsset = NULL, const CAsset* reissuanceToken = NULL);
std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true, std::vector<CAmount> *outAmounts = NULL, bool fBlindIssuances = true, const uint256* issuanceEntropy = NULL, const CAsset* reissuanceAsset = NULL, const CAsset* reissuanceToken = NULL, bool fIgnoreBlindFail = true);
bool CommitTransaction(CWalletTx& wtxNew, std::vector<CReserveKey*>& reservekey);
bool AddAccountingEntry(const CAccountingEntry&, CWalletDB & pwalletdb);