diff --git a/qa/rpc-tests/confidential_transactions.py b/qa/rpc-tests/confidential_transactions.py index aabaa8ef4d..05b285e23f 100755 --- a/qa/rpc-tests/confidential_transactions.py +++ b/qa/rpc-tests/confidential_transactions.py @@ -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. diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index cdbd5652a8..a67a32fdf6 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -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 }, diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index bcb6d5517a..aa7603e5ce 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -709,14 +709,14 @@ void FillBlinds(CMutableTransaction& tx, bool fUseWallet, std::vector& 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 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 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(), std::vector(), 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(), std::vector(), 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 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 > 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 > 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 output_asset_blinds; std::vector output_assets; std::vector output_pubkeys; + int n_blinded_ins = 0; for (size_t nIn = 0; nIn < tx.vin.size(); nIn++) { std::map::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(), std::vector(), 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(), std::vector(), 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); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index e3f3ba8508..7dbc1de7bf 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -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 6) + fIgnoreBlindFail = params[6].get_bool(); + set setAddress; vector 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& vecSend, CWalletTx& wtxNew, std::vector& vpChangeKey, CAmount& nFeeRet, - int& nChangePosInOut, std::string& strFailReason, const CCoinControl* coinControl, bool sign, std::vector *outAmounts, bool fBlindIssuances, const uint256* issuanceEntropy, const CAsset* reissuanceAsset, const CAsset* reissuanceToken) + int& nChangePosInOut, std::string& strFailReason, const CCoinControl* coinControl, bool sign, std::vector *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& vecSend, CWalletTx& wt nChangePosInOut = nChangePosRequest; std::vector 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& 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& 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& 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& 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& 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; } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 63a2a068fc..249f0efbfa 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -816,7 +816,7 @@ public: * @note passing nChangePosInOut as -1 will result in setting a random position */ bool CreateTransaction(const std::vector& vecSend, CWalletTx& wtxNew, std::vector& vpChangeKey, CAmount& nFeeRet, int& nChangePosInOut, - std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true, std::vector *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 *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& reservekey); bool AddAccountingEntry(const CAccountingEntry&, CWalletDB & pwalletdb);