mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-16 13:01:19 +02:00
Make best-effort to blind desired outputs, fail or return depending on ignoreblindfail arguments.
This commit is contained in:
parent
2aa6e1ac11
commit
a3f68d0db7
6 changed files with 189 additions and 58 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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++) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue