diff --git a/src/base58.cpp b/src/base58.cpp index 8d5c6f883b..00b50c7c92 100644 --- a/src/base58.cpp +++ b/src/base58.cpp @@ -237,6 +237,9 @@ bool CBitcoinAddress::Set(const CTxDestination& dest) CBitcoinAddress& CBitcoinAddress::AddBlindingKey(const CPubKey& pubkey) { + if (!pubkey.IsValid()) { + return *this; + } assert(pubkey.size() == 33); assert(!IsBlinded()); std::vector data = vchVersion; diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index b23acb5089..6254a7de9e 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -298,11 +298,12 @@ static void MutateTxBlind(CMutableTransaction& tx, const string& strInput) bool fBlindedIns = false; bool fBlindedOuts = false; - std::vector > input_blinds; - std::vector > output_blinds; + std::vector input_blinds; + std::vector output_blinds; std::vector output_pubkeys; for (size_t nIn = 0; nIn < tx.vin.size(); nIn++) { - std::vector blind = ParseHex(input_blinding_factors[nIn]); + uint256 blind; + blind.SetHex(input_blinding_factors[nIn]); if (blind.size() == 0) { input_blinds.push_back(blind); } else if (blind.size() == 32) { @@ -323,7 +324,7 @@ static void MutateTxBlind(CMutableTransaction& tx, const string& strInput) output_pubkeys.push_back(pubkey); fBlindedOuts = true; } - output_blinds.push_back(std::vector(0, 0)); + output_blinds.push_back(uint256()); } if (fBlindedIns && !fBlindedOuts) { diff --git a/src/blind.cpp b/src/blind.cpp index 8e1c820303..17620feb97 100644 --- a/src/blind.cpp +++ b/src/blind.cpp @@ -34,33 +34,32 @@ const secp256k1_context* ECC_Blinding_Context() { return secp256k1_blind_context; } -int UnblindOutput(const CKey &key, const CTxOut& txout, CAmount& amount_out, std::vector& blinding_factor_out) +bool UnblindOutput(const CKey &key, const CTxOut& txout, CAmount& amount_out, uint256& blinding_factor_out) { - if (txout.nValue.IsAmount()) { - amount_out = txout.nValue.GetAmount(); - blinding_factor_out.resize(0); - return -1; + if (!key.IsValid()) { + return false; } CPubKey ephemeral_key(txout.nValue.vchNonceCommitment); if (!ephemeral_key.IsValid()) { - return 0; + return false; } uint256 nonce = key.ECDH(ephemeral_key); CSHA256().Write(nonce.begin(), 32).Finalize(nonce.begin()); unsigned char msg[4096]; int msg_size; uint64_t min_value, max_value, amount; - blinding_factor_out.resize(32); - int res = secp256k1_rangeproof_rewind(secp256k1_blind_context, &blinding_factor_out[0], &amount, msg, &msg_size, nonce.begin(), &min_value, &max_value, &txout.nValue.vchCommitment[0], &txout.nValue.vchRangeproof[0], txout.nValue.vchRangeproof.size()); + int res = secp256k1_rangeproof_rewind(secp256k1_blind_context, blinding_factor_out.begin(), &amount, msg, &msg_size, nonce.begin(), &min_value, &max_value, &txout.nValue.vchCommitment[0], &txout.nValue.vchRangeproof[0], txout.nValue.vchRangeproof.size()); if (!res || amount > (uint64_t)MAX_MONEY || !MoneyRange((CAmount)amount)) { amount_out = 0; - blinding_factor_out.resize(0); - } else + blinding_factor_out = uint256(); + return false; + } else { amount_out = (CAmount)amount; - return res ? 1 : 0; + return true; + } } -void BlindOutputs(const std::vector >& input_blinding_factors, const std::vector >& output_blinding_factors, const std::vector& output_pubkeys, CMutableTransaction& tx) +void BlindOutputs(const std::vector& input_blinding_factors, const std::vector& output_blinding_factors, const std::vector& output_pubkeys, CMutableTransaction& tx) { assert(tx.vout.size() == output_blinding_factors.size()); assert(tx.vout.size() == output_pubkeys.size()); @@ -71,9 +70,9 @@ void BlindOutputs(const std::vector >& input_blinding int nBlindsIn = 0; for (size_t nIn = 0; nIn < tx.vin.size(); nIn++) { - if (input_blinding_factors[nIn].size() != 0) { + if (input_blinding_factors[nIn] != uint256()) { assert(input_blinding_factors[nIn].size() == 32); - blindptrs.push_back(&input_blinding_factors[nIn][0]); + blindptrs.push_back(input_blinding_factors[nIn].begin()); nBlindsIn++; } } @@ -81,10 +80,9 @@ void BlindOutputs(const std::vector >& input_blinding int nBlindsOut = 0; int nToBlind = 0; for (size_t nOut = 0; nOut < tx.vout.size(); nOut++) { - assert((output_blinding_factors[nOut].size() != 0) == !tx.vout[nOut].nValue.IsAmount()); - if (output_blinding_factors[nOut].size() != 0) { - assert(output_blinding_factors[nOut].size() == 32); - blindptrs.push_back(&output_blinding_factors[nOut][0]); + assert((output_blinding_factors[nOut] != uint256()) == !tx.vout[nOut].nValue.IsAmount()); + if (output_blinding_factors[nOut] != uint256()) { + blindptrs.push_back(output_blinding_factors[nOut].begin()); nBlindsOut++; } else { if (output_pubkeys[nOut].IsValid()) { @@ -98,7 +96,7 @@ void BlindOutputs(const std::vector >& input_blinding } int nBlinded = 0; - unsigned char blind[nToBlind][32]; + unsigned char blind[tx.vout.size()][32]; for (size_t nOut = 0; nOut < tx.vout.size(); nOut++) { if (tx.vout[nOut].nValue.IsAmount() && output_pubkeys[nOut].IsValid()) { diff --git a/src/blind.h b/src/blind.h index c44677ac11..393f38ae3f 100644 --- a/src/blind.h +++ b/src/blind.h @@ -8,7 +8,7 @@ void ECC_Blinding_Start(); void ECC_Blinding_Stop(); -int UnblindOutput(const CKey& blinding_key, const CTxOut& txout, CAmount& amount_out, std::vector& blinding_factor_out); -void BlindOutputs(const std::vector >& input_blinding_factors, const std::vector >& output_blinding_factors, const std::vector& output_pubkeys, CMutableTransaction& tx); +bool UnblindOutput(const CKey& blinding_key, const CTxOut& txout, CAmount& amount_out, uint256& blinding_factor_out); +void BlindOutputs(const std::vector& input_blinding_factors, const std::vector& output_blinding_factors, const std::vector& output_pubkeys, CMutableTransaction& tx); #endif diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index 417702daa6..2153af0d30 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -383,7 +383,7 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con return QString(); } } - strAddress = CBitcoinAddress(newKey.GetID()).AddBlindingKey(wallet->blinding_pubkey).ToString(); + strAddress = CBitcoinAddress(newKey.GetID()).AddBlindingKey(wallet->GetBlindingPubKey(GetScriptForDestination(CTxDestination(newKey.GetID())))).ToString(); } else { diff --git a/src/rpcdump.cpp b/src/rpcdump.cpp index 8b95373cff..cc92ff370f 100644 --- a/src/rpcdump.cpp +++ b/src/rpcdump.cpp @@ -397,3 +397,85 @@ Value dumpwallet(const Array& params, bool fHelp) file.close(); return Value::null; } + +Value dumpblindingkey(const Array& params, bool fHelp) +{ + if (fHelp || params.size() < 1 || params.size() > 1) + throw runtime_error( + "dumpblindingkey \"address\"\n" + "\nDumps the private blinding key for a CT address in hex." + "\nArguments:\n" + "1. \"address\" (string, required) The CT address\n" + ); + + CBitcoinAddress address(params[0].get_str()); + if (!address.IsValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); + } + if (!address.IsBlinded()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Not a CT address"); + } + + CTxDestination dest = address.Get(); + CScript script = GetScriptForDestination(dest); + CKey key; + key = pwalletMain->GetBlindingKey(&script); + if (key.IsValid()) { + CPubKey pubkey = key.GetPubKey(); + if (pubkey == address.GetBlindingKey()) { + return HexStr(key.begin(), key.end()); + } + } + // Just for backward compatibility + key = pwalletMain->GetBlindingKey(NULL); + if (key.IsValid()) { + CPubKey pubkey = key.GetPubKey(); + if (pubkey == address.GetBlindingKey()) { + return HexStr(key.begin(), key.end()); + } + } + throw JSONRPCError(RPC_WALLET_ERROR, "Blinding key for address is unknown"); +} + +Value importblindingkey(const Array& params, bool fHelp) +{ + if (fHelp || params.size() < 2 || params.size() > 2) + throw runtime_error( + "importblindingkey \"address\" \"blindinghex\"\n" + "\nImports a private blinding key in hex for a CT address." + "\nArguments:\n" + "1. \"address\" (string, required) The CT address\n" + "2. \"hexkey\" (string, required) The blinding key in hex\n" + ); + + CBitcoinAddress address(params[0].get_str()); + if (!address.IsValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script"); + } + if (!address.IsBlinded()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Not a CT address"); + } + + if (!IsHex(params[1].get_str())) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid hexadecimal for key"); + } + std::vector keydata = ParseHex(params[1].get_str()); + if (keydata.size() != 32) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid hexadecimal key length"); + } + + CKey key; + key.Set(keydata.begin(), keydata.end(), true); + if (!key.IsValid() || key.GetPubKey() != address.GetBlindingKey()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Address and key do not match"); + } + + uint256 keyval; + memcpy(keyval.begin(), &keydata[0], 32); + if (!pwalletMain->AddSpecificBlindingKey(CScriptID(GetScriptForDestination(address.Get())), keyval)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Failed to import blinding key"); + } + pwalletMain->MarkDirty(); + + return Value::null; +} diff --git a/src/rpcmisc.cpp b/src/rpcmisc.cpp index f6ee836fa9..c94d304871 100644 --- a/src/rpcmisc.cpp +++ b/src/rpcmisc.cpp @@ -196,12 +196,13 @@ Value validateaddress(const Array& params, bool fHelp) } #ifdef ENABLE_WALLET isminetype mine = pwalletMain ? IsMine(*pwalletMain, dest) : ISMINE_NO; - if (address.IsBlinded() && address.GetBlindingKey() != pwalletMain->blinding_pubkey) { + 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)); if (!address.IsBlinded() && mine != ISMINE_NO) { - ret.push_back(Pair("confidential", address.AddBlindingKey(pwalletMain->blinding_pubkey).ToString())); + ret.push_back(Pair("confidential", address.AddBlindingKey(pwalletMain->GetBlindingPubKey(GetScriptForDestination(dest))).ToString())); } if (mine != ISMINE_NO) { ret.push_back(Pair("iswatchonly", (mine & ISMINE_WATCH_ONLY) ? true: false)); diff --git a/src/rpcrawtransaction.cpp b/src/rpcrawtransaction.cpp index fbfb6b6413..8664dc8dde 100644 --- a/src/rpcrawtransaction.cpp +++ b/src/rpcrawtransaction.cpp @@ -421,7 +421,7 @@ Value listunspent(const Array& params, bool fHelp) if (ExtractDestination(out.tx->vout[out.i].scriptPubKey, address)) { CBitcoinAddress addr(address); if (out.tx->GetBlindingFactor(out.i).size() > 0) { - addr.AddBlindingKey(pwalletMain->blinding_pubkey); + addr.AddBlindingKey(out.tx->GetBlindingKey(out.i)); } entry.push_back(Pair("address", addr.ToString())); if (pwalletMain->mapAddressBook.count(address)) @@ -588,8 +588,8 @@ Value rawblindrawtransaction(const Array& params, bool fHelp) 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 input_blinds; + std::vector output_blinds; std::vector output_pubkeys; for (size_t nOut = 0; nOut < tx.vout.size(); nOut++) { if (!tx.vout[nOut].nValue.IsAmount()) @@ -603,7 +603,7 @@ Value rawblindrawtransaction(const Array& params, bool fHelp) } output_pubkeys.push_back(pubkey); } - output_blinds.push_back(std::vector(0, 0)); + output_blinds.push_back(uint256()); } BlindOutputs(input_blinds, output_blinds, output_pubkeys, tx); @@ -649,8 +649,8 @@ Value blindrawtransaction(const Array& params, bool fHelp) LOCK(pwalletMain->cs_wallet); - std::vector > input_blinds; - std::vector > output_blinds; + 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); @@ -665,7 +665,7 @@ Value blindrawtransaction(const Array& params, bool fHelp) for (size_t nOut = 0; nOut < tx.vout.size(); nOut++) { if (!tx.vout[nOut].nValue.IsAmount()) { - std::vector blinding_factor; + uint256 blinding_factor; CAmount amount; if (UnblindOutput(pwalletMain->blinding_key, tx.vout[nOut], amount, blinding_factor) != 0) { output_blinds.push_back(blinding_factor); @@ -675,14 +675,14 @@ Value blindrawtransaction(const Array& params, bool fHelp) } } else if (tx.vout[nOut].nValue.vchNonceCommitment.size() == 0) { output_pubkeys.push_back(CPubKey()); - output_blinds.push_back(std::vector(0, 0)); + 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(std::vector(0, 0)); + output_blinds.push_back(uint256()); } } diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index 9c0562f161..136bb1bfc7 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -321,6 +321,7 @@ static const CRPCCommand vRPCCommands[] = { "wallet", "backupwallet", &backupwallet, true, false, true }, { "wallet", "dumpprivkey", &dumpprivkey, true, false, true }, { "wallet", "dumpwallet", &dumpwallet, true, false, true }, + { "wallet", "dumpblindingkey", &dumpblindingkey, true, false, true }, { "wallet", "encryptwallet", &encryptwallet, true, false, true }, { "wallet", "getaccountaddress", &getaccountaddress, true, false, true }, { "wallet", "getaccount", &getaccount, true, false, true }, @@ -336,6 +337,7 @@ static const CRPCCommand vRPCCommands[] = { "wallet", "importprivkey", &importprivkey, true, false, true }, { "wallet", "importwallet", &importwallet, true, false, true }, { "wallet", "importaddress", &importaddress, true, false, true }, + { "wallet", "importblindingkey", &importblindingkey, true, false, true }, { "wallet", "keypoolrefill", &keypoolrefill, true, false, true }, { "wallet", "listaccounts", &listaccounts, false, false, true }, { "wallet", "listaddressgroupings", &listaddressgroupings, false, false, true }, diff --git a/src/rpcserver.h b/src/rpcserver.h index 7052378651..798eb2b440 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -150,6 +150,8 @@ extern json_spirit::Value importprivkey(const json_spirit::Array& params, bool f extern json_spirit::Value importaddress(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value dumpwallet(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value importwallet(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value dumpblindingkey(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value importblindingkey(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value getgenerate(const json_spirit::Array& params, bool fHelp); // in rpcmining.cpp extern json_spirit::Value setgenerate(const json_spirit::Array& params, bool fHelp); diff --git a/src/rpcwallet.cpp b/src/rpcwallet.cpp index ff54548b83..6d97292072 100644 --- a/src/rpcwallet.cpp +++ b/src/rpcwallet.cpp @@ -110,7 +110,7 @@ Value getnewaddress(const Array& params, bool fHelp) pwalletMain->SetAddressBook(keyID, strAccount, "receive"); - return CBitcoinAddress(keyID).AddBlindingKey(pwalletMain->blinding_pubkey).ToString(); + return CBitcoinAddress(keyID).AddBlindingKey(pwalletMain->GetBlindingPubKey(GetScriptForDestination(CTxDestination(keyID)))).ToString(); } @@ -148,7 +148,7 @@ CBitcoinAddress GetAccountAddress(string strAccount, bool bForceNew=false) walletdb.WriteAccount(strAccount, account); } - return CBitcoinAddress(account.vchPubKey.GetID()).AddBlindingKey(pwalletMain->blinding_pubkey); + return CBitcoinAddress(account.vchPubKey.GetID()).AddBlindingKey(pwalletMain->GetBlindingPubKey(GetScriptForDestination(account.vchPubKey.GetID()))); } Value getaccountaddress(const Array& params, bool fHelp) @@ -205,7 +205,7 @@ Value getrawchangeaddress(const Array& params, bool fHelp) CKeyID keyID = vchPubKey.GetID(); - return CBitcoinAddress(keyID).AddBlindingKey(pwalletMain->blinding_pubkey).ToString(); + return CBitcoinAddress(keyID).AddBlindingKey(pwalletMain->GetBlindingPubKey(GetScriptForDestination(CTxDestination(keyID)))).ToString(); } @@ -300,9 +300,10 @@ Value getaddressesbyaccount(const Array& params, bool fHelp) // Find all addresses that have the given account Array ret; - 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()); @@ -426,7 +427,7 @@ Value listaddressgroupings(const Array& params, bool fHelp) { Array addressInfo; CBitcoinAddress addr(address); - addr.AddBlindingKey(pwalletMain->blinding_pubkey); + addr.AddBlindingKey(pwalletMain->GetBlindingPubKey(GetScriptForDestination(addr.Get()))); addressInfo.push_back(addr.ToString()); addressInfo.push_back(ValueFromAmount(balances[address])); { @@ -521,8 +522,6 @@ Value getreceivedbyaddress(const Array& params, bool fHelp) CScript scriptPubKey = GetScriptForDestination(address.Get()); if (!IsMine(*pwalletMain,scriptPubKey)) return (double)0.0; - if (address.IsBlinded() && address.GetBlindingKey() != pwalletMain->blinding_pubkey) - return (double)0.0; // Minimum confirmations int nMinDepth = 1; @@ -976,6 +975,7 @@ Value addmultisigaddress(const Array& params, bool fHelp) struct tallyitem { + CBitcoinAddress address; CAmount nAmount; int nConf; vector txids; @@ -1006,7 +1006,7 @@ Value ListReceived(const Array& 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; @@ -1028,7 +1028,12 @@ Value ListReceived(const Array& params, bool fByAccounts) if(!(mine & filter)) continue; + CBitcoinAddress bitcoinaddress(address); + if (!wtx.vout[i].nValue.IsAmount()) { + bitcoinaddress.AddBlindingKey(wtx.GetBlindingKey(i)); + } tallyitem& item = mapTally[address]; + item.address = bitcoinaddress; item.nAmount += wtx.GetValueOut(i); item.nConf = min(item.nConf, nDepth); item.txids.push_back(wtx.GetHash()); @@ -1040,19 +1045,21 @@ Value ListReceived(const Array& params, bool fByAccounts) // Reply Array ret; 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; @@ -1070,7 +1077,7 @@ Value ListReceived(const Array& params, bool fByAccounts) Object obj; 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))); diff --git a/src/test/blind_tests.cpp b/src/test/blind_tests.cpp index 514de11562..ca67119eb8 100644 --- a/src/test/blind_tests.cpp +++ b/src/test/blind_tests.cpp @@ -24,7 +24,7 @@ BOOST_AUTO_TEST_CASE(naive_blinding_test) CPubKey pubkey1 = key1.GetPubKey(); CPubKey pubkey2 = key2.GetPubKey(); - std::vector blind3, blind4; + uint256 blind3, blind4; { CCoinsModifier tx1 = cache.ModifyCoins(uint256(1)); @@ -52,12 +52,12 @@ BOOST_AUTO_TEST_CASE(naive_blinding_test) tx3.nTxFee = 22; BOOST_CHECK(cache.VerifyAmounts(tx3)); - std::vector > input_blinds; - std::vector > output_blinds; + std::vector input_blinds; + std::vector output_blinds; std::vector output_pubkeys; - input_blinds.push_back(std::vector(0, 0)); - input_blinds.push_back(std::vector(0, 0)); - output_blinds.push_back(std::vector(0, 0)); + input_blinds.push_back(uint256()); + input_blinds.push_back(uint256()); + output_blinds.push_back(uint256()); output_pubkeys.push_back(pubkey1); BlindOutputs(input_blinds, output_blinds, output_pubkeys, tx3); BOOST_CHECK(!tx3.vout[0].nValue.IsAmount()); @@ -91,14 +91,14 @@ BOOST_AUTO_TEST_CASE(naive_blinding_test) tx4.nTxFee = 100 + 111 - 30 - 40 - 50; BOOST_CHECK(cache.VerifyAmounts(tx4)); - std::vector > input_blinds; - std::vector > output_blinds; + std::vector input_blinds; + std::vector output_blinds; std::vector output_pubkeys; - input_blinds.push_back(std::vector(0, 0)); + input_blinds.push_back(uint256()); input_blinds.push_back(blind3); - output_blinds.push_back(std::vector(0, 0)); - output_blinds.push_back(std::vector(0, 0)); - output_blinds.push_back(std::vector(0, 0)); + output_blinds.push_back(uint256()); + output_blinds.push_back(uint256()); + output_blinds.push_back(uint256()); output_pubkeys.push_back(pubkey2); output_pubkeys.push_back(CPubKey()); output_pubkeys.push_back(pubkey2); diff --git a/src/wallet.cpp b/src/wallet.cpp index 7520613d51..e8e0fd5717 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -8,6 +8,7 @@ #include "base58.h" #include "checkpoints.h" #include "coincontrol.h" +#include "crypto/hmac_sha256.h" #include "net.h" #include "script/script.h" #include "script/sign.h" @@ -895,7 +896,7 @@ void CWalletTx::GetAmounts(list& listReceived, COutputEntry output = {address, GetValueOut(i), (int)i, CPubKey()}; if (!txout.nValue.IsAmount() && GetValueOut(i) > 0) { - output.confidentiality_pubkey = pwallet->blinding_pubkey; + output.confidentiality_pubkey = GetBlindingKey(i); } // If we are debited by the transaction, add the output as a "sent" entry @@ -1614,7 +1615,7 @@ bool CWallet::CreateTransaction(const vector& vecSend, const vector::iterator position = txNew.vout.begin()+pos; txNew.vout.insert(position, newTxOut); - output_pubkeys.insert(output_pubkeys.begin() + pos, blinding_pubkey); + output_pubkeys.insert(output_pubkeys.begin() + pos, GetBlindingPubKey(scriptChange)); nValueOut += nChange; fBlindedOuts = true; } @@ -1625,7 +1626,7 @@ bool CWallet::CreateTransaction(const vector& vecSend, const vector& vecSend, const vector > input_blinds; - std::vector > output_blinds; + std::vector input_blinds; + std::vector output_blinds; BOOST_FOREACH(const PAIRTYPE(const CWalletTx*,unsigned int)& coin, setCoins) { - std::vector blind =coin.first->GetBlindingFactor(coin.second); + 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(std::vector(0, 0)); + output_blinds.push_back(uint256()); } if (fBlindedIns && !fBlindedOuts) { strFailReason = _("Confidential inputs without confidential outputs"); @@ -2528,3 +2529,133 @@ bool CMerkleTx::AcceptToMemoryPool(bool fLimitFree, bool fRejectInsaneFee) return ::AcceptToMemoryPool(mempool, state, *this, fLimitFree, NULL, fRejectInsaneFee); } +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 != 0) { + 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 = 0; + 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 = 0; +} + +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; +} diff --git a/src/wallet.h b/src/wallet.h index 5a169ec5e2..1a3758126e 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -161,6 +161,7 @@ public: std::set setKeyPool; std::map mapKeyMetadata; + std::map mapSpecificBlindingKeys; typedef std::map MasterKeyMap; MasterKeyMap mapMasterKeys; @@ -196,9 +197,8 @@ public: nNextResend = 0; nLastResend = 0; nTimeFirstKey = 0; - unsigned char static_blinding_key[32] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32}; - blinding_key.Set(&static_blinding_key[0], &static_blinding_key[32], true); - blinding_pubkey = blinding_key.GetPubKey(); + blinding_key = CKey(); + blinding_derivation_key = uint256(); } std::map mapWallet; @@ -214,8 +214,12 @@ 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; - CPubKey blinding_pubkey; const CWalletTx* GetWalletTx(const uint256& hash) const; @@ -244,6 +248,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; } @@ -427,6 +435,12 @@ public: /** Watch-only address added */ boost::signals2::signal NotifyWatchonlyChanged; + + //! 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; }; /** A key allocated from the key pool. */ @@ -552,7 +566,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 @@ -561,8 +575,10 @@ public: std::string strFromAccount; int64_t nOrderPos; //! position in ordered transaction list - mutable std::vector > vBlindingFactors; + // For each output + mutable std::vector vBlindingFactors; mutable std::vector vAmountsOut; + mutable std::vector vBlindingKeys; // memory only mutable bool fDebitCached; @@ -662,8 +678,6 @@ public: READWRITE(nTimeReceived); READWRITE(fFromMe); READWRITE(fSpent); - READWRITE(vBlindingFactors); - READWRITE(vAmountsOut); if (ser_action.ForRead()) { @@ -930,31 +944,29 @@ public: std::set GetConflicts() const; + private: - void FillValuesAndBlindingFactors() const { - if (!vAmountsOut.size()) { - vAmountsOut.resize(vout.size()); - vBlindingFactors.resize(vout.size()); - for (unsigned int i = 0; i < vout.size(); i++) { - std::vector nonce(32, 0); - int res = UnblindOutput(pwallet->blinding_key, vout[i], vAmountsOut[i], vBlindingFactors[i]); - if (!res) - vAmountsOut[i] = -1; - } - } - } + 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 { - FillValuesAndBlindingFactors(); - return vAmountsOut[nOut]; + CAmount ret; + GetBlindingData(nOut, &ret, NULL, NULL); + return ret; } //! Returns either the blinding factor (if it is to us) or 0 - std::vector GetBlindingFactor(unsigned int nOut) const { - FillValuesAndBlindingFactors(); - return vBlindingFactors[nOut]; + uint256 GetBlindingFactor(unsigned int nOut) const { + uint256 ret; + GetBlindingData(nOut, NULL, NULL, &ret); + return ret; + } + + CPubKey GetBlindingKey(unsigned int nOut) const { + CPubKey ret; + GetBlindingData(nOut, NULL, &ret, NULL); + return ret; } }; diff --git a/src/walletdb.cpp b/src/walletdb.cpp index 7b848ffcac..32e8b1cd5f 100644 --- a/src/walletdb.cpp +++ b/src/walletdb.cpp @@ -193,9 +193,14 @@ bool CWalletDB::WriteAccountingEntry(const CAccountingEntry& acentry) return WriteAccountingEntry(++nAccountingEntryNumber, acentry); } -bool CWalletDB::WriteBlindingKey(const CKey& privKey) +bool CWalletDB::WriteSpecificBlindingKey(const CScriptID& scriptid, const uint256& key) { - return Write(std::string("blindingkey"), std::vector(privKey.begin(), privKey.end())); + 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) @@ -595,14 +600,34 @@ 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()) { - pwallet->blinding_pubkey = pwallet->blinding_key.GetPubKey(); + if (!pwallet->blinding_key.IsValid()) { + strErr = "Error reading wallet blinding key"; + return false; + } + } + else if (strType == "blindingderivationkey") + { + assert(pwallet->blinding_derivation_key == 0); + 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 (...) @@ -716,10 +741,15 @@ DBErrors CWalletDB::LoadWallet(CWallet* pwallet) if (wss.fAnyUnordered) result = ReorderTransactions(pwallet); - if (!pwallet->blinding_key.IsValid()) { - pwallet->blinding_key.MakeNewKey(true); - pwallet->blinding_pubkey = pwallet->blinding_key.GetPubKey(); - WriteBlindingKey(pwallet->blinding_key); + if (result == DB_LOAD_OK && pwallet->blinding_derivation_key == 0) { + 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/walletdb.h b/src/walletdb.h index 87bf1644c6..25837f6d4c 100644 --- a/src/walletdb.h +++ b/src/walletdb.h @@ -123,7 +123,8 @@ public: CAmount GetAccountCreditDebit(const std::string& strAccount); void ListAccountCreditDebit(const std::string& strAccount, std::list& acentries); - bool WriteBlindingKey(const CKey& privKey); + bool WriteSpecificBlindingKey(const CScriptID& scriptid, const uint256& key); + bool WriteBlindingDerivationKey(const uint256& key); DBErrors ReorderTransactions(CWallet* pwallet); DBErrors LoadWallet(CWallet* pwallet);