From 683dd3cdbebd1814ddfb61b2990e0c47d15fcc4d Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Tue, 1 Mar 2016 14:56:41 -0800 Subject: [PATCH] Wallet updates for CT A few fixes from Jerzy Kozera --- src/rpc/client.cpp | 1 + src/rpc/misc.cpp | 19 +++ src/rpc/rawtransaction.cpp | 205 +++++++++++++++++++++++++- src/wallet/rpcwallet.cpp | 107 +++++++++----- src/wallet/wallet.cpp | 286 +++++++++++++++++++++++++++++++------ src/wallet/wallet.h | 49 ++++++- src/wallet/walletdb.cpp | 51 +++++++ src/wallet/walletdb.h | 4 + 8 files changed, 641 insertions(+), 81 deletions(-) diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 82a9b459c3..61e871bbbf 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -75,6 +75,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "createrawtransaction", 0 }, { "createrawtransaction", 1 }, { "createrawtransaction", 2 }, + { "rawblindrawtransaction", 1 }, { "signrawtransaction", 1 }, { "signrawtransaction", 2 }, { "sendrawtransaction", 1 }, diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 06489566ba..7f0a4ce2ee 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -166,6 +166,9 @@ UniValue validateaddress(const UniValue& params, bool fHelp) " \"pubkey\" : \"publickeyhex\", (string) The hex value of the raw public key\n" " \"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" + " \"confidential_key\" : \"pubkey\" (string) The confidentiality key associated with the address, or \"\" if none\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\" : \"\" (string, optional) The Hash160 of the HD master pubkey\n" "}\n" @@ -194,10 +197,26 @@ UniValue validateaddress(const UniValue& params, bool fHelp) 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)) diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index ce69e790d2..742adc0101 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -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 #include +#include #include 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; @@ -104,7 +130,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); @@ -356,6 +403,7 @@ UniValue createrawtransaction(const UniValue& params, bool fHelp) " {\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" @@ -454,6 +502,12 @@ UniValue createrawtransaction(const UniValue& params, bool fHelp) 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(confidentiality_pubkey.begin(), confidentiality_pubkey.end()); + } rawTx.vout.push_back(out); } } @@ -463,6 +517,153 @@ UniValue createrawtransaction(const UniValue& params, bool fHelp) return EncodeHexTx(rawTx); } +void FillOutputBlinds(const CMutableTransaction& tx, bool fUseWallet, std::vector& output_blinds, std::vector& 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 UniValue& params, bool fHelp) +{ + if (fHelp || (params.size() != 2 && params.size() != 3)) + throw 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 (params.size() == 2) { + RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)(UniValue::VARR)); + } else { + RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)(UniValue::VARR)(UniValue::VSTR)); + } + + vector txData(ParseHexV(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 = 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 input_blinds; + std::vector output_blinds; + std::vector 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 UniValue& params, bool fHelp) +{ + if (fHelp || (params.size() != 1 && 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 (params.size() == 1) { + RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)); + } else { + RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)(UniValue::VSTR)); + } + + vector txData(ParseHexV(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 input_blinds; + std::vector output_blinds; + std::vector output_pubkeys; + for (size_t nIn = 0; nIn < tx.vin.size(); nIn++) { + std::map::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.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 UniValue& params, bool fHelp) { if (fHelp || params.size() != 1) @@ -921,6 +1122,8 @@ static const CRPCCommand commands[] = { "rawtransactions", "decodescript", &decodescript, true }, { "rawtransactions", "sendrawtransaction", &sendrawtransaction, false }, { "rawtransactions", "signrawtransaction", &signrawtransaction, false }, /* uses wallet if enabled */ + { "rawtransactions", "rawblindrawtransaction", &rawblindrawtransaction, false }, + { "rawtransactions", "blindrawtransaction", &blindrawtransaction, true }, { "blockchain", "gettxoutproof", &gettxoutproof, true }, { "blockchain", "verifytxoutproof", &verifytxoutproof, true }, diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index c52fcfdf60..0d7c1f3d9f 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -141,7 +141,7 @@ UniValue getnewaddress(const UniValue& params, bool fHelp) pwalletMain->SetAddressBook(keyID, strAccount, "receive"); - return CBitcoinAddress(keyID).ToString(); + return CBitcoinAddress(keyID).AddBlindingKey(pwalletMain->GetBlindingPubKey(GetScriptForDestination(CTxDestination(keyID)))).ToString(); } @@ -152,7 +152,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 UniValue& params, bool fHelp) @@ -218,7 +218,7 @@ UniValue getrawchangeaddress(const UniValue& params, bool fHelp) CKeyID keyID = vchPubKey.GetID(); - return CBitcoinAddress(keyID).ToString(); + return CBitcoinAddress(keyID).AddBlindingKey(pwalletMain->GetBlindingPubKey(GetScriptForDestination(CTxDestination(keyID)))).ToString(); } @@ -327,9 +327,10 @@ UniValue getaddressesbyaccount(const UniValue& params, bool fHelp) // 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()); @@ -337,7 +338,7 @@ UniValue getaddressesbyaccount(const UniValue& params, bool fHelp) 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(); @@ -357,7 +358,7 @@ static void SendMoney(const CTxDestination &address, CAmount nValue, bool fSubtr std::string strError; vector 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 > pwalletMain->GetBalance()) @@ -408,6 +409,11 @@ UniValue sendtoaddress(const UniValue& params, bool fHelp) 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 (params.size() > 2 && !params[2].isNull() && !params[2].get_str().empty()) @@ -421,7 +427,7 @@ UniValue sendtoaddress(const UniValue& params, bool fHelp) EnsureWalletIsUnlocked(); - SendMoney(address.Get(), nAmount, fSubtractFeeFromAmount, wtx); + SendMoney(address.Get(), nAmount, fSubtractFeeFromAmount, confidentiality_pubkey, wtx); return wtx.GetHash().GetHex(); } @@ -464,11 +470,13 @@ UniValue listaddressgroupings(const UniValue& params, bool fHelp) 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); } @@ -581,10 +589,11 @@ UniValue getreceivedbyaddress(const UniValue& params, bool fHelp) if (wtx.IsCoinBase() || !CheckFinalTx(wtx)) continue; - BOOST_FOREACH(const CTxOut& txout, wtx.vout) - if (txout.scriptPubKey == scriptPubKey) + + for (unsigned int i = 0; i < wtx.vout.size(); i++) + if (wtx.vout[i].scriptPubKey == scriptPubKey) if (wtx.GetDepthInMainChain() >= nMinDepth) - nAmount += txout.nValue; + nAmount += wtx.GetValueOut(i); } return ValueFromAmount(nAmount); @@ -635,12 +644,12 @@ UniValue getreceivedbyaccount(const UniValue& params, bool fHelp) if (wtx.IsCoinBase() || !CheckFinalTx(wtx)) continue; - BOOST_FOREACH(const CTxOut& txout, wtx.vout) + for (unsigned int i = 0; i < wtx.vout.size(); i++) { CTxDestination address; - if (ExtractDestination(txout.scriptPubKey, address) && IsMine(*pwalletMain, address) && setAddress.count(address)) + if (ExtractDestination(wtx.vout[i].scriptPubKey, address) && IsMine(*pwalletMain, address) && setAddress.count(address)) if (wtx.GetDepthInMainChain() >= nMinDepth) - nAmount += txout.nValue; + nAmount += wtx.GetValueOut(i); } } @@ -830,6 +839,11 @@ UniValue sendfrom(const UniValue& params, bool fHelp) if (params.size() > 3) nMinDepth = params[3].get_int(); + + CPubKey confidentiality_pubkey; + if (address.IsBlinded()) + confidentiality_pubkey = address.GetBlindingKey(); + CWalletTx wtx; wtx.strFromAccount = strAccount; if (params.size() > 4 && !params[4].isNull() && !params[4].get_str().empty()) @@ -844,7 +858,7 @@ UniValue sendfrom(const UniValue& params, bool fHelp) 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(); } @@ -929,6 +943,11 @@ UniValue sendmany(const UniValue& params, bool fHelp) 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]; @@ -936,7 +955,7 @@ UniValue sendmany(const UniValue& params, bool fHelp) fSubtractFeeFromAmount = true; } - CRecipient recipient = {scriptPubKey, nAmount, fSubtractFeeFromAmount}; + CRecipient recipient = {scriptPubKey, nAmount, confidentiality_pubkey, fSubtractFeeFromAmount}; vecSend.push_back(recipient); } @@ -1103,6 +1122,7 @@ UniValue addwitnessaddress(const UniValue& params, bool fHelp) struct tallyitem { + CBitcoinAddress address; CAmount nAmount; int nConf; vector txids; @@ -1133,7 +1153,7 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts) filter = filter | ISMINE_WATCH_ONLY; // Tally - map mapTally; + map mapTally; for (map::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) { const CWalletTx& wtx = (*it).second; @@ -1145,18 +1165,23 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts) if (nDepth < nMinDepth) continue; - BOOST_FOREACH(const CTxOut& txout, wtx.vout) + for (unsigned int i = 0; i < wtx.vout.size(); i++) { CTxDestination address; - if (!ExtractDestination(txout.scriptPubKey, address)) + if (!ExtractDestination(wtx.vout[i].scriptPubKey, address)) continue; isminefilter mine = IsMine(*pwalletMain, address); if(!(mine & filter)) continue; + CBitcoinAddress bitcoinaddress(address); + if (!wtx.vout[i].nValue.IsAmount()) + bitcoinaddress.AddBlindingKey(wtx.GetBlindingKey(i)); + tallyitem& item = mapTally[address]; - item.nAmount += txout.nValue; + 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) @@ -1167,19 +1192,21 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts) // Reply UniValue ret(UniValue::VARR); map 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::iterator it = mapTally.find(address); + map::iterator it = mapTally.find(address); if (it == mapTally.end() && !fIncludeEmpty) continue; + CBitcoinAddress fulladdress = address; CAmount nAmount = 0; int nConf = std::numeric_limits::max(); bool fIsWatchonly = false; if (it != mapTally.end()) { + fulladdress = (*it).second.address; nAmount = (*it).second.nAmount; nConf = (*it).second.nConf; fIsWatchonly = (*it).second.fIsWatchonly; @@ -1197,7 +1224,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::max() ? 0 : nConf))); @@ -1310,11 +1337,14 @@ UniValue listreceivedbyaccount(const UniValue& params, bool fHelp) return ListReceived(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) @@ -1338,7 +1368,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)) @@ -1366,7 +1396,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) @@ -2359,6 +2389,8 @@ UniValue listunspent(const UniValue& params, bool fHelp) " \"scriptPubKey\" : \"key\", (string) the script key\n" " \"amount\" : x.xxx, (numeric) the transaction 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" @@ -2412,13 +2444,16 @@ UniValue listunspent(const UniValue& params, bool fHelp) 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)); @@ -2431,10 +2466,14 @@ UniValue listunspent(const UniValue& params, bool fHelp) } entry.push_back(Pair("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end()))); - entry.push_back(Pair("amount", ValueFromAmount(out.tx->vout[out.i].nValue))); + 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); } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index a305215d47..51d28cc0ee 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -11,6 +11,7 @@ #include "coincontrol.h" #include "consensus/consensus.h" #include "consensus/validation.h" +#include "crypto/hmac_sha256.h" #include "key.h" #include "keystore.h" #include "main.h" @@ -32,6 +33,8 @@ #include #include +#include + using namespace std; CWallet* pwalletMain = NULL; @@ -74,7 +77,7 @@ struct CompareValueOnly std::string COutput::ToString() const { - return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->vout[i].nValue)); + return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->GetValueOut(i))); } const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const @@ -1079,7 +1082,7 @@ CAmount CWallet::GetDebit(const CTxIn &txin, const isminefilter& filter) const const CWalletTx& prev = (*mi).second; if (txin.prevout.n < prev.vout.size()) if (IsMine(prev.vout[txin.prevout.n]) & filter) - return prev.vout[txin.prevout.n].nValue; + return prev.GetValueOut(txin.prevout.n); } } return 0; @@ -1090,13 +1093,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)) - throw std::runtime_error(std::string(__func__) + ": value out of range"); - return ((IsMine(txout) & filter) ? txout.nValue : 0); -} - bool CWallet::IsChange(const CTxOut& txout) const { // TODO: fix handling of 'change' outputs. The assumption is that any @@ -1119,13 +1115,6 @@ bool CWallet::IsChange(const CTxOut& txout) const return false; } -CAmount CWallet::GetChange(const CTxOut& txout) const -{ - if (!MoneyRange(txout.nValue)) - throw std::runtime_error(std::string(__func__) + ": value out of range"); - return (IsChange(txout) ? txout.nValue : 0); -} - bool CWallet::IsMine(const CTransaction& tx) const { BOOST_FOREACH(const CTxOut& txout, tx.vout) @@ -1151,24 +1140,24 @@ CAmount CWallet::GetDebit(const CTransaction& tx, const isminefilter& filter) co return nDebit; } -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.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.vout.size(); i++) { - nChange += GetChange(txout); + nChange += tx.GetChange(i); if (!MoneyRange(nChange)) throw std::runtime_error(std::string(__func__) + ": value out of range"); } @@ -1319,7 +1308,11 @@ void CWalletTx::GetAmounts(list& listReceived, address = CNoDestination(); } - COutputEntry output = {address, txout.nValue, (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) @@ -1502,6 +1495,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(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 @@ -1567,8 +1573,7 @@ CAmount CWalletTx::GetAvailableCredit(bool fUseCache) const { if (!pwallet->IsSpent(hashTx, i)) { - const CTxOut &txout = 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"); } @@ -1610,8 +1615,7 @@ CAmount CWalletTx::GetAvailableWatchOnlyCredit(const bool& fUseCache) const { if (!pwallet->IsSpent(GetHash(), i)) { - const CTxOut &txout = 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"); } @@ -1622,6 +1626,16 @@ CAmount CWalletTx::GetAvailableWatchOnlyCredit(const bool& fUseCache) const return nCredit; } +CAmount CWalletTx::GetChange(unsigned int nTxOut) const +{ + CAmount amount = 0; + if (pwallet->IsChange(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) @@ -1680,6 +1694,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 < vout.size()); + if (mapValue["blindingdata"].size() < (nOut + 1) * 74) { + mapValue["blindingdata"].resize(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(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 CWallet::ResendWalletTransactionsBefore(int64_t nTime) { std::vector result; @@ -1860,7 +1936,7 @@ void CWallet::AvailableCoins(vector& vCoins, bool fOnlyConfirmed, const for (unsigned int i = 0; i < pcoin->vout.size(); i++) { isminetype mine = IsMine(pcoin->vout[i]); if (!(IsSpent(wtxid, i)) && mine != ISMINE_NO && - !IsLockedCoin((*it).first, i) && (pcoin->vout[i].nValue > 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) || @@ -1943,7 +2019,7 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int continue; int i = output.i; - CAmount n = pcoin->vout[i].nValue; + CAmount n = pcoin->GetValueOut(i); pair > coin = make_pair(n,make_pair(pcoin, i)); @@ -2029,8 +2105,8 @@ bool CWallet::SelectCoins(const vector& vAvailableCoins, const CAmount& BOOST_FOREACH(const COutput& out, vCoins) { if (!out.fSpendable) - continue; - nValueRet += out.tx->vout[out.i].nValue; + continue; + nValueRet += out.tx->GetValueOut(out.i); setCoinsRet.insert(make_pair(out.tx, out.i)); } return (nValueRet >= nTargetValue); @@ -2052,7 +2128,7 @@ bool CWallet::SelectCoins(const vector& vAvailableCoins, const CAmount& // Clearly invalid input, fail if (pcoin->vout.size() <= outpoint.n) return false; - nValueFromPresetInputs += pcoin->vout[outpoint.n].nValue; + nValueFromPresetInputs += pcoin->GetValueOut(outpoint.n); setPresetCoins.insert(make_pair(pcoin, outpoint.n)); } else return false; // TODO: Allow non-wallet inputs @@ -2088,7 +2164,11 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, bool ov // Turn the txout set into a CRecipient vector BOOST_FOREACH(const CTxOut& txOut, tx.vout) { - CRecipient recipient = {txOut.scriptPubKey, txOut.nValue, false}; + 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); } @@ -2199,6 +2279,9 @@ bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wt while (true) { nChangePosInOut = nChangePosRequest; + std::vector output_pubkeys; + bool fBlindedOuts = false; + txNew.vin.clear(); txNew.vout.clear(); txNew.wit.SetNull(); @@ -2216,12 +2299,12 @@ bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wt if (recipient.fSubtractFeeFromAmount) { - txout.nValue -= 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 -= nFeeRet % nSubtractFeeFromAmount; + txout.nValue = recipient.nAmount - (nFeeRet / nSubtractFeeFromAmount) - (nFeeRet % nSubtractFeeFromAmount); } } @@ -2229,7 +2312,7 @@ bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wt { if (recipient.fSubtractFeeFromAmount && nFeeRet > 0) { - if (txout.nValue < 0) + if (txout.nValue.GetAmount() < 0) strFailReason = _("The transaction amount is too small to pay the fee"); else strFailReason = _("The transaction amount is too small to send after the fee has been deducted"); @@ -2239,6 +2322,9 @@ bool CWallet::CreateTransaction(const vector& 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 @@ -2249,9 +2335,12 @@ bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wt strFailReason = _("Insufficient funds"); return false; } + bool fBlindedIns = false; BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins) { - CAmount nCredit = pcoin.first->vout[pcoin.second].nValue; + if (!pcoin.first->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. @@ -2301,13 +2390,13 @@ bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wt // So instead we raise the change and deduct from the recipient. if (nSubtractFeeFromAmount > 0 && newTxOut.IsDust(::minRelayTxFee)) { - CAmount nDust = newTxOut.GetDustThreshold(::minRelayTxFee) - newTxOut.nValue; - newTxOut.nValue += 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 -= nDust; + txNew.vout[i].nValue = txNew.vout[i].nValue.GetAmount() - nDust; if (txNew.vout[i].IsDust(::minRelayTxFee)) { strFailReason = _("The transaction amount is too small to send after the fee has been deducted"); @@ -2341,11 +2430,21 @@ bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wt vector::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 max()-1 so that the @@ -2355,6 +2454,23 @@ bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wt std::numeric_limits::max()-1)); txNew.nTxFee = nFeeRet; + LogPrintf("Created transaction (before blinding): %s", CTransaction(txNew).ToString()); + + // Create blinded outputs + std::vector input_blinds; + std::vector 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); // Sign int nIn = 0; @@ -2836,7 +2952,7 @@ std::map CWallet::GetAddressBalances() if(!ExtractDestination(pcoin->vout[i].scriptPubKey, addr)) continue; - CAmount n = IsSpent(walletEntry.first, i) ? 0 : pcoin->vout[i].nValue; + CAmount n = IsSpent(walletEntry.first, i) ? 0 : pcoin->GetValueOut(i); if (!balances.count(addr)) balances[addr] = 0; @@ -3596,3 +3712,91 @@ bool CMerkleTx::AcceptToMemoryPool(bool fLimitFree, CAmount nAbsurdFee) CValidationState state; return ::AcceptToMemoryPool(mempool, state, *this, fLimitFree, NULL, false, nAbsurdFee); } + +CKey CWallet::GetBlindingKey(const CScript* script) const +{ + CKey key; + + if (script != NULL) { + std::map::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(); +} diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 0c95fdf4b0..b5b86d6fc7 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -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" @@ -124,6 +125,7 @@ struct CRecipient { CScript scriptPubKey; CAmount nAmount; + CPubKey confidentiality_key; bool fSubtractFeeFromAmount; }; @@ -153,6 +155,7 @@ struct COutputEntry CTxDestination destination; CAmount amount; int vout; + CPubKey confidentiality_pubkey; }; /** A transaction with a merkle branch linking it to the block chain. */ @@ -229,7 +232,7 @@ private: const CWallet* pwallet; public: - mapValue_t mapValue; + mutable mapValue_t mapValue; std::vector > vOrderForm; unsigned int fTimeReceivedIsTxTime; unsigned int nTimeReceived; //!< time received by this node @@ -238,6 +241,11 @@ public: std::string strFromAccount; int64_t nOrderPos; //!< position in ordered transaction list + // For each output + mutable std::vector vBlindingFactors; + mutable std::vector vAmountsOut; + mutable std::vector vBlindingKeys; + // memory only mutable bool fDebitCached; mutable bool fCreditCached; @@ -374,11 +382,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& listReceived, @@ -404,6 +414,17 @@ public: bool RelayWalletTransaction(); std::set 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; }; @@ -596,6 +617,7 @@ public: std::set setKeyPool; std::map mapKeyMetadata; + std::map mapSpecificBlindingKeys; typedef std::map MasterKeyMap; MasterKeyMap mapMasterKeys; @@ -632,6 +654,8 @@ public: nLastResend = 0; nTimeFirstKey = 0; fBroadcastTransactions = false; + blinding_key = CKey(); + blinding_derivation_key = uint256(); } std::map mapWallet; @@ -652,6 +676,13 @@ public: int64_t nTimeFirstKey; + //! 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 @@ -689,6 +720,10 @@ public: bool LoadKey(const CKey& key, const CPubKey &pubkey) { return CCryptoKeyStore::AddKeyPubKey(key, pubkey); } //! Load metadata (used by LoadWallet) bool LoadKeyMetadata(const CPubKey &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; } @@ -792,15 +827,13 @@ public: isminetype IsMine(const CTxIn& txin) const; 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 CTransaction& tx, const isminefilter& filter) const; - CAmount GetChange(const CTransaction& tx) const; + CAmount GetCredit(const CWalletTx& tx, const isminefilter& filter) const; + CAmount GetChange(const CWalletTx& tx) const; void SetBestChain(const CBlockLocator& loc); DBErrors LoadWallet(bool& fFirstRunRet); @@ -886,6 +919,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; + /* Returns the wallets help message */ static std::string GetWalletHelpString(bool showDebug); diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 31aec0f9e5..2a875fd7c0 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -197,6 +197,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 entries; @@ -609,6 +619,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 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; @@ -726,6 +766,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; } diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 5addd5c5c0..cfd8039147 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -25,6 +25,7 @@ struct CBlockLocator; class CKeyPool; class CMasterKey; class CScript; +class CScriptID; class CWallet; class CWalletTx; class uint160; @@ -167,6 +168,9 @@ public: CAmount GetAccountCreditDebit(const std::string& strAccount); void ListAccountCreditDebit(const std::string& strAccount, std::list& acentries); + bool WriteSpecificBlindingKey(const CScriptID& scriptid, const uint256& key); + bool WriteBlindingDerivationKey(const uint256& key); + DBErrors ReorderTransactions(CWallet* pwallet); DBErrors LoadWallet(CWallet* pwallet); DBErrors FindWalletTx(CWallet* pwallet, std::vector& vTxHash, std::vector& vWtx);