diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 2c767e9869..8a4ad2ff9c 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -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" }, diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index a89c9c743a..75b66b6617 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -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\" : \"\" (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)) diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 16d641c9d9..0b0861f834 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; @@ -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(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& 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 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 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 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 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 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 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.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"} }, diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index d7a76e9ae1..f3ece36986 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -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 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 txids; @@ -1164,7 +1184,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; @@ -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 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; @@ -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::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); } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 6151f164cd..ae55cbf824 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -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 #include +#include + 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& 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 CWallet::ResendWalletTransactionsBefore(int64_t nTime, CConnman* connman) { std::vector result; @@ -2081,7 +2157,7 @@ void CWallet::AvailableCoins(vector& 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 > coin = make_pair(n,make_pair(pcoin, i)); @@ -2253,8 +2329,8 @@ bool CWallet::SelectCoins(const vector& 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& 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& vecSend, CWalletTx& wt while (true) { nChangePosInOut = nChangePosRequest; + std::vector output_pubkeys; + bool fBlindedOuts = false; + txNew.vin.clear(); txNew.vout.clear(); wtxNew.fFromMe = true; @@ -2456,12 +2539,12 @@ bool CWallet::CreateTransaction(const vector& 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& 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& 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& 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& 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 non-maxint so that @@ -2605,6 +2704,23 @@ bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wt std::numeric_limits::max() - (fWalletRbf ? 2 : 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); // Fill in dummy signatures for fee calculation. if (!DummySignTx(txNew, setCoins)) { @@ -3146,7 +3262,7 @@ std::map 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::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 98e4fb87b9..cdb3f6027d 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" @@ -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 > 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 vBlindingFactors; + mutable std::vector vAmountsOut; + mutable std::vector 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& listReceived, @@ -427,6 +437,17 @@ public: bool RelayWalletTransaction(CConnman* connman); 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; }; @@ -656,6 +677,7 @@ public: typedef std::map MasterKeyMap; MasterKeyMap mapMasterKeys; unsigned int nMasterKeyMaxID; + std::map mapSpecificBlindingKeys; CWallet() { @@ -686,6 +708,8 @@ public: nLastResend = 0; nTimeFirstKey = 0; fBroadcastTransactions = false; + blinding_key = CKey(); + blinding_derivation_key = uint256(); } std::map mapWallet; @@ -704,6 +728,13 @@ public: std::set 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); diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 81fdde401e..5c0f500791 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -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 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 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; } diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index c7c65465df..cf614f4a12 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; @@ -166,6 +167,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 LoadWallet(CWallet* pwallet); DBErrors FindWalletTx(CWallet* pwallet, std::vector& vTxHash, std::vector& vWtx); DBErrors ZapWalletTx(CWallet* pwallet, std::vector& vWtx);