mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-18 13:17:55 +02:00
Make best-effort to blind desired outputs, fail or return depending on ignoreblindfail arguments.
This commit is contained in:
parent
f63aa69a16
commit
8ef102a035
6 changed files with 188 additions and 56 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.
|
||||
|
|
|
|||
|
|
@ -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" },
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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++) {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue