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 f63aa69a16
commit 8ef102a035
6 changed files with 188 additions and 56 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

@ -41,6 +41,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "sendtoaddress", 1, "amount" },
{ "sendtoaddress", 4, "subtractfeefromamount" },
{ "settxfee", 0, "amount" },
{ "sendtoaddress", 6, "ignoreblindfail" },
{ "getreceivedbyaddress", 1, "minconf" },
{ "destroyamount", 1, "amount" },
{ "getreceivedbyaccount", 1, "minconf" },
@ -86,6 +87,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "getrawtransaction", 1, "verbose" },
{ "rawblindrawtransaction", 1, "inputblinder" },
{ "blindrawtransaction", 1, "assetcommitments" },
{ "blindrawtransaction", 2, "ignoreblindfail" },
{ "createrawtransaction", 0, "inputs" },
{ "createrawtransaction", 1, "outputs" },
{ "dumpissuanceblindingkey", 1, "vin" },

View file

@ -726,14 +726,14 @@ void FillBlinds(CMutableTransaction& tx, bool fUseWallet, std::vector<uint256>&
UniValue rawblindrawtransaction(const JSONRPCRequest& request)
{
if (request.fHelp || (request.params.size() < 5 || request.params.size() > 6))
if (request.fHelp || (request.params.size() < 5 || request.params.size() > 7))
throw std::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"
@ -751,6 +751,7 @@ UniValue rawblindrawtransaction(const JSONRPCRequest& request)
" \"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"
@ -758,8 +759,10 @@ UniValue rawblindrawtransaction(const JSONRPCRequest& request)
if (request.params.size() == 5) {
RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VSTR)(UniValue::VARR)(UniValue::VARR)(UniValue::VARR)(UniValue::VARR));
} else {
} else if (request.params.size() == 6) {
RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VSTR)(UniValue::VARR)(UniValue::VARR)(UniValue::VARR)(UniValue::VARR)(UniValue::VSTR));
} else {
RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VSTR)(UniValue::VARR)(UniValue::VARR)(UniValue::VARR)(UniValue::VARR)(UniValue::VSTR)(UniValue::VBOOL));
}
vector<unsigned char> txData(ParseHexV(request.params[0], "argument 1"));
@ -776,6 +779,13 @@ UniValue rawblindrawtransaction(const JSONRPCRequest& request)
UniValue inputAssets = request.params[3].get_array();
UniValue inputAssetBlinds = request.params[4].get_array();
bool fIgnoreBlindFail = true;
if (request.params.size() > 6) {
fIgnoreBlindFail = request.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"));
@ -813,6 +823,10 @@ UniValue rawblindrawtransaction(const JSONRPCRequest& request)
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;
@ -821,14 +835,40 @@ UniValue rawblindrawtransaction(const JSONRPCRequest& request)
// 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);
@ -837,21 +877,22 @@ UniValue rawblindrawtransaction(const JSONRPCRequest& request)
#ifdef ENABLE_WALLET
UniValue blindrawtransaction(const JSONRPCRequest& request)
{
if (request.fHelp || (request.params.size() != 1 && request.params.size() != 2))
if (request.fHelp || (request.params.size() < 1 || request.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"
@ -860,9 +901,11 @@ UniValue blindrawtransaction(const JSONRPCRequest& request)
if (request.params.size() == 1) {
RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VSTR));
} else if (request.params.size() == 2){
RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VSTR)(UniValue::VARR));
RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VSTR)(UniValue::VBOOL));
} else if (request.params.size() == 3){
RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VSTR)(UniValue::VARR)(UniValue::VSTR));
RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VSTR)(UniValue::VBOOL)(UniValue::VARR));
} else {
RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VSTR)(UniValue::VBOOL)(UniValue::VARR)(UniValue::VSTR));
}
vector<unsigned char> txData(ParseHexV(request.params[0], "argument 1"));
@ -874,10 +917,15 @@ UniValue blindrawtransaction(const JSONRPCRequest& request)
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
}
std::vector<std::vector<unsigned char> > auxiliary_generators;
bool fIgnoreBlindFail = true;
if (request.params.size() > 1) {
UniValue assetCommitments = request.params[1].get_array();
if (assetCommitments.size() < tx.vin.size()) {
fIgnoreBlindFail = 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 at least as many entries as transaction inputs.");
}
for (size_t nIn = 0; nIn < assetCommitments.size(); nIn++) {
@ -902,6 +950,7 @@ UniValue blindrawtransaction(const JSONRPCRequest& request)
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);
@ -933,6 +982,7 @@ UniValue blindrawtransaction(const JSONRPCRequest& request)
}
else {
input_amounts.push_back(it->second.GetOutputValueOut(tx.vin[nIn].prevout.n));
n_blinded_ins += 1;
}
}
@ -942,15 +992,39 @@ UniValue blindrawtransaction(const JSONRPCRequest& request)
// 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

@ -391,7 +391,7 @@ UniValue getaddressesbyaccount(const JSONRPCRequest& request)
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];
@ -422,7 +422,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", FormatMoney(nFeeRequired));
throw JSONRPCError(RPC_WALLET_ERROR, strError);
@ -434,9 +434,9 @@ static void SendMoney(const CScript& scriptPubKey, CAmount nValue, CAsset asset,
}
}
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)
@ -495,9 +495,9 @@ UniValue sendtoaddress(const JSONRPCRequest& request)
if (!EnsureWalletIsAvailable(request.fHelp))
return NullUniValue;
if (request.fHelp || request.params.size() < 2 || request.params.size() > 6)
if (request.fHelp || request.params.size() < 2 || request.params.size() > 7)
throw runtime_error(
"sendtoaddress \"address\" 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"
@ -509,8 +509,9 @@ UniValue sendtoaddress(const JSONRPCRequest& request)
" 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"
"\"txid\" (string) The transaction id.\n"
"\nExamples:\n"
@ -553,11 +554,15 @@ UniValue sendtoaddress(const JSONRPCRequest& request)
strasset = request.params[5].get_str();
}
bool fIgnoreBlindFail = true;
if (request.params.size() > 6)
fIgnoreBlindFail = request.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.tx->vout.size(); i++) {
@ -610,7 +615,7 @@ UniValue destroyamount(const JSONRPCRequest& request)
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.tx->vout.size(); i++) {
@ -1086,6 +1091,7 @@ UniValue sendmany(const JSONRPCRequest& request)
" \"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"
"\"txid\" (string) The transaction id for the send. Only 1 transaction is created regardless of \n"
" the number of addresses.\n"
@ -1127,6 +1133,11 @@ UniValue sendmany(const JSONRPCRequest& request)
assets = request.params[5].get_obj();
}
bool fIgnoreBlindFail = true;
if (request.params.size() > 6) {
fIgnoreBlindFail = request.params[6].get_bool();
}
set<CBitcoinAddress> setAddress;
vector<CRecipient> vecSend;
@ -1200,7 +1211,7 @@ UniValue sendmany(const JSONRPCRequest& request)
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);
CValidationState state;
@ -2862,6 +2873,7 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
"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"
@ -3564,7 +3576,7 @@ UniValue sendtomainchain(const JSONRPCRequest& request)
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.tx->vout.size(); i++) {

View file

@ -2690,7 +2690,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.
@ -2778,6 +2778,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();
@ -2827,6 +2833,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;
}
}
@ -2942,7 +2949,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;
}
@ -3082,6 +3091,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->tx->vout[coin.second].nValue.IsCommitment() || coin.first->tx->vout[coin.second].nAsset.IsCommitment()) {
numInputsBlinded++;
}
}
if(outAmounts)
outAmounts->clear();
@ -3094,30 +3106,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
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;
}
// Fill in dummy signatures for fee calculation.

View file

@ -875,7 +875,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, CConnman* connman, CValidationState& state);
void ListAccountCreditDebit(const std::string& strAccount, std::list<CAccountingEntry>& entries);