Wallet updates for CT

This commit is contained in:
Pieter Wuille 2016-03-01 14:56:41 -08:00 committed by Gregory Sanders
parent 8cbec40bec
commit 8eb5eaab32
8 changed files with 640 additions and 81 deletions

View file

@ -83,6 +83,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "getblockheader", 1, "verbose" },
{ "gettransaction", 1, "include_watchonly" },
{ "getrawtransaction", 1, "verbose" },
{ "rawblindrawtransaction", 1, "inputblinder" },
{ "createrawtransaction", 0, "inputs" },
{ "createrawtransaction", 1, "outputs" },
{ "createrawtransaction", 2, "locktime" },

View file

@ -168,6 +168,8 @@ UniValue validateaddress(const JSONRPCRequest& request)
" \"iscompressed\" : true|false, (boolean) If the address is compressed\n"
" \"account\" : \"account\" (string) DEPRECATED. The account associated with the address, \"\" is the default account\n"
" \"timestamp\" : timestamp, (number, optional) The creation time of the key if available in seconds since epoch (Jan 1 1970 GMT)\n"
" \"unconfidential\" : \"address\" (string) The address without confidentiality key\n"
" \"confidential\" : \"address\" (string) Confidential version of the address, only if it is yours and unconfidential\n"
" \"hdkeypath\" : \"keypath\" (string, optional) The HD keypath if the key is HD and available\n"
" \"hdmasterkeyid\" : \"<hash160>\" (string, optional) The Hash160 of the HD master pubkey\n"
"}\n"
@ -196,10 +198,26 @@ UniValue validateaddress(const JSONRPCRequest& request)
CScript scriptPubKey = GetScriptForDestination(dest);
ret.push_back(Pair("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end())));
if (address.IsBlinded()) {
CPubKey key = address.GetBlindingKey();
ret.push_back(Pair("confidential_key", HexStr(key.begin(), key.end())));
ret.push_back(Pair("unconfidential", address.GetUnblinded().ToString()));
} else {
ret.push_back(Pair("confidential_key", ""));
ret.push_back(Pair("unconfidential", currentAddress));
}
#ifdef ENABLE_WALLET
isminetype mine = pwalletMain ? IsMine(*pwalletMain, dest) : ISMINE_NO;
if (mine != ISMINE_NO && address.IsBlinded() && address.GetBlindingKey() != pwalletMain->GetBlindingPubKey(GetScriptForDestination(dest))) {
// Note: this will fail to return ismine for deprecated static blinded addresses.
mine = ISMINE_NO;
}
ret.push_back(Pair("ismine", (mine & ISMINE_SPENDABLE) ? true : false));
ret.push_back(Pair("iswatchonly", (mine & ISMINE_WATCH_ONLY) ? true: false));
if (!address.IsBlinded() && mine != ISMINE_NO) {
ret.push_back(Pair("confidential", address.AddBlindingKey(pwalletMain->GetBlindingPubKey(GetScriptForDestination(dest))).ToString()));
}
UniValue detail = boost::apply_visitor(DescribeAddressVisitor(), dest);
ret.pushKVs(detail);
if (pwalletMain && pwalletMain->mapAddressBook.count(dest))

View file

@ -4,6 +4,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "base58.h"
#include "blind.h"
#include "chain.h"
#include "coins.h"
#include "consensus/validation.h"
@ -30,11 +31,36 @@
#include <stdint.h>
#include <boost/assign/list_of.hpp>
#include <secp256k1_rangeproof.h>
#include <univalue.h>
using namespace std;
static secp256k1_context* secp256k1_blind_context = NULL;
class RPCRawTransaction_ECC_Init {
public:
RPCRawTransaction_ECC_Init() {
assert(secp256k1_blind_context == NULL);
secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
assert(ctx != NULL);
secp256k1_blind_context = ctx;
}
~RPCRawTransaction_ECC_Init() {
secp256k1_context *ctx = secp256k1_blind_context;
secp256k1_blind_context = NULL;
if (ctx) {
secp256k1_context_destroy(ctx);
}
}
};
static RPCRawTransaction_ECC_Init ecc_init_on_load;
void ScriptPubKeyToJSON(const CScript& scriptPubKey, UniValue& out, bool fIncludeHex)
{
txnouttype type;
@ -101,7 +127,28 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry)
UniValue out(UniValue::VOBJ);
if (txout.nValue.IsAmount())
out.push_back(Pair("value", ValueFromAmount(txout.nValue.GetAmount())));
else {} // TODO: Non-Amount values
else {
int exp;
int mantissa;
uint64_t minv;
uint64_t maxv;
if (secp256k1_rangeproof_info(secp256k1_blind_context, &exp, &mantissa, &minv, &maxv, &txout.nValue.vchRangeproof[0], txout.nValue.vchRangeproof.size())) {
if (exp == -1) {
out.push_back(Pair("value", ValueFromAmount((CAmount)minv)));
} else {
out.push_back(Pair("value-minimum", ValueFromAmount((CAmount)minv)));
out.push_back(Pair("value-maximum", ValueFromAmount((CAmount)maxv)));
}
out.push_back(Pair("ct-exponent", exp));
out.push_back(Pair("ct-bits", mantissa));
}
}
{
CDataStream ssValue(SER_NETWORK, PROTOCOL_VERSION);
ssValue << txout.nValue;
out.push_back(Pair("serValue", HexStr(ssValue.begin(), ssValue.end())));
}
out.push_back(Pair("n", (int64_t)i));
UniValue o(UniValue::VOBJ);
ScriptPubKeyToJSON(txout.scriptPubKey, o, true);
@ -370,6 +417,7 @@ UniValue createrawtransaction(const JSONRPCRequest& request)
" {\n"
" \"txid\":\"id\", (string, required) The transaction id\n"
" \"vout\":n, (numeric, required) The output number\n"
" \"nValue\":x.xxx, (numeric, required) The amount being spent\n"
" \"sequence\":n (numeric, optional) The sequence number\n"
" } \n"
" ,...\n"
@ -468,6 +516,12 @@ UniValue createrawtransaction(const JSONRPCRequest& request)
CTxOut out(nAmount, scriptPubKey);
if (address.IsBlinded()) {
CPubKey confidentiality_pubkey = address.GetBlindingKey();
if (!confidentiality_pubkey.IsValid())
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter: invalid confidentiality public key given"));
out.nValue.vchNonceCommitment = std::vector<unsigned char>(confidentiality_pubkey.begin(), confidentiality_pubkey.end());
}
rawTx.vout.push_back(out);
}
}
@ -477,6 +531,153 @@ UniValue createrawtransaction(const JSONRPCRequest& request)
return EncodeHexTx(rawTx);
}
void FillOutputBlinds(const CMutableTransaction& tx, bool fUseWallet, std::vector<uint256>& output_blinds, std::vector<CPubKey>& output_pubkeys) {
for (size_t nOut = 0; nOut < tx.vout.size(); nOut++) {
if (!tx.vout[nOut].nValue.IsAmount()) {
uint256 blinding_factor;
CAmount amount;
#ifdef ENABLE_WALLET
if (fUseWallet && UnblindOutput(pwalletMain->blinding_key, tx.vout[nOut], amount, blinding_factor) != 0) {
output_blinds.push_back(blinding_factor);
output_pubkeys.push_back(CPubKey());
} else if (fUseWallet)
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter: transaction outputs must be unblinded or to wallet"));
#endif
if (!fUseWallet)
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter: transaction outputs must be unblinded"));
} else if (tx.vout[nOut].nValue.vchNonceCommitment.size() == 0) {
output_pubkeys.push_back(CPubKey());
output_blinds.push_back(uint256());
} else {
CPubKey pubkey(tx.vout[nOut].nValue.vchNonceCommitment);
if (!pubkey.IsValid()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter: invalid confidentiality public key given"));
}
output_pubkeys.push_back(pubkey);
output_blinds.push_back(uint256());
}
}
}
UniValue rawblindrawtransaction(const JSONRPCRequest& request)
{
if (request.fHelp || (request.params.size() != 2 && request.params.size() != 3))
throw std::runtime_error(
"rawblindrawtransaction \"hexstring\" [\"inputblinder\",...] [\"totalblinder\"]\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"
"\nArguments:\n"
"1. \"hexstring\", (string, required) A hex-encoded raw transaction.\n"
"2. [ (array, required) An array with one entry per transaction input.\n"
" \"inputblinder\" (string, required) A hex-encoded blinding factor, one for each input.\n"
" Blinding factors can be found in the \"blinder\" output of listunspent.\n"
" ],\n"
"3. \"totalblinder\" (string, optional) Ignored for now.\n"
"\nResult:\n"
"\"transaction\" (string) hex string of the transaction\n"
);
if (request.params.size() == 2) {
RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VSTR)(UniValue::VARR));
} else {
RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VSTR)(UniValue::VARR)(UniValue::VSTR));
}
vector<unsigned char> txData(ParseHexV(request.params[0], "argument 1"));
CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION);
CMutableTransaction tx;
try {
ssData >> tx;
} catch (const std::exception &) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
}
UniValue inputBlinds = request.params[1].get_array();
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"));
std::vector<uint256> input_blinds;
std::vector<uint256> output_blinds;
std::vector<CPubKey> output_pubkeys;
for (size_t nIn = 0; nIn < tx.vin.size(); nIn++) {
if (!inputBlinds[nIn].isStr())
throw JSONRPCError(RPC_INVALID_PARAMETER, "input blinds must be an array of hex strings");
std::string blind(inputBlinds[nIn].get_str());
if (!IsHex(blind) || blind.length() != 32*2)
throw JSONRPCError(RPC_INVALID_PARAMETER, "input blinds must be an array of 32-byte hex-encoded strings");
input_blinds.push_back(uint256S(blind));
}
FillOutputBlinds(tx, false, output_blinds, output_pubkeys);
BlindOutputs(input_blinds, output_blinds, output_pubkeys, tx);
return EncodeHexTx(tx);
}
#ifdef ENABLE_WALLET
UniValue blindrawtransaction(const JSONRPCRequest& request)
{
if (request.fHelp || (request.params.size() != 1 && request.params.size() != 2))
throw runtime_error(
"blindrawtransaction \"hexstring\" [\"totalblinder\"]\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"
"\nArguments:\n"
"1. \"hexstring\", (string, required) A hex-encoded raw transaction.\n"
"2. \"totalblinder\" (string, optional) Ignored for now.\n"
"\nResult:\n"
"\"transaction\" (string) hex string of the transaction\n"
);
if (request.params.size() == 1) {
RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VSTR));
} else {
RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VSTR)(UniValue::VSTR));
}
vector<unsigned char> txData(ParseHexV(request.params[0], "argument 1"));
CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION);
CMutableTransaction tx;
try {
ssData >> tx;
} catch (const std::exception &) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
}
LOCK(pwalletMain->cs_wallet);
std::vector<uint256> input_blinds;
std::vector<uint256> output_blinds;
std::vector<CPubKey> output_pubkeys;
for (size_t nIn = 0; nIn < tx.vin.size(); nIn++) {
std::map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.find(tx.vin[nIn].prevout.hash);
if (it == pwalletMain->mapWallet.end()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter: transaction spends from non-wallet output"));
}
if (tx.vin[nIn].prevout.n >= it->second.tx->vout.size()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter: transaction spends non-existing output"));
}
input_blinds.push_back(it->second.GetBlindingFactor(tx.vin[nIn].prevout.n));
}
FillOutputBlinds(tx, true, output_blinds, output_pubkeys);
BlindOutputs(input_blinds, output_blinds, output_pubkeys, tx);
return EncodeHexTx(tx);
}
#endif
UniValue decoderawtransaction(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() != 1)
@ -953,6 +1154,8 @@ static const CRPCCommand commands[] =
{ "rawtransactions", "decodescript", &decodescript, true, {"hexstring"} },
{ "rawtransactions", "sendrawtransaction", &sendrawtransaction, false, {"hexstring","allowhighfees"} },
{ "rawtransactions", "signrawtransaction", &signrawtransaction, false, {"hexstring","prevtxs","privkeys","sighashtype"} }, /* uses wallet if enabled */
{ "rawtransactions", "rawblindrawtransaction", &rawblindrawtransaction, false, {}},
{ "rawtransactions", "blindrawtransaction", &blindrawtransaction, true, {}},
{ "blockchain", "gettxoutproof", &gettxoutproof, true, {"txids", "blockhash"} },
{ "blockchain", "verifytxoutproof", &verifytxoutproof, true, {"proof"} },

View file

@ -143,7 +143,7 @@ UniValue getnewaddress(const JSONRPCRequest& request)
pwalletMain->SetAddressBook(keyID, strAccount, "receive");
return CBitcoinAddress(keyID).ToString();
return CBitcoinAddress(keyID).AddBlindingKey(pwalletMain->GetBlindingPubKey(GetScriptForDestination(CTxDestination(keyID)))).ToString();
}
@ -154,7 +154,7 @@ CBitcoinAddress GetAccountAddress(string strAccount, bool bForceNew=false)
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");
}
return CBitcoinAddress(pubKey.GetID());
return CBitcoinAddress(pubKey.GetID()).AddBlindingKey(pwalletMain->GetBlindingPubKey(GetScriptForDestination(pubKey.GetID())));
}
UniValue getaccountaddress(const JSONRPCRequest& request)
@ -220,7 +220,7 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request)
CKeyID keyID = vchPubKey.GetID();
return CBitcoinAddress(keyID).ToString();
return CBitcoinAddress(keyID).AddBlindingKey(pwalletMain->GetBlindingPubKey(GetScriptForDestination(CTxDestination(keyID)))).ToString();
}
@ -329,9 +329,10 @@ UniValue getaddressesbyaccount(const JSONRPCRequest& request)
// Find all addresses that have the given account
UniValue ret(UniValue::VARR);
BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, CAddressBookData)& item, pwalletMain->mapAddressBook)
BOOST_FOREACH(const PAIRTYPE(CTxDestination, CAddressBookData)& item, pwalletMain->mapAddressBook)
{
const CBitcoinAddress& address = item.first;
CBitcoinAddress address = item.first;
address.AddBlindingKey(pwalletMain->GetBlindingPubKey(GetScriptForDestination(item.first)));
const string& strName = item.second.name;
if (strName == strAccount)
ret.push_back(address.ToString());
@ -339,7 +340,7 @@ UniValue getaddressesbyaccount(const JSONRPCRequest& request)
return ret;
}
static void SendMoney(const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, CWalletTx& wtxNew)
static void SendMoney(const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, const CPubKey &confidentiality_key, CWalletTx& wtxNew)
{
CAmount curBalance = pwalletMain->GetBalance();
@ -362,7 +363,7 @@ static void SendMoney(const CTxDestination &address, CAmount nValue, bool fSubtr
std::string strError;
vector<CRecipient> vecSend;
int nChangePosRet = -1;
CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount};
CRecipient recipient = {scriptPubKey, nValue, confidentiality_key, fSubtractFeeFromAmount};
vecSend.push_back(recipient);
if (!pwalletMain->CreateTransaction(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strError)) {
if (!fSubtractFeeFromAmount && nValue + nFeeRequired > curBalance)
@ -416,6 +417,11 @@ UniValue sendtoaddress(const JSONRPCRequest& request)
if (nAmount <= 0)
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send");
CPubKey confidentiality_pubkey;
if (address.IsBlinded()) {
confidentiality_pubkey = address.GetBlindingKey();
}
// Wallet comments
CWalletTx wtx;
if (request.params.size() > 2 && !request.params[2].isNull() && !request.params[2].get_str().empty())
@ -429,7 +435,7 @@ UniValue sendtoaddress(const JSONRPCRequest& request)
EnsureWalletIsUnlocked();
SendMoney(address.Get(), nAmount, fSubtractFeeFromAmount, wtx);
SendMoney(address.Get(), nAmount, fSubtractFeeFromAmount, confidentiality_pubkey, wtx);
return wtx.GetHash().GetHex();
}
@ -472,11 +478,13 @@ UniValue listaddressgroupings(const JSONRPCRequest& request)
BOOST_FOREACH(CTxDestination address, grouping)
{
UniValue addressInfo(UniValue::VARR);
addressInfo.push_back(CBitcoinAddress(address).ToString());
CBitcoinAddress addr(address);
addr.AddBlindingKey(pwalletMain->GetBlindingPubKey(GetScriptForDestination(addr.Get())));
addressInfo.push_back(addr.ToString());
addressInfo.push_back(ValueFromAmount(balances[address]));
{
if (pwalletMain->mapAddressBook.find(CBitcoinAddress(address).Get()) != pwalletMain->mapAddressBook.end())
addressInfo.push_back(pwalletMain->mapAddressBook.find(CBitcoinAddress(address).Get())->second.name);
if (pwalletMain->mapAddressBook.find(addr.Get()) != pwalletMain->mapAddressBook.end())
addressInfo.push_back(pwalletMain->mapAddressBook.find(addr.Get())->second.name);
}
jsonGrouping.push_back(addressInfo);
}
@ -589,10 +597,11 @@ UniValue getreceivedbyaddress(const JSONRPCRequest& request)
if (wtx.IsCoinBase() || !CheckFinalTx(*wtx.tx))
continue;
BOOST_FOREACH(const CTxOut& txout, wtx.tx->vout)
if (txout.scriptPubKey == scriptPubKey)
for (unsigned int i = 0; i < wtx.tx->vout.size(); i++)
if (wtx.tx->vout[i].scriptPubKey == scriptPubKey)
if (wtx.GetDepthInMainChain() >= nMinDepth)
nAmount += txout.nValue.GetAmount();
nAmount += wtx.GetValueOut(i);
}
return ValueFromAmount(nAmount);
@ -643,12 +652,12 @@ UniValue getreceivedbyaccount(const JSONRPCRequest& request)
if (wtx.IsCoinBase() || !CheckFinalTx(*wtx.tx))
continue;
BOOST_FOREACH(const CTxOut& txout, wtx.tx->vout)
for (unsigned int i = 0; i < wtx.tx->vout.size(); i++)
{
CTxDestination address;
if (ExtractDestination(txout.scriptPubKey, address) && IsMine(*pwalletMain, address) && setAddress.count(address))
if (ExtractDestination(wtx.tx->vout[i].scriptPubKey, address) && IsMine(*pwalletMain, address) && setAddress.count(address))
if (wtx.GetDepthInMainChain() >= nMinDepth)
nAmount += txout.nValue.GetAmount();
nAmount += wtx.GetValueOut(i);
}
}
@ -855,6 +864,11 @@ UniValue sendfrom(const JSONRPCRequest& request)
if (request.params.size() > 3)
nMinDepth = request.params[3].get_int();
CPubKey confidentiality_pubkey;
if (address.IsBlinded())
confidentiality_pubkey = address.GetBlindingKey();
CWalletTx wtx;
wtx.strFromAccount = strAccount;
if (request.params.size() > 4 && !request.params[4].isNull() && !request.params[4].get_str().empty())
@ -869,7 +883,7 @@ UniValue sendfrom(const JSONRPCRequest& request)
if (nAmount > nBalance)
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Account has insufficient funds");
SendMoney(address.Get(), nAmount, false, wtx);
SendMoney(address.Get(), nAmount, false, confidentiality_pubkey, wtx);
return wtx.GetHash().GetHex();
}
@ -957,6 +971,11 @@ UniValue sendmany(const JSONRPCRequest& request)
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send");
totalAmount += nAmount;
CPubKey confidentiality_pubkey;
if (address.IsBlinded())
confidentiality_pubkey = address.GetBlindingKey();
bool fSubtractFeeFromAmount = false;
for (unsigned int idx = 0; idx < subtractFeeFromAmount.size(); idx++) {
const UniValue& addr = subtractFeeFromAmount[idx];
@ -964,7 +983,7 @@ UniValue sendmany(const JSONRPCRequest& request)
fSubtractFeeFromAmount = true;
}
CRecipient recipient = {scriptPubKey, nAmount, fSubtractFeeFromAmount};
CRecipient recipient = {scriptPubKey, nAmount, confidentiality_pubkey, fSubtractFeeFromAmount};
vecSend.push_back(recipient);
}
@ -1134,6 +1153,7 @@ UniValue addwitnessaddress(const JSONRPCRequest& request)
struct tallyitem
{
CBitcoinAddress address;
CAmount nAmount;
int nConf;
vector<uint256> txids;
@ -1164,7 +1184,7 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts)
filter = filter | ISMINE_WATCH_ONLY;
// Tally
map<CBitcoinAddress, tallyitem> mapTally;
map<CTxDestination, tallyitem> mapTally;
for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it)
{
const CWalletTx& wtx = (*it).second;
@ -1176,18 +1196,23 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts)
if (nDepth < nMinDepth)
continue;
BOOST_FOREACH(const CTxOut& txout, wtx.tx->vout)
for (unsigned int i = 0; i < wtx.tx->vout.size(); i++)
{
CTxDestination address;
if (!ExtractDestination(txout.scriptPubKey, address))
if (!ExtractDestination(wtx.tx->vout[i].scriptPubKey, address))
continue;
isminefilter mine = IsMine(*pwalletMain, address);
if(!(mine & filter))
continue;
CBitcoinAddress bitcoinaddress(address);
if (!wtx.tx->vout[i].nValue.IsAmount())
bitcoinaddress.AddBlindingKey(wtx.GetBlindingKey(i));
tallyitem& item = mapTally[address];
item.nAmount += txout.nValue.GetAmount();
item.address = bitcoinaddress;
item.nAmount += wtx.GetValueOut(i);
item.nConf = min(item.nConf, nDepth);
item.txids.push_back(wtx.GetHash());
if (mine & ISMINE_WATCH_ONLY)
@ -1198,19 +1223,21 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts)
// Reply
UniValue ret(UniValue::VARR);
map<string, tallyitem> mapAccountTally;
BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, CAddressBookData)& item, pwalletMain->mapAddressBook)
BOOST_FOREACH(const PAIRTYPE(CTxDestination, CAddressBookData)& item, pwalletMain->mapAddressBook)
{
const CBitcoinAddress& address = item.first;
const CTxDestination& address = item.first;
const string& strAccount = item.second.name;
map<CBitcoinAddress, tallyitem>::iterator it = mapTally.find(address);
map<CTxDestination, tallyitem>::iterator it = mapTally.find(address);
if (it == mapTally.end() && !fIncludeEmpty)
continue;
CBitcoinAddress fulladdress = address;
CAmount nAmount = 0;
int nConf = std::numeric_limits<int>::max();
bool fIsWatchonly = false;
if (it != mapTally.end())
{
fulladdress = (*it).second.address;
nAmount = (*it).second.nAmount;
nConf = (*it).second.nConf;
fIsWatchonly = (*it).second.fIsWatchonly;
@ -1228,7 +1255,7 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts)
UniValue obj(UniValue::VOBJ);
if(fIsWatchonly)
obj.push_back(Pair("involvesWatchonly", true));
obj.push_back(Pair("address", address.ToString()));
obj.push_back(Pair("address", fulladdress.ToString()));
obj.push_back(Pair("account", strAccount));
obj.push_back(Pair("amount", ValueFromAmount(nAmount)));
obj.push_back(Pair("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf)));
@ -1345,11 +1372,14 @@ UniValue listreceivedbyaccount(const JSONRPCRequest& request)
return ListReceived(request.params, true);
}
static void MaybePushAddress(UniValue & entry, const CTxDestination &dest)
static void MaybePushAddress(UniValue & entry, const CTxDestination &dest, const CPubKey& confidentiality_pubkey)
{
CBitcoinAddress addr;
if (addr.Set(dest))
if (addr.Set(dest)) {
if (confidentiality_pubkey.size() == 33)
addr.AddBlindingKey(confidentiality_pubkey);
entry.push_back(Pair("address", addr.ToString()));
}
}
void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter)
@ -1373,7 +1403,7 @@ void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDe
if(involvesWatchonly || (::IsMine(*pwalletMain, s.destination) & ISMINE_WATCH_ONLY))
entry.push_back(Pair("involvesWatchonly", true));
entry.push_back(Pair("account", strSentAccount));
MaybePushAddress(entry, s.destination);
MaybePushAddress(entry, s.destination, s.confidentiality_pubkey);
entry.push_back(Pair("category", "send"));
entry.push_back(Pair("amount", ValueFromAmount(-s.amount)));
if (pwalletMain->mapAddressBook.count(s.destination))
@ -1401,7 +1431,7 @@ void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDe
if(involvesWatchonly || (::IsMine(*pwalletMain, r.destination) & ISMINE_WATCH_ONLY))
entry.push_back(Pair("involvesWatchonly", true));
entry.push_back(Pair("account", account));
MaybePushAddress(entry, r.destination);
MaybePushAddress(entry, r.destination, r.confidentiality_pubkey);
if (wtx.IsCoinBase())
{
if (wtx.GetDepthInMainChain() < 1)
@ -2422,6 +2452,8 @@ UniValue listunspent(const JSONRPCRequest& request)
" \"scriptPubKey\" : \"key\", (string) the script key\n"
" \"amount\" : x.xxx, (numeric) the transaction output amount in " + CURRENCY_UNIT + "\n"
" \"confirmations\" : n, (numeric) The number of confirmations\n"
" \"serValue\" : \"hex\", (string) the output's value commitment\n"
" \"blinder\" : \"blind\" (string) The blinding factor used for a confidential output (or \"\")\n"
" \"redeemScript\" : n (string) The redeemScript if scriptPubKey is P2SH\n"
" \"spendable\" : xxx, (bool) Whether we have the private keys to spend this output\n"
" \"solvable\" : xxx (bool) Whether we know how to spend this output, ignoring the lack of keys\n"
@ -2484,13 +2516,16 @@ UniValue listunspent(const JSONRPCRequest& request)
if (setAddress.size() && (!fValidAddress || !setAddress.count(address)))
continue;
CAmount nValue = out.tx->GetValueOut(out.i);
UniValue entry(UniValue::VOBJ);
entry.push_back(Pair("txid", out.tx->GetHash().GetHex()));
entry.push_back(Pair("vout", out.i));
if (fValidAddress) {
entry.push_back(Pair("address", CBitcoinAddress(address).ToString()));
CBitcoinAddress addr(address);
if (out.tx->GetBlindingFactor(out.i).size() > 0)
addr.AddBlindingKey(out.tx->GetBlindingKey(out.i));
entry.push_back(Pair("address", addr.ToString()));
if (pwalletMain->mapAddressBook.count(address))
entry.push_back(Pair("account", pwalletMain->mapAddressBook[address].name));
@ -2503,10 +2538,14 @@ UniValue listunspent(const JSONRPCRequest& request)
}
entry.push_back(Pair("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end())));
entry.push_back(Pair("amount", ValueFromAmount(out.tx->tx->vout[out.i].nValue.GetAmount())));
entry.push_back(Pair("amount", ValueFromAmount(nValue)));
entry.push_back(Pair("confirmations", out.nDepth));
entry.push_back(Pair("spendable", out.fSpendable));
entry.push_back(Pair("solvable", out.fSolvable));
CDataStream ssValue(SER_NETWORK, PROTOCOL_VERSION);
ssValue << nValue;
entry.push_back(Pair("serValue", HexStr(ssValue.begin(), ssValue.end())));
entry.push_back(Pair("blinder",out.tx->GetBlindingFactor(out.i).ToString()));
results.push_back(entry);
}

View file

@ -11,6 +11,7 @@
#include "wallet/coincontrol.h"
#include "consensus/consensus.h"
#include "consensus/validation.h"
#include "crypto/hmac_sha256.h"
#include "key.h"
#include "keystore.h"
#include "validation.h"
@ -32,6 +33,8 @@
#include <boost/filesystem.hpp>
#include <boost/thread.hpp>
#include <secp256k1.h>
using namespace std;
CWallet* pwalletMain = NULL;
@ -75,7 +78,7 @@ struct CompareValueOnly
std::string COutput::ToString() const
{
return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->tx->vout[i].nValue.GetAmount()));
return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->GetValueOut(i)));
}
const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const
@ -1226,7 +1229,7 @@ CAmount CWallet::GetDebit(const CTxIn &txin, const isminefilter& filter) const
const CWalletTx& prev = (*mi).second;
if (txin.prevout.n < prev.tx->vout.size())
if (IsMine(prev.tx->vout[txin.prevout.n]) & filter)
return prev.tx->vout[txin.prevout.n].nValue.GetAmount();
return prev.GetValueOut(txin.prevout.n);
}
}
return 0;
@ -1237,13 +1240,6 @@ isminetype CWallet::IsMine(const CTxOut& txout) const
return ::IsMine(*this, txout.scriptPubKey);
}
CAmount CWallet::GetCredit(const CTxOut& txout, const isminefilter& filter) const
{
if (!MoneyRange(txout.nValue.GetAmount()))
throw std::runtime_error(std::string(__func__) + ": value out of range");
return ((IsMine(txout) & filter) ? txout.nValue.GetAmount() : 0);
}
bool CWallet::IsChange(const CTxOut& txout) const
{
// TODO: fix handling of 'change' outputs. The assumption is that any
@ -1266,13 +1262,6 @@ bool CWallet::IsChange(const CTxOut& txout) const
return false;
}
CAmount CWallet::GetChange(const CTxOut& txout) const
{
if (!MoneyRange(txout.nValue.GetAmount()))
throw std::runtime_error(std::string(__func__) + ": value out of range");
return (IsChange(txout) ? txout.nValue.GetAmount() : 0);
}
bool CWallet::IsMine(const CTransaction& tx) const
{
BOOST_FOREACH(const CTxOut& txout, tx.vout)
@ -1319,24 +1308,24 @@ bool CWallet::IsAllFromMe(const CTransaction& tx, const isminefilter& filter) co
return true;
}
CAmount CWallet::GetCredit(const CTransaction& tx, const isminefilter& filter) const
CAmount CWallet::GetCredit(const CWalletTx& tx, const isminefilter& filter) const
{
CAmount nCredit = 0;
BOOST_FOREACH(const CTxOut& txout, tx.vout)
for (unsigned int i = 0; i < tx.tx->vout.size(); i++)
{
nCredit += GetCredit(txout, filter);
nCredit += tx.GetCredit(i, filter);
if (!MoneyRange(nCredit))
throw std::runtime_error(std::string(__func__) + ": value out of range");
}
return nCredit;
}
CAmount CWallet::GetChange(const CTransaction& tx) const
CAmount CWallet::GetChange(const CWalletTx& tx) const
{
CAmount nChange = 0;
BOOST_FOREACH(const CTxOut& txout, tx.vout)
for (unsigned int i = 0; i < tx.tx->vout.size(); i++)
{
nChange += GetChange(txout);
nChange += tx.GetChange(i);
if (!MoneyRange(nChange))
throw std::runtime_error(std::string(__func__) + ": value out of range");
}
@ -1492,7 +1481,11 @@ void CWalletTx::GetAmounts(list<COutputEntry>& listReceived,
address = CNoDestination();
}
COutputEntry output = {address, txout.nValue.GetAmount(), (int)i};
COutputEntry output = {address, GetValueOut(i), (int)i, CPubKey()};
if (!txout.nValue.IsAmount() && GetValueOut(i) > 0) {
output.confidentiality_pubkey = GetBlindingKey(i);
}
// If we are debited by the transaction, add the output as a "sent" entry
if (nDebit > 0)
@ -1692,6 +1685,19 @@ CAmount CWalletTx::GetDebit(const isminefilter& filter) const
return debit;
}
CAmount CWalletTx::GetCredit(unsigned int nTxOut, const isminefilter& filter) const
{
CAmount amount = 0;
if (pwallet->IsMine(tx->vout[nTxOut]) & filter)
amount = GetValueOut(nTxOut);
// Can be -1 if someone sent us a transaction using a wrong scanning key:
if (amount == -1)
return 0;
if (!MoneyRange(amount))
throw std::runtime_error("CWallet::GetCredit(): value out of range");
return amount;
}
CAmount CWalletTx::GetCredit(const isminefilter& filter) const
{
// Must wait until coinbase is safely deep enough in the chain before valuing it
@ -1757,8 +1763,7 @@ CAmount CWalletTx::GetAvailableCredit(bool fUseCache) const
{
if (!pwallet->IsSpent(hashTx, i))
{
const CTxOut &txout = tx->vout[i];
nCredit += pwallet->GetCredit(txout, ISMINE_SPENDABLE);
nCredit += GetCredit(i, ISMINE_SPENDABLE);
if (!MoneyRange(nCredit))
throw std::runtime_error("CWalletTx::GetAvailableCredit() : value out of range");
}
@ -1800,8 +1805,7 @@ CAmount CWalletTx::GetAvailableWatchOnlyCredit(const bool& fUseCache) const
{
if (!pwallet->IsSpent(GetHash(), i))
{
const CTxOut &txout = tx->vout[i];
nCredit += pwallet->GetCredit(txout, ISMINE_WATCH_ONLY);
nCredit += GetCredit(i, ISMINE_WATCH_ONLY);
if (!MoneyRange(nCredit))
throw std::runtime_error("CWalletTx::GetAvailableCredit() : value out of range");
}
@ -1812,6 +1816,16 @@ CAmount CWalletTx::GetAvailableWatchOnlyCredit(const bool& fUseCache) const
return nCredit;
}
CAmount CWalletTx::GetChange(unsigned int nTxOut) const
{
CAmount amount = 0;
if (pwallet->IsChange(tx->vout[nTxOut]))
amount = GetValueOut(nTxOut);
if (!MoneyRange(amount))
throw std::runtime_error("CWallet::GetChange(): value out of range");
return amount;
}
CAmount CWalletTx::GetChange() const
{
if (fChangeCached)
@ -1870,6 +1884,68 @@ bool CWalletTx::IsEquivalentTo(const CWalletTx& _tx) const
return CTransaction(tx1) == CTransaction(tx2);
}
void CWalletTx::GetBlindingData(unsigned int nOut, CAmount* pamountOut, CPubKey* ppubkeyOut, uint256* pblindingfactorOut) const
{
// Blinding data is cached in a serialized record mapWallet["blindingdata"].
// It contains a concatenation byte vectors, 74 bytes per txout.
// Each consists of:
// * 1 byte boolean marker (has the output been computed)?
// * 8 bytes amount (-1 if unknown)
// * 32 bytes blinding factor
// * 33 bytes blinding pubkey (ECDH pubkey of the destination)
// This is really ugly, and should use CDataStream serialization instead.
assert(nOut < tx->vout.size());
if (mapValue["blindingdata"].size() < (nOut + 1) * 74) {
mapValue["blindingdata"].resize(tx->vout.size() * 74);
}
unsigned char* it = (unsigned char*)(&mapValue["blindingdata"][0]) + 74 * nOut;
CAmount amount = -1;
CPubKey pubkey;
uint256 blindingfactor;
if (*it == 1) {
memcpy(&amount, &*(it + 1), 8);
memcpy(blindingfactor.begin(), &*(it + 9), 32);
pubkey.Set(it + 41, it + 74);
} else {
pwallet->ComputeBlindingData(tx->vout[nOut], amount, pubkey, blindingfactor);
*it = 1;
memcpy(&*(it + 1), &amount, 8);
memcpy(&*(it + 9), blindingfactor.begin(), 32);
if (pubkey.IsValid() && pubkey.size() == 33) {
memcpy(&*(it + 41), pubkey.begin(), 33);
} else {
memset(&*(it + 41), 0, 33);
}
}
if (pamountOut) *pamountOut = amount;
if (ppubkeyOut) *ppubkeyOut = pubkey;
if (pblindingfactorOut) *pblindingfactorOut = blindingfactor;
}
CAmount CWalletTx::GetValueOut(unsigned int nOut) const {
CAmount ret;
GetBlindingData(nOut, &ret, NULL, NULL);
return ret;
}
uint256 CWalletTx::GetBlindingFactor(unsigned int nOut) const {
uint256 ret;
GetBlindingData(nOut, NULL, NULL, &ret);
return ret;
}
CPubKey CWalletTx::GetBlindingKey(unsigned int nOut) const {
CPubKey ret;
GetBlindingData(nOut, NULL, &ret, NULL);
return ret;
}
std::vector<uint256> CWallet::ResendWalletTransactionsBefore(int64_t nTime, CConnman* connman)
{
std::vector<uint256> result;
@ -2081,7 +2157,7 @@ void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const
for (unsigned int i = 0; i < pcoin->tx->vout.size(); i++) {
isminetype mine = IsMine(pcoin->tx->vout[i]);
if (!(IsSpent(wtxid, i)) && mine != ISMINE_NO &&
!IsLockedCoin((*it).first, i) && (pcoin->tx->vout[i].nValue.GetAmount() > 0 || fIncludeZeroValue) &&
!IsLockedCoin((*it).first, i) && (pcoin->GetValueOut(i) > 0 || fIncludeZeroValue) &&
(!coinControl || !coinControl->HasSelected() || coinControl->fAllowOtherInputs || coinControl->IsSelected(COutPoint((*it).first, i))))
vCoins.push_back(COutput(pcoin, i, nDepth,
((mine & ISMINE_SPENDABLE) != ISMINE_NO) ||
@ -2167,7 +2243,7 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const int nConfMin
continue;
int i = output.i;
CAmount n = pcoin->tx->vout[i].nValue.GetAmount();
CAmount n = pcoin->GetValueOut(i);
pair<CAmount,pair<const CWalletTx*,unsigned int> > coin = make_pair(n,make_pair(pcoin, i));
@ -2253,8 +2329,8 @@ bool CWallet::SelectCoins(const vector<COutput>& vAvailableCoins, const CAmount&
BOOST_FOREACH(const COutput& out, vCoins)
{
if (!out.fSpendable)
continue;
nValueRet += out.tx->tx->vout[out.i].nValue.GetAmount();
continue;
nValueRet += out.tx->GetValueOut(out.i);
setCoinsRet.insert(make_pair(out.tx, out.i));
}
return (nValueRet >= nTargetValue);
@ -2276,7 +2352,7 @@ bool CWallet::SelectCoins(const vector<COutput>& vAvailableCoins, const CAmount&
// Clearly invalid input, fail
if (pcoin->tx->vout.size() <= outpoint.n)
return false;
nValueFromPresetInputs += pcoin->tx->vout[outpoint.n].nValue.GetAmount();
nValueFromPresetInputs += pcoin->GetValueOut(outpoint.n);
setPresetCoins.insert(make_pair(pcoin, outpoint.n));
} else
return false; // TODO: Allow non-wallet inputs
@ -2320,7 +2396,11 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, bool ov
for (size_t idx = 0; idx < tx.vout.size(); idx++)
{
const CTxOut& txOut = tx.vout[idx];
CRecipient recipient = {txOut.scriptPubKey, txOut.nValue.GetAmount(), setSubtractFeeFromOutputs.count(idx) == 1};
if (!txOut.nValue.IsAmount()) {
strFailReason = _("Pre-funded amounts must be non-blinded");
return false;
}
CRecipient recipient = {txOut.scriptPubKey, txOut.nValue.GetAmount(), CPubKey(), false};
vecSend.push_back(recipient);
}
@ -2440,6 +2520,9 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
while (true)
{
nChangePosInOut = nChangePosRequest;
std::vector<CPubKey> output_pubkeys;
bool fBlindedOuts = false;
txNew.vin.clear();
txNew.vout.clear();
wtxNew.fFromMe = true;
@ -2456,12 +2539,12 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
if (recipient.fSubtractFeeFromAmount)
{
txout.nValue = CTxOutValue(txout.nValue.GetAmount() - (nFeeRet / nSubtractFeeFromAmount)); // Subtract fee equally from each selected recipient
txout.nValue = recipient.nAmount - (nFeeRet / nSubtractFeeFromAmount); // Subtract fee equally from each selected recipient
if (fFirst) // first receiver pays the remainder not divisible by output count
{
fFirst = false;
txout.nValue = CTxOutValue(txout.nValue.GetAmount() - (nFeeRet % nSubtractFeeFromAmount));
txout.nValue = recipient.nAmount - (nFeeRet / nSubtractFeeFromAmount) - (nFeeRet % nSubtractFeeFromAmount);
}
}
@ -2479,6 +2562,9 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
return false;
}
txNew.vout.push_back(txout);
output_pubkeys.push_back(recipient.confidentiality_key);
if (recipient.confidentiality_key.size() != 0)
fBlindedOuts = true;
}
// Choose coins to use
@ -2489,9 +2575,12 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
strFailReason = _("Insufficient funds");
return false;
}
for (const auto& pcoin : setCoins)
bool fBlindedIns = false;
BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins)
{
CAmount nCredit = pcoin.first->tx->vout[pcoin.second].nValue.GetAmount();
if (!pcoin.first->tx->vout[pcoin.second].nValue.IsAmount())
fBlindedIns = true;
CAmount nCredit = pcoin.first->GetValueOut(pcoin.second);
//The coin age after the next block (depth+1) is used instead of the current,
//reflecting an assumption the user would accept a bit more delay for
//a chance at a free transaction.
@ -2545,13 +2634,13 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
// So instead we raise the change and deduct from the recipient.
if (nSubtractFeeFromAmount > 0 && newTxOut.IsDust(dustRelayFee))
{
CAmount nDust = newTxOut.GetDustThreshold(dustRelayFee) - newTxOut.nValue.GetAmount();
newTxOut.nValue = CTxOutValue(newTxOut.nValue.GetAmount() + nDust); // raise change until no more dust
CAmount nDust = newTxOut.GetDustThreshold(::minRelayTxFee) - newTxOut.nValue.GetAmount();
newTxOut.nValue = newTxOut.nValue.GetAmount() + nDust; // raise change until no more dust
for (unsigned int i = 0; i < vecSend.size(); i++) // subtract from first recipient
{
if (vecSend[i].fSubtractFeeFromAmount)
{
txNew.vout[i].nValue = CTxOutValue(txNew.vout[i].nValue.GetAmount() - nDust);
txNew.vout[i].nValue = txNew.vout[i].nValue.GetAmount() - nDust;
if (txNew.vout[i].IsDust(dustRelayFee))
{
strFailReason = _("The transaction amount is too small to send after the fee has been deducted");
@ -2585,11 +2674,21 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
vector<CTxOut>::iterator position = txNew.vout.begin()+nChangePosInOut;
txNew.vout.insert(position, newTxOut);
output_pubkeys.insert(output_pubkeys.begin() + nChangePosInOut, GetBlindingPubKey(scriptChange));
fBlindedOuts = true;
}
}
else
reservekey.ReturnKey();
if (fBlindedIns && !fBlindedOuts) {
CTxOut newTxOut(0, CScript() << OP_RETURN);
txNew.vout.push_back(newTxOut);
output_pubkeys.push_back(GetBlindingPubKey(newTxOut.scriptPubKey));
fBlindedOuts = true;
}
// Fill vin
//
// Note how the sequence number is set to non-maxint so that
@ -2605,6 +2704,23 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
std::numeric_limits<unsigned int>::max() - (fWalletRbf ? 2 : 1)));
txNew.nTxFee = nFeeRet;
LogPrintf("Created transaction (before blinding): %s", CTransaction(txNew).ToString());
// Create blinded outputs
std::vector<uint256> input_blinds;
std::vector<uint256> output_blinds;
BOOST_FOREACH(const PAIRTYPE(const CWalletTx*,unsigned int)& coin, setCoins) {
uint256 blind = coin.first->GetBlindingFactor(coin.second);
input_blinds.push_back(blind);
}
for (size_t nOut = 0; nOut < txNew.vout.size(); nOut++) {
output_blinds.push_back(uint256());
}
if (fBlindedIns && !fBlindedOuts) {
strFailReason = _("Confidential inputs without confidential outputs");
return false;
}
BlindOutputs(input_blinds, output_blinds, output_pubkeys, txNew);
// Fill in dummy signatures for fee calculation.
if (!DummySignTx(txNew, setCoins)) {
@ -3146,7 +3262,7 @@ std::map<CTxDestination, CAmount> CWallet::GetAddressBalances()
if(!ExtractDestination(pcoin->tx->vout[i].scriptPubKey, addr))
continue;
CAmount n = IsSpent(walletEntry.first, i) ? 0 : pcoin->tx->vout[i].nValue.GetAmount();
CAmount n = IsSpent(walletEntry.first, i) ? 0 : pcoin->GetValueOut(i);
if (!balances.count(addr))
balances[addr] = 0;
@ -3976,3 +4092,91 @@ bool CMerkleTx::AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState&
{
return ::AcceptToMemoryPool(mempool, state, tx, true, NULL, NULL, false, nAbsurdFee);
}
CKey CWallet::GetBlindingKey(const CScript* script) const
{
CKey key;
if (script != NULL) {
std::map<CScriptID, uint256>::const_iterator it = mapSpecificBlindingKeys.find(CScriptID(*script));
if (it != mapSpecificBlindingKeys.end()) {
key.Set(it->second.begin(), it->second.end(), true);
if (key.IsValid()) {
return key;
}
}
}
if (script != NULL && !blinding_derivation_key.IsNull()) {
unsigned char vch[32];
CHMAC_SHA256(blinding_derivation_key.begin(), blinding_derivation_key.size()).Write(&((*script)[0]), script->size()).Finalize(vch);
key.Set(&vch[0], &vch[32], true);
if (key.IsValid()) {
return key;
}
}
if (script == NULL && blinding_key.IsValid()) {
return blinding_key;
}
return CKey();
}
CPubKey CWallet::GetBlindingPubKey(const CScript& script) const
{
CKey key = GetBlindingKey(&script);
if (key.IsValid()) {
return key.GetPubKey();
}
return CPubKey();
}
bool CWallet::LoadSpecificBlindingKey(const CScriptID& scriptid, const uint256& key)
{
AssertLockHeld(cs_wallet); // mapSpecificBlindingKeys
mapSpecificBlindingKeys[scriptid] = key;
return true;
}
bool CWallet::AddSpecificBlindingKey(const CScriptID& scriptid, const uint256& key)
{
AssertLockHeld(cs_wallet); // mapSpecificBlindingKeys
if (!LoadSpecificBlindingKey(scriptid, key))
return false;
if (!fFileBacked)
return true;
return CWalletDB(strWalletFile).WriteSpecificBlindingKey(scriptid, key);
}
void CWallet::ComputeBlindingData(const CTxOut& output, CAmount& amount, CPubKey& pubkey, uint256& blindingfactor) const
{
if (output.nValue.IsAmount()) {
amount = output.nValue.GetAmount();
pubkey = CPubKey();
blindingfactor.SetNull();
return;
}
CKey blinding_key;
if ((blinding_key = GetBlindingKey(&output.scriptPubKey)).IsValid()) {
// For outputs using derived blinding.
if (UnblindOutput(blinding_key, output, amount, blindingfactor)) {
pubkey = blinding_key.GetPubKey();
return;
}
}
if ((blinding_key = GetBlindingKey(NULL)).IsValid()) {
// For outputs using deprecated static blinding.
if (UnblindOutput(blinding_key, output, amount, blindingfactor)) {
pubkey = blinding_key.GetPubKey();
return;
}
}
amount = -1;
pubkey = CPubKey();
blindingfactor.SetNull();
}

View file

@ -7,6 +7,7 @@
#define BITCOIN_WALLET_WALLET_H
#include "amount.h"
#include "blind.h"
#include "streams.h"
#include "tinyformat.h"
#include "ui_interface.h"
@ -137,6 +138,7 @@ struct CRecipient
{
CScript scriptPubKey;
CAmount nAmount;
CPubKey confidentiality_key;
bool fSubtractFeeFromAmount;
};
@ -166,6 +168,7 @@ struct COutputEntry
CTxDestination destination;
CAmount amount;
int vout;
CPubKey confidentiality_pubkey;
};
/** A transaction with a merkle branch linking it to the block chain. */
@ -256,7 +259,7 @@ private:
const CWallet* pwallet;
public:
mapValue_t mapValue;
mutable mapValue_t mapValue;
std::vector<std::pair<std::string, std::string> > vOrderForm;
unsigned int fTimeReceivedIsTxTime;
unsigned int nTimeReceived; //!< time received by this node
@ -270,6 +273,11 @@ public:
std::string strFromAccount;
int64_t nOrderPos; //!< position in ordered transaction list
// For each output
mutable std::vector<uint256> vBlindingFactors;
mutable std::vector<CAmount> vAmountsOut;
mutable std::vector<CPubKey> vBlindingKeys;
// memory only
mutable bool fDebitCached;
mutable bool fCreditCached;
@ -397,11 +405,13 @@ public:
//! filter decides which addresses will count towards the debit
CAmount GetDebit(const isminefilter& filter) const;
CAmount GetCredit(unsigned int nTxOut, const isminefilter& filter) const;
CAmount GetCredit(const isminefilter& filter) const;
CAmount GetImmatureCredit(bool fUseCache=true) const;
CAmount GetAvailableCredit(bool fUseCache=true) const;
CAmount GetImmatureWatchOnlyCredit(const bool& fUseCache=true) const;
CAmount GetAvailableWatchOnlyCredit(const bool& fUseCache=true) const;
CAmount GetChange(unsigned int nTxOut) const;
CAmount GetChange() const;
void GetAmounts(std::list<COutputEntry>& listReceived,
@ -427,6 +437,17 @@ public:
bool RelayWalletTransaction(CConnman* connman);
std::set<uint256> GetConflicts() const;
private:
void GetBlindingData(unsigned int nOut, CAmount* pamountOut, CPubKey* ppubkeyOut, uint256* pblindingfactorOut) const;
public:
//! Returns either the value out (if it is to us) or 0
CAmount GetValueOut(unsigned int nOut) const;
//! Returns either the blinding factor (if it is to us) or 0
uint256 GetBlindingFactor(unsigned int nOut) const;
CPubKey GetBlindingKey(unsigned int nOut) const;
};
@ -656,6 +677,7 @@ public:
typedef std::map<unsigned int, CMasterKey> MasterKeyMap;
MasterKeyMap mapMasterKeys;
unsigned int nMasterKeyMaxID;
std::map<CScriptID, uint256> mapSpecificBlindingKeys;
CWallet()
{
@ -686,6 +708,8 @@ public:
nLastResend = 0;
nTimeFirstKey = 0;
fBroadcastTransactions = false;
blinding_key = CKey();
blinding_derivation_key = uint256();
}
std::map<uint256, CWalletTx> mapWallet;
@ -704,6 +728,13 @@ public:
std::set<COutPoint> setLockedCoins;
//! The actual blinding key is computed as HMAC-SHA256(key=blinding_derivation_key, msg=scriptPubKey).
//! There can be exceptions in mapSpecificBlindingKeys.
uint256 blinding_derivation_key;
//! Only for backward compatibility with older wallets (superseded by blinding_derivation_key).
CKey blinding_key;
const CWalletTx* GetWalletTx(const uint256& hash) const;
//! check whether we are allowed to upgrade (or already support) to the named feature
@ -742,6 +773,10 @@ public:
bool LoadKey(const CKey& key, const CPubKey &pubkey) { return CCryptoKeyStore::AddKeyPubKey(key, pubkey); }
//! Load metadata (used by LoadWallet)
bool LoadKeyMetadata(const CTxDestination& pubKey, const CKeyMetadata &metadata);
//! Adds a script-specific blinding key to the wallet, and saves it to disk.
bool AddSpecificBlindingKey(const CScriptID& scriptid, const uint256& key);
//! Adds a script-specific blinding key to the wallet without saving it to disk (used by LoadWallet)
bool LoadSpecificBlindingKey(const CScriptID& scriptid, const uint256& key);
bool LoadMinVersion(int nVersion) { AssertLockHeld(cs_wallet); nWalletVersion = nVersion; nWalletMaxVersion = std::max(nWalletMaxVersion, nVersion); return true; }
void UpdateTimeFirstKey(int64_t nCreateTime);
@ -861,17 +896,15 @@ public:
*/
CAmount GetDebit(const CTxIn& txin, const isminefilter& filter) const;
isminetype IsMine(const CTxOut& txout) const;
CAmount GetCredit(const CTxOut& txout, const isminefilter& filter) const;
bool IsChange(const CTxOut& txout) const;
CAmount GetChange(const CTxOut& txout) const;
bool IsMine(const CTransaction& tx) const;
/** should probably be renamed to IsRelevantToMe */
bool IsFromMe(const CTransaction& tx) const;
CAmount GetDebit(const CTransaction& tx, const isminefilter& filter) const;
CAmount GetCredit(const CWalletTx& tx, const isminefilter& filter) const;
CAmount GetChange(const CWalletTx& tx) const;
/** Returns whether all of the inputs match the filter */
bool IsAllFromMe(const CTransaction& tx, const isminefilter& filter) const;
CAmount GetCredit(const CTransaction& tx, const isminefilter& filter) const;
CAmount GetChange(const CTransaction& tx) const;
void SetBestChain(const CBlockLocator& loc) override;
DBErrors LoadWallet(bool& fFirstRunRet);
@ -960,6 +993,12 @@ public:
/* Mark a transaction (and it in-wallet descendants) as abandoned so its inputs may be respent. */
bool AbandonTransaction(const uint256& hashTx);
//! script == NULL gives the backward compatible blinding key
CKey GetBlindingKey(const CScript* script) const;
CPubKey GetBlindingPubKey(const CScript& script) const;
void ComputeBlindingData(const CTxOut& output, CAmount& amount, CPubKey& pubkey, uint256& blindingfactor) const;
/** Mark a transaction as replaced by another transaction (e.g., BIP 125). */
bool MarkReplaced(const uint256& originalHash, const uint256& newHash);

View file

@ -204,6 +204,16 @@ bool CWalletDB::WriteAccountingEntry_Backend(const CAccountingEntry& acentry)
return WriteAccountingEntry(++nAccountingEntryNumber, acentry);
}
bool CWalletDB::WriteSpecificBlindingKey(const CScriptID& scriptid, const uint256& key)
{
return Write(make_pair(std::string("specificblindingkey"), scriptid), key);
}
bool CWalletDB::WriteBlindingDerivationKey(const uint256& key)
{
return Write(std::string("blindingderivationkey"), key);
}
CAmount CWalletDB::GetAccountCreditDebit(const string& strAccount)
{
list<CAccountingEntry> entries;
@ -539,6 +549,36 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
return false;
}
}
/* Only for backward compatibility with older wallets. */
else if (strType == "blindingkey")
{
assert(!pwallet->blinding_key.IsValid());
std::vector<unsigned char> vchBlindingKey;
ssValue >> vchBlindingKey;
pwallet->blinding_key.Set(vchBlindingKey.begin(), vchBlindingKey.end(), true);
if (!pwallet->blinding_key.IsValid()) {
strErr = "Error reading wallet blinding key";
return false;
}
}
else if (strType == "blindingderivationkey")
{
assert(pwallet->blinding_derivation_key.IsNull());
uint256 key;
ssValue >> key;
pwallet->blinding_derivation_key = key;
}
else if (strType == "specificblindingkey")
{
CScriptID scriptid;
ssKey >> scriptid;
uint256 key;
ssValue >> key;
if (!pwallet->LoadSpecificBlindingKey(scriptid, key)) {
strErr = "Error reading wallet database: LoadSpecificBlindingKey failed";
return false;
}
}
} catch (...)
{
return false;
@ -656,6 +696,17 @@ DBErrors CWalletDB::LoadWallet(CWallet* pwallet)
pwallet->wtxOrdered.insert(make_pair(entry.nOrderPos, CWallet::TxPair((CWalletTx*)0, &entry)));
}
if (result == DB_LOAD_OK && pwallet->blinding_derivation_key.IsNull()) {
CKey key;
key.MakeNewKey(true);
uint256 keybin;
memcpy(keybin.begin(), key.begin(), key.size());
pwallet->blinding_derivation_key = keybin;
if (!WriteBlindingDerivationKey(pwallet->blinding_derivation_key)) {
result = DB_LOAD_FAIL;
}
}
return result;
}

View file

@ -25,6 +25,7 @@ struct CBlockLocator;
class CKeyPool;
class CMasterKey;
class CScript;
class CScriptID;
class CWallet;
class CWalletTx;
class uint160;
@ -166,6 +167,9 @@ public:
CAmount GetAccountCreditDebit(const std::string& strAccount);
void ListAccountCreditDebit(const std::string& strAccount, std::list<CAccountingEntry>& acentries);
bool WriteSpecificBlindingKey(const CScriptID& scriptid, const uint256& key);
bool WriteBlindingDerivationKey(const uint256& key);
DBErrors LoadWallet(CWallet* pwallet);
DBErrors FindWalletTx(CWallet* pwallet, std::vector<uint256>& vTxHash, std::vector<CWalletTx>& vWtx);
DBErrors ZapWalletTx(CWallet* pwallet, std::vector<CWalletTx>& vWtx);