From 292f923d0341a7f33a85e8e73cc955697c7aa070 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Mon, 8 Jun 2015 19:28:47 -0700 Subject: [PATCH] Wallet, RPC, bitcoin-tx, etc updates for confidential transactions --- src/Makefile.am | 2 + src/Makefile.test.include | 1 + src/base58.cpp | 43 +++++++ src/base58.h | 7 ++ src/bitcoin-tx.cpp | 78 ++++++++++++- src/blind.cpp | 137 ++++++++++++++++++++++ src/blind.h | 14 +++ src/chainparams.cpp | 2 + src/chainparams.h | 1 + src/init.cpp | 3 + src/key.cpp | 11 ++ src/key.h | 5 + src/primitives/transaction.cpp | 4 +- src/qt/coincontroldialog.cpp | 14 +-- src/qt/transactiondesc.cpp | 29 +++-- src/qt/transactionrecord.cpp | 9 +- src/qt/walletmodel.cpp | 15 ++- src/rpcmisc.cpp | 17 +++ src/rpcrawtransaction.cpp | 203 ++++++++++++++++++++++++++++++++- src/rpcserver.cpp | 2 + src/rpcserver.h | 2 + src/rpcwallet.cpp | 72 +++++++----- src/test/blind_tests.cpp | 129 +++++++++++++++++++++ src/test/test_bitcoin.cpp | 3 + src/test/wallet_tests.cpp | 80 ++++++------- src/wallet.cpp | 117 +++++++++++++++---- src/wallet.h | 120 ++++++++++++------- src/walletdb.cpp | 21 ++++ src/walletdb.h | 2 + 29 files changed, 968 insertions(+), 175 deletions(-) create mode 100644 src/blind.cpp create mode 100644 src/blind.h create mode 100644 src/test/blind_tests.cpp diff --git a/src/Makefile.am b/src/Makefile.am index d2edac32a6..2355e0c2f3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -75,6 +75,7 @@ BITCOIN_CORE_H = \ allocators.h \ amount.h \ base58.h \ + blind.h \ bloom.h \ callrpc.h \ chain.h \ @@ -232,6 +233,7 @@ libbitcoin_common_a_SOURCES = \ allocators.cpp \ amount.cpp \ base58.cpp \ + blind.cpp \ bloom.cpp \ chainparams.cpp \ coins.cpp \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 9cc479c305..c6161c5ec2 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -40,6 +40,7 @@ BITCOIN_TESTS =\ test/base32_tests.cpp \ test/base58_tests.cpp \ test/base64_tests.cpp \ + test/blind_tests.cpp \ test/bloom_tests.cpp \ test/checkblock_tests.cpp \ test/Checkpoints_tests.cpp \ diff --git a/src/base58.cpp b/src/base58.cpp index c594993ea0..8d5c6f883b 100644 --- a/src/base58.cpp +++ b/src/base58.cpp @@ -235,21 +235,58 @@ bool CBitcoinAddress::Set(const CTxDestination& dest) return boost::apply_visitor(CBitcoinAddressVisitor(this), dest); } +CBitcoinAddress& CBitcoinAddress::AddBlindingKey(const CPubKey& pubkey) +{ + assert(pubkey.size() == 33); + assert(!IsBlinded()); + std::vector data = vchVersion; + data.insert(data.end(), pubkey.begin(), pubkey.end()); + data.insert(data.end(), vchData.begin(), vchData.end()); + SetData(Params().Base58Prefix(CChainParams::BLINDED_ADDRESS), &data[0], data.size()); + return *this; +} + +CPubKey CBitcoinAddress::GetBlindingKey() const +{ + assert(IsBlinded()); + CPubKey pubkey(&vchData[1], &vchData[34]); + return pubkey; +} + +bool CBitcoinAddress::IsBlinded(const CChainParams& params) const +{ + return (vchVersion == params.Base58Prefix(CChainParams::BLINDED_ADDRESS) && vchData.size() > 34); +} + bool CBitcoinAddress::IsValid() const { return IsValid(Params()); } +CBitcoinAddress CBitcoinAddress::GetUnblinded() const +{ + CBitcoinAddress subaddr; + subaddr.SetData(std::vector(&vchData[0], &vchData[1]), &vchData[34], vchData.size() - 34); + return subaddr; +} + bool CBitcoinAddress::IsValid(const CChainParams& params) const { + if (IsBlinded(params)) { + return GetUnblinded().IsValid(params); + } bool fCorrectSize = vchData.size() == 20; bool fKnownVersion = vchVersion == params.Base58Prefix(CChainParams::PUBKEY_ADDRESS) || vchVersion == params.Base58Prefix(CChainParams::SCRIPT_ADDRESS); return fCorrectSize && fKnownVersion; } + CTxDestination CBitcoinAddress::Get() const { + if (IsBlinded()) { + return GetUnblinded().Get(); + } if (!IsValid()) return CNoDestination(); uint160 id; @@ -264,6 +301,9 @@ CTxDestination CBitcoinAddress::Get() const bool CBitcoinAddress::GetKeyID(CKeyID& keyID) const { + if (IsBlinded()) { + return GetUnblinded().GetKeyID(keyID); + } if (!IsValid() || vchVersion != Params().Base58Prefix(CChainParams::PUBKEY_ADDRESS)) return false; uint160 id; @@ -274,6 +314,9 @@ bool CBitcoinAddress::GetKeyID(CKeyID& keyID) const bool CBitcoinAddress::IsScript() const { + if (IsBlinded()) { + return GetUnblinded().IsScript(); + } return IsValid() && vchVersion == Params().Base58Prefix(CChainParams::SCRIPT_ADDRESS); } diff --git a/src/base58.h b/src/base58.h index c4cb96814c..5eabe889af 100644 --- a/src/base58.h +++ b/src/base58.h @@ -101,6 +101,8 @@ public: * The data vector contains RIPEMD160(SHA256(cscript)), where cscript is the serialized redemption script. */ class CBitcoinAddress : public CBase58Data { +private: + public: bool Set(const CKeyID &id); bool Set(const CScriptID &id); @@ -116,6 +118,11 @@ public: CTxDestination Get() const; bool GetKeyID(CKeyID &keyID) const; bool IsScript() const; + + CBitcoinAddress& AddBlindingKey(const CPubKey &pubkey); + CPubKey GetBlindingKey() const; + CBitcoinAddress GetUnblinded() const; + bool IsBlinded(const CChainParams& params = Params()) const; }; /** diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index b7edbd7685..bab53b4485 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -3,6 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "base58.h" +#include "blind.h" #include "clientversion.h" #include "primitives/block.h" // for MAX_BLOCK_SIZE #include "primitives/transaction.h" @@ -72,7 +73,8 @@ static bool AppInitRawTx(int argc, char* argv[]) strUsage = _("Commands:") + "\n"; strUsage += " delin=N " + _("Delete input N from TX") + "\n"; strUsage += " delout=N " + _("Delete output N from TX") + "\n"; - strUsage += " in=TXID:VOUT[:SEQ] " + _("Add input to TX") + "\n"; + strUsage += " in=TXID:VOUT:VALUE:SEQ " + _("Add input to TX") + "\n"; + strUsage += " blind=B1::B3:B4:... " + _("Blind transaction outputs") + "\n"; strUsage += " locktime=N " + _("Set TX lock time to N") + "\n"; strUsage += " nversion=N " + _("Set TX version to N") + "\n"; strUsage += " outaddr=VALUE:ADDRESS " + _("Add address-based output to TX") + "\n"; @@ -201,9 +203,18 @@ static void MutateTxAddInput(CMutableTransaction& tx, const string& strInput) // Remove txid string strVout = strInput.substr(pos + 1, string::npos); + // extract and validate VALUE + pos = strVout.find(':'); + if (pos == string::npos) + throw runtime_error("TX input missing separator"); + string strValue = strVout.substr(0, pos); + CAmount value; + if (!ParseMoney(strValue, value)) + throw runtime_error("invalid TX output value"); + // extract and validate sequence number uint32_t nSequence = ~(uint32_t)0; - pos = strVout.find(':'); + pos = strVout.find(':', pos + 1); if (pos != string::npos) { if ((pos == 0) || (pos == (strVout.size() - 1))) throw runtime_error("empty TX input field"); @@ -224,6 +235,8 @@ static void MutateTxAddInput(CMutableTransaction& tx, const string& strInput) } nSequence = (uint32_t)nSeq; + } else { + throw runtime_error("invalid TX input: sequence missing"); } // extract and validate vout @@ -234,6 +247,7 @@ static void MutateTxAddInput(CMutableTransaction& tx, const string& strInput) // append to transaction input list CTxIn txin(txid, vout, CScript(), nSequence); tx.vin.push_back(txin); + tx.nTxFee += value; } static void MutateTxAddOutAddr(CMutableTransaction& tx, const string& strInput) @@ -262,9 +276,57 @@ static void MutateTxAddOutAddr(CMutableTransaction& tx, const string& strInput) // construct TxOut, append to transaction output list CTxOut txout(value, scriptPubKey); + if (addr.IsBlinded()) { + CPubKey pubkey = addr.GetBlindingKey(); + txout.nValue.vchNonceCommitment = std::vector(pubkey.begin(), pubkey.end()); + } tx.vout.push_back(txout); } +static void MutateTxBlind(CMutableTransaction& tx, const string& strInput) +{ + std::vector input_blinding_factors; + boost::split(input_blinding_factors, strInput, boost::is_any_of(":")); + + if (input_blinding_factors.size() != tx.vin.size()) + throw runtime_error("One input blinding factor required per transaction input"); + + bool fBlindedIns = false; + bool fBlindedOuts = false; + 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]); + if (blind.size() == 0) { + input_blinds.push_back(blind); + } else if (blind.size() == 32) { + input_blinds.push_back(blind); + fBlindedIns = true; + } + } + for (size_t nOut = 0; nOut < tx.vout.size(); nOut++) { + if (!tx.vout[nOut].nValue.IsAmount()) + throw runtime_error("Invalid parameter: transaction outputs must be unblinded"); + if (tx.vout[nOut].nValue.vchNonceCommitment.size() == 0) { + output_pubkeys.push_back(CPubKey()); + } else { + CPubKey pubkey(tx.vout[nOut].nValue.vchNonceCommitment); + if (!pubkey.IsValid()) { + throw runtime_error("Invalid parameter: invalid confidentiality public key given"); + } + output_pubkeys.push_back(pubkey); + fBlindedOuts = true; + } + output_blinds.push_back(std::vector(0, 0)); + } + + if (fBlindedIns && !fBlindedOuts) { + throw runtime_error("Confidential inputs without confidential outputs"); + } + BlindOutputs(input_blinds, output_blinds, output_pubkeys, tx); +} + static void MutateTxAddOutScript(CMutableTransaction& tx, const string& strInput) { // separate VALUE:SCRIPT in string @@ -286,10 +348,13 @@ static void MutateTxAddOutScript(CMutableTransaction& tx, const string& strInput // construct TxOut, append to transaction output list CTxOut txout(value, scriptPubKey); tx.vout.push_back(txout); + tx.nTxFee -= value; } static void MutateTxDelInput(CMutableTransaction& tx, const string& strInIdx) { + // TODO: reduce nTxFee + // parse requested deletion index int inIdx = atoi(strInIdx); if (inIdx < 0 || inIdx >= (int)tx.vin.size()) { @@ -303,6 +368,8 @@ static void MutateTxDelInput(CMutableTransaction& tx, const string& strInIdx) static void MutateTxDelOutput(CMutableTransaction& tx, const string& strOutIdx) { + // TODO: increase nTxFee + // parse requested deletion index int outIdx = atoi(strOutIdx); if (outIdx < 0 || outIdx >= (int)tx.vout.size()) { @@ -552,8 +619,8 @@ static void MutateTxWithdrawSign(CMutableTransaction& tx, const string& flagStr) class Secp256k1Init { public: - Secp256k1Init() { ECC_Start(); } - ~Secp256k1Init() { ECC_Stop(); } + Secp256k1Init() { ECC_Start(); ECC_Blinding_Start(); } + ~Secp256k1Init() { ECC_Stop(); ECC_Blinding_Stop(); } }; static void MutateTx(CMutableTransaction& tx, const string& command, @@ -581,6 +648,9 @@ static void MutateTx(CMutableTransaction& tx, const string& command, else if (command == "sign") { if (!ecc) { ecc.reset(new Secp256k1Init()); } MutateTxSign(tx, commandVal); + } else if (command == "blind") { + if (!ecc) { ecc.reset(new Secp256k1Init()); } + MutateTxBlind(tx, commandVal); } else if (command == "withdrawsign") MutateTxWithdrawSign(tx, commandVal); diff --git a/src/blind.cpp b/src/blind.cpp new file mode 100644 index 0000000000..5de469a72f --- /dev/null +++ b/src/blind.cpp @@ -0,0 +1,137 @@ +#include "blind.h" + +#include "hash.h" +#include "primitives/transaction.h" +#include "random.h" +#include "util.h" + +#include + +static secp256k1_context_t* secp256k1_context = NULL; + +void ECC_Blinding_Start() { + assert(secp256k1_context == NULL); + + secp256k1_context_t *ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_COMMIT | SECP256K1_CONTEXT_RANGEPROOF); + assert(ctx != NULL); + + secp256k1_context = ctx; +} + +void ECC_Blinding_Stop() { + secp256k1_context_t *ctx = secp256k1_context; + secp256k1_context = NULL; + + if (ctx) { + secp256k1_context_destroy(ctx); + } +} + +const secp256k1_context_t* ECC_Blinding_Context() { + return secp256k1_context; +} + +int UnblindOutput(const CKey &key, const CTxOut& txout, CAmount& amount_out, std::vector& blinding_factor_out) +{ + if (txout.nValue.IsAmount()) { + amount_out = txout.nValue.GetAmount(); + blinding_factor_out.resize(0); + return -1; + } + CPubKey ephemeral_key(txout.nValue.vchNonceCommitment); + if (!ephemeral_key.IsValid()) { + return 0; + } + CPubKey nonce_key = key.ECDH(ephemeral_key); + unsigned char nonce[32]; + CHash256().Write(nonce_key.begin(), nonce_key.size()).Finalize(nonce); + 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_context, &blinding_factor_out[0], &amount, msg, &msg_size, nonce, &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 + amount_out = (CAmount)amount; + return res ? 1 : 0; +} + +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()); + assert(tx.vin.size() == input_blinding_factors.size()); + + std::vector blindptrs; + blindptrs.reserve(tx.vout.size() + tx.vin.size()); + + int nBlindsIn = 0; + for (size_t nIn = 0; nIn < tx.vin.size(); nIn++) { + if (input_blinding_factors[nIn].size() != 0) { + assert(input_blinding_factors[nIn].size() == 32); + blindptrs.push_back(&input_blinding_factors[nIn][0]); + nBlindsIn++; + } + } + + 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]); + nBlindsOut++; + } else { + if (output_pubkeys[nOut].IsValid()) { + nToBlind++; + } + } + } + + if (nBlindsIn != 0) { + assert((nBlindsOut + nToBlind) != 0); + } + + int nBlinded = 0; + unsigned char blind[nToBlind][32]; + + for (size_t nOut = 0; nOut < tx.vout.size(); nOut++) { + if (tx.vout[nOut].nValue.IsAmount() && output_pubkeys[nOut].IsValid()) { + assert(output_pubkeys[nOut].IsValid()); + if (nBlinded + 1 == nToBlind) { + // Last to-be-blinded value: compute from all other blinding factors. + assert(secp256k1_pedersen_blind_sum(ECC_Blinding_Context(), &blind[nBlinded][0], &blindptrs[0], nBlindsOut + nBlindsIn, nBlindsIn)); + blindptrs.push_back(&blind[nBlinded++][0]); + } else { + GetRandBytes(&blind[nBlinded][0], 32); + blindptrs.push_back(&blind[nBlinded++][0]); + } + nBlindsOut++; + // Create blinded value + CTxOutValue& value = tx.vout[nOut].nValue; + CAmount amount = value.GetAmount(); + assert(secp256k1_pedersen_commit(ECC_Blinding_Context(), &value.vchCommitment[0], (unsigned char*)blindptrs.back(), amount)); + // Generate ephemeral key for ECDH nonce generation + CKey ephemeral_key; + ephemeral_key.MakeNewKey(true); + CPubKey ephemeral_pubkey = ephemeral_key.GetPubKey(); + value.vchNonceCommitment.resize(33); + memcpy(&value.vchNonceCommitment[0], &ephemeral_pubkey[0], 33); + // Generate nonce + CPubKey nonce_key = ephemeral_key.ECDH(output_pubkeys[nOut]); + unsigned char nonce[32]; + CHash256().Write(nonce_key.begin(), nonce_key.size()).Finalize(nonce); + // Create range proof + int nRangeProofLen = 5134; + // TODO: smarter min_value selection + value.vchRangeproof.resize(nRangeProofLen); + int res = secp256k1_rangeproof_sign(ECC_Blinding_Context(), &value.vchRangeproof[0], &nRangeProofLen, 0, &value.vchCommitment[0], blindptrs.back(), nonce, std::min(std::max((int)GetArg("-ct_exponent", 0), -1),18), std::min(std::max((int)GetArg("-ct_bits", 32), 1), 51), amount); + value.vchRangeproof.resize(nRangeProofLen); + // TODO: do something smarter here + assert(res); + } + } +} diff --git a/src/blind.h b/src/blind.h new file mode 100644 index 0000000000..c44677ac11 --- /dev/null +++ b/src/blind.h @@ -0,0 +1,14 @@ +#ifndef BITCOIN_BLIND_H_ +#define BITCOIN_BLIND_H_ 1 + +#include "key.h" +#include "pubkey.h" +#include "primitives/transaction.h" + +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); + +#endif diff --git a/src/chainparams.cpp b/src/chainparams.cpp index d0e8e5dcd4..bb5d5519c2 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -140,6 +140,7 @@ public: base58Prefixes[PUBKEY_ADDRESS] = list_of(0); base58Prefixes[SCRIPT_ADDRESS] = list_of(5); + base58Prefixes[BLINDED_ADDRESS]= list_of(10); base58Prefixes[SECRET_KEY] = list_of(128); base58Prefixes[EXT_PUBLIC_KEY] = list_of(0x04)(0x88)(0xB2)(0x1E); base58Prefixes[EXT_SECRET_KEY] = list_of(0x04)(0x88)(0xAD)(0xE4); @@ -200,6 +201,7 @@ public: base58Prefixes[PUBKEY_ADDRESS] = list_of(111); base58Prefixes[SCRIPT_ADDRESS] = list_of(196); + base58Prefixes[BLINDED_ADDRESS]= list_of(25); base58Prefixes[SECRET_KEY] = list_of(239); base58Prefixes[EXT_PUBLIC_KEY] = list_of(0x04)(0x35)(0x87)(0xCF); base58Prefixes[EXT_SECRET_KEY] = list_of(0x04)(0x35)(0x83)(0x94); diff --git a/src/chainparams.h b/src/chainparams.h index ef20a88558..34206100a2 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -34,6 +34,7 @@ public: enum Base58Type { PUBKEY_ADDRESS, SCRIPT_ADDRESS, + BLINDED_ADDRESS, SECRET_KEY, EXT_PUBLIC_KEY, EXT_SECRET_KEY, diff --git a/src/init.cpp b/src/init.cpp index 902c33c0c9..a80ba01cd9 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -11,6 +11,7 @@ #include "addrman.h" #include "amount.h" +#include "blind.h" #include "checkpoints.h" #include "compat/sanity.h" #include "key.h" @@ -196,6 +197,7 @@ void Shutdown() delete pwalletMain; pwalletMain = NULL; #endif + ECC_Blinding_Stop(); ECC_Stop(); ECC_Verify_Stop(); LogPrintf("%s: done\n", __func__); @@ -772,6 +774,7 @@ bool AppInit2(boost::thread_group& threadGroup) // ********************************************************* Step 4: application initialization: dir lock, daemonize, pidfile, debug log // Initialize elliptic curve code + ECC_Blinding_Start(); ECC_Verify_Start(); ECC_Start(); diff --git a/src/key.cpp b/src/key.cpp index 507361d190..f60c6e1429 100644 --- a/src/key.cpp +++ b/src/key.cpp @@ -58,6 +58,17 @@ CPubKey CKey::GetPubKey() const { return result; } +CPubKey CKey::ECDH(const CPubKey& pubkey) const { + assert(fValid); + CPubKey result = pubkey; + int clen = result.size(); + int ret = secp256k1_point_multiply((unsigned char*)result.begin(), &clen, begin()); + assert((int)result.size() == clen); + assert(ret); + assert(result.IsValid()); + return result; +} + bool CKey::Sign(const uint256 &hash, std::vector& vchSig, uint32_t test_case) const { if (!fValid) return false; diff --git a/src/key.h b/src/key.h index 81feb58c21..080fce9a15 100644 --- a/src/key.h +++ b/src/key.h @@ -122,6 +122,11 @@ public: */ CPubKey GetPubKey() const; + /** + * Compute the ECDH exchange result using this private key and another public key. + */ + CPubKey ECDH(const CPubKey& pubkey) const; + /** * Create a DER-serialized signature. * The test_case parameter tweaks the deterministic nonce. diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index 8a604bc6b3..dc561fb74c 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -185,12 +185,12 @@ unsigned int CTransaction::CalculateModifiedSize(unsigned int nTxSize) const std::string CTransaction::ToString() const { std::string str; - str += strprintf("CTransaction(hash=%s, ver=%d, vin.size=%u, vout.size=%u, nLockTime=%u)\n", + str += strprintf("CTransaction(hash=%s, ver=%d, vin.size=%u, vout.size=%u, nLockTime=%u, fee=%u)\n", GetHash().ToString().substr(0,10), nVersion, vin.size(), vout.size(), - nLockTime); + nLockTime, nTxFee); for (unsigned int i = 0; i < vin.size(); i++) str += " " + vin[i].ToString() + "\n"; for (unsigned int i = 0; i < vout.size(); i++) diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index 19c5776b92..4cdd61cae1 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -506,10 +506,10 @@ void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) nQuantity++; // Amount - nAmount += out.tx->vout[out.i].nValue; + nAmount += out.tx->GetValueOut(out.i); // Priority - dPriorityInputs += (double)out.tx->vout[out.i].nValue * (out.nDepth+1); + dPriorityInputs += (double)COIN * (out.nDepth+1); // Bytes CTxDestination address; @@ -708,7 +708,7 @@ void CoinControlDialog::updateView() BOOST_FOREACH(const COutput& out, coins.second) { int nInputSize = 0; - nSum += out.tx->vout[out.i].nValue; + nSum += out.tx->GetValueOut(out.i); nChildren++; QTreeWidgetItem *itemOutput; @@ -750,8 +750,8 @@ void CoinControlDialog::updateView() } // amount - itemOutput->setText(COLUMN_AMOUNT, BitcoinUnits::format(nDisplayUnit, out.tx->vout[out.i].nValue)); - itemOutput->setText(COLUMN_AMOUNT_INT64, strPad(QString::number(out.tx->vout[out.i].nValue), 15, " ")); // padding so that sorting works correctly + itemOutput->setText(COLUMN_AMOUNT, BitcoinUnits::format(nDisplayUnit, out.tx->GetValueOut(out.i))); + itemOutput->setText(COLUMN_AMOUNT_INT64, strPad(QString::number(out.tx->GetValueOut(out.i)), 15, " ")); // padding so that sorting works correctly // date itemOutput->setText(COLUMN_DATE, GUIUtil::dateTimeStr(out.tx->GetTxTime())); @@ -761,10 +761,10 @@ void CoinControlDialog::updateView() itemOutput->setText(COLUMN_CONFIRMATIONS, strPad(QString::number(out.nDepth), 8, " ")); // priority - double dPriority = ((double)out.tx->vout[out.i].nValue / (nInputSize + 78)) * (out.nDepth+1); // 78 = 2 * 34 + 10 + double dPriority = ((double)COIN / (nInputSize + 78)) * (out.nDepth+1); // 78 = 2 * 34 + 10 itemOutput->setText(COLUMN_PRIORITY, CoinControlDialog::getPriorityLabel(dPriority, mempoolEstimatePriority)); itemOutput->setText(COLUMN_PRIORITY_INT64, strPad(QString::number((int64_t)dPriority), 20, " ")); - dPrioritySum += (double)out.tx->vout[out.i].nValue * (out.nDepth+1); + dPrioritySum += (double)COIN * (out.nDepth+1); nInputSum += nInputSize; // transaction hash diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp index 3bab101b99..9abc7988ef 100644 --- a/src/qt/transactiondesc.cpp +++ b/src/qt/transactiondesc.cpp @@ -133,9 +133,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco // // Coinbase // - CAmount nUnmatured = 0; - BOOST_FOREACH(const CTxOut& txout, wtx.vout) - nUnmatured += wallet->GetCredit(txout, ISMINE_ALL); + CAmount nUnmatured = wallet->GetCredit(wtx, ISMINE_ALL); strHTML += "" + tr("Credit") + ": "; if (wtx.IsInMainChain()) strHTML += BitcoinUnits::formatHtmlWithUnit(unit, nUnmatured)+ " (" + tr("matures in %n more block(s)", "", wtx.GetBlocksToMaturity()) + ")"; @@ -174,8 +172,9 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco // // Debit // - BOOST_FOREACH(const CTxOut& txout, wtx.vout) + for (unsigned int i = 0; i < wtx.vout.size(); i++) { + const CTxOut& txout = wtx.vout[i]; // Ignore change isminetype toSelf = wallet->IsMine(txout); if ((toSelf == ISMINE_SPENDABLE) && (fAllFromMe == ISMINE_SPENDABLE)) @@ -199,9 +198,9 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco } } - strHTML += "" + tr("Debit") + ": " + BitcoinUnits::formatHtmlWithUnit(unit, -txout.nValue) + "
"; + strHTML += "" + tr("Debit") + ": " + BitcoinUnits::formatHtmlWithUnit(unit, -wtx.GetValueOut(i)) + "
"; if(toSelf) - strHTML += "" + tr("Credit") + ": " + BitcoinUnits::formatHtmlWithUnit(unit, txout.nValue) + "
"; + strHTML += "" + tr("Credit") + ": " + BitcoinUnits::formatHtmlWithUnit(unit, wtx.GetValueOut(i)) + "
"; } if (fAllToMe) @@ -213,9 +212,8 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco strHTML += "" + tr("Total credit") + ": " + BitcoinUnits::formatHtmlWithUnit(unit, nValue) + "
"; } - CAmount nTxFee = nDebit - wtx.GetValueOut(); - if (nTxFee > 0) - strHTML += "" + tr("Transaction fee") + ": " + BitcoinUnits::formatHtmlWithUnit(unit, -nTxFee) + "
"; + if (wtx.nTxFee > 0) + strHTML += "" + tr("Transaction fee") + ": " + BitcoinUnits::formatHtmlWithUnit(unit, -wtx.nTxFee) + "
"; } else { @@ -225,9 +223,9 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco BOOST_FOREACH(const CTxIn& txin, wtx.vin) if (wallet->IsMine(txin)) strHTML += "" + tr("Debit") + ": " + BitcoinUnits::formatHtmlWithUnit(unit, -wallet->GetDebit(txin, ISMINE_ALL)) + "
"; - BOOST_FOREACH(const CTxOut& txout, wtx.vout) - if (wallet->IsMine(txout)) - strHTML += "" + tr("Credit") + ": " + BitcoinUnits::formatHtmlWithUnit(unit, wallet->GetCredit(txout, ISMINE_ALL)) + "
"; + for (unsigned int i = 0; i < wtx.vout.size(); i++) + if (wallet->IsMine(wtx.vout[i]) & ISMINE_ALL) + strHTML += "" + tr("Credit") + ": " + BitcoinUnits::formatHtmlWithUnit(unit, wtx.GetValueOut(i)) + "
"; } } @@ -278,9 +276,9 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco BOOST_FOREACH(const CTxIn& txin, wtx.vin) if(wallet->IsMine(txin)) strHTML += "" + tr("Debit") + ": " + BitcoinUnits::formatHtmlWithUnit(unit, -wallet->GetDebit(txin, ISMINE_ALL)) + "
"; - BOOST_FOREACH(const CTxOut& txout, wtx.vout) - if(wallet->IsMine(txout)) - strHTML += "" + tr("Credit") + ": " + BitcoinUnits::formatHtmlWithUnit(unit, wallet->GetCredit(txout, ISMINE_ALL)) + "
"; + for (unsigned int i = 0; i < wtx.vout.size(); i++) + if(wallet->IsMine(wtx.vout[i]) & ISMINE_ALL) + strHTML += "" + tr("Credit") + ": " + BitcoinUnits::formatHtmlWithUnit(unit, wtx.GetCredit(i)) + "
"; strHTML += "
" + tr("Transaction") + ":
"; strHTML += GUIUtil::HtmlEscape(wtx.ToString(), true); @@ -306,7 +304,6 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco strHTML += GUIUtil::HtmlEscape(wallet->mapAddressBook[address].name) + " "; strHTML += QString::fromStdString(CBitcoinAddress(address).ToString()); } - strHTML = strHTML + " " + tr("Amount") + "=" + BitcoinUnits::formatHtmlWithUnit(unit, vout.nValue); strHTML = strHTML + " IsMine=" + (wallet->IsMine(vout) & ISMINE_SPENDABLE ? tr("true") : tr("false")) + ""; strHTML = strHTML + " IsWatchOnly=" + (wallet->IsMine(vout) & ISMINE_WATCH_ONLY ? tr("true") : tr("false")) + ""; } diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index 587b756818..2cc35ff422 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -43,15 +43,16 @@ QList TransactionRecord::decomposeTransaction(const CWallet * // // Credit // - BOOST_FOREACH(const CTxOut& txout, wtx.vout) + for (unsigned int i = 0; i < wtx.vout.size(); i++) { + const CTxOut& txout = wtx.vout[i]; isminetype mine = wallet->IsMine(txout); if(mine) { TransactionRecord sub(hash, nTime); CTxDestination address; sub.idx = parts.size(); // sequence number - sub.credit = txout.nValue; + sub.credit = wtx.GetValueOut(i); sub.involvesWatchAddress = mine == ISMINE_WATCH_ONLY; if (ExtractDestination(txout.scriptPubKey, address) && IsMine(*wallet, address)) { @@ -108,7 +109,7 @@ QList TransactionRecord::decomposeTransaction(const CWallet * // // Debit // - CAmount nTxFee = nDebit - wtx.GetValueOut(); + CAmount nTxFee = wtx.nTxFee; for (unsigned int nOut = 0; nOut < wtx.vout.size(); nOut++) { @@ -138,7 +139,7 @@ QList TransactionRecord::decomposeTransaction(const CWallet * sub.address = mapValue["to"]; } - CAmount nValue = txout.nValue; + CAmount nValue = wtx.GetValueOut(nOut); /* Add fee to first output */ if (nTxFee > 0) { diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 75172d6340..4373f6a85c 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -63,7 +63,7 @@ CAmount WalletModel::getBalance(const CCoinControl *coinControl) const wallet->AvailableCoins(vCoins, true, coinControl); BOOST_FOREACH(const COutput& out, vCoins) if(out.fSpendable) - nBalance += out.tx->vout[out.i].nValue; + nBalance += out.tx->GetValueOut(out.i); return nBalance; } @@ -192,7 +192,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact { CAmount total = 0; QList recipients = transaction.getRecipients(); - std::vector > vecSend; + std::vector vecSend; if(recipients.empty()) { @@ -216,7 +216,7 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact subtotal += out.amount(); const unsigned char* scriptStr = (const unsigned char*)out.script().data(); CScript scriptPubKey(scriptStr, scriptStr+out.script().size()); - vecSend.push_back(std::pair(scriptPubKey, out.amount())); + vecSend.push_back(CSend(scriptPubKey, out.amount())); } if (subtotal <= 0) { @@ -237,8 +237,13 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact setAddress.insert(rcp.address); ++nAddresses; - CScript scriptPubKey = GetScriptForDestination(CBitcoinAddress(rcp.address.toStdString()).Get()); - vecSend.push_back(std::pair(scriptPubKey, rcp.amount)); + CBitcoinAddress addr(rcp.address.toStdString()); + CScript scriptPubKey = GetScriptForDestination(addr.Get()); + CPubKey confidentiality_pubkey; + if (addr.IsBlinded()) { + confidentiality_pubkey = addr.GetBlindingKey(); + } + vecSend.push_back(CSend(scriptPubKey, rcp.amount, confidentiality_pubkey)); total += rcp.amount; } diff --git a/src/rpcmisc.cpp b/src/rpcmisc.cpp index 5842f6a2da..f6ee836fa9 100644 --- a/src/rpcmisc.cpp +++ b/src/rpcmisc.cpp @@ -167,6 +167,9 @@ Value validateaddress(const Array& 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) 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" "}\n" "\nExamples:\n" + HelpExampleCli("validateaddress", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"") @@ -183,9 +186,23 @@ Value validateaddress(const Array& params, bool fHelp) CTxDestination dest = address.Get(); string currentAddress = address.ToString(); ret.push_back(Pair("address", currentAddress)); + 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 (address.IsBlinded() && address.GetBlindingKey() != pwalletMain->blinding_pubkey) { + 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())); + } if (mine != ISMINE_NO) { ret.push_back(Pair("iswatchonly", (mine & ISMINE_WATCH_ONLY) ? true: false)); Object detail = boost::apply_visitor(DescribeAddressVisitor(mine), dest); diff --git a/src/rpcrawtransaction.cpp b/src/rpcrawtransaction.cpp index 5832727303..68e9528044 100644 --- a/src/rpcrawtransaction.cpp +++ b/src/rpcrawtransaction.cpp @@ -4,6 +4,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "base58.h" +#include "blind.h" #include "primitives/transaction.h" #include "core_io.h" #include "init.h" @@ -25,6 +26,7 @@ #include #include "json/json_spirit_utils.h" #include "json/json_spirit_value.h" +#include using namespace boost; using namespace boost::assign; @@ -60,6 +62,7 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, Object& entry) entry.push_back(Pair("txid", tx.GetHash().GetHex())); entry.push_back(Pair("version", tx.nVersion)); entry.push_back(Pair("locktime", (int64_t)tx.nLockTime)); + entry.push_back(Pair("fee", ValueFromAmount(tx.nTxFee))); Array vin; BOOST_FOREACH(const CTxIn& txin, tx.vin) { Object in; @@ -81,9 +84,24 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, Object& entry) for (unsigned int i = 0; i < tx.vout.size(); i++) { const CTxOut& txout = tx.vout[i]; Object out; - if (txout.nValue.IsAmount()) + if (txout.nValue.IsAmount()) { out.push_back(Pair("value", ValueFromAmount(txout.nValue.GetAmount()))); - // TODO: Non-Amount values + } else { + int exp; + int mantissa; + uint64_t minv; + uint64_t maxv; + if (secp256k1_rangeproof_info(NULL, &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)); + } + } out.push_back(Pair("n", (int64_t)i)); Object o; ScriptPubKeyToJSON(txout.scriptPubKey, o, true); @@ -333,7 +351,9 @@ Value listunspent(const Array& params, bool fHelp) " \"account\" : \"account\", (string) The associated account, or \"\" for the default account\n" " \"scriptPubKey\" : \"key\", (string) the script key\n" " \"amount\" : x.xxx, (numeric) the transaction amount in btc\n" - " \"confirmations\" : n (numeric) The number of confirmations\n" + " \"serValue\" : \"hex\", (string) the output's value commitment\n" + " \"confirmations\" : n, (numeric) The number of confirmations\n" + " \"blinder\" : \"blind\" (string) The blinding factor used for a confidential output (or \"\")\n" " }\n" " ,...\n" "]\n" @@ -384,14 +404,18 @@ Value listunspent(const Array& params, bool fHelp) continue; } - CAmount nValue = out.tx->vout[out.i].nValue; + CAmount nValue = out.tx->GetValueOut(out.i); const CScript& pk = out.tx->vout[out.i].scriptPubKey; Object entry; entry.push_back(Pair("txid", out.tx->GetHash().GetHex())); entry.push_back(Pair("vout", out.i)); CTxDestination address; if (ExtractDestination(out.tx->vout[out.i].scriptPubKey, address)) { - entry.push_back(Pair("address", CBitcoinAddress(address).ToString())); + CBitcoinAddress addr(address); + if (out.tx->GetBlindingFactor(out.i).size() > 0) { + addr.AddBlindingKey(pwalletMain->blinding_pubkey); + } + entry.push_back(Pair("address", addr.ToString())); if (pwalletMain->mapAddressBook.count(address)) entry.push_back(Pair("account", pwalletMain->mapAddressBook[address].name)); } @@ -405,7 +429,11 @@ Value listunspent(const Array& params, bool fHelp) entry.push_back(Pair("redeemScript", HexStr(redeemScript.begin(), redeemScript.end()))); } } + CDataStream ssValue(SER_NETWORK, PROTOCOL_VERSION); + ssValue << nValue; + entry.push_back(Pair("serValue", HexStr(ssValue.begin(), ssValue.end()))); entry.push_back(Pair("amount",ValueFromAmount(nValue))); + entry.push_back(Pair("blinder",HexStr(out.tx->GetBlindingFactor(out.i)))); entry.push_back(Pair("confirmations",out.nDepth)); entry.push_back(Pair("spendable", out.fSpendable)); results.push_back(entry); @@ -431,6 +459,7 @@ Value createrawtransaction(const Array& 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" " }\n" " ,...\n" " ]\n" @@ -455,6 +484,8 @@ Value createrawtransaction(const Array& params, bool fHelp) CMutableTransaction rawTx; + CAmount inputValue = 0; + BOOST_FOREACH(const Value& input, inputs) { const Object& o = input.get_obj(); @@ -467,10 +498,15 @@ Value createrawtransaction(const Array& params, bool fHelp) if (nOutput < 0) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout must be positive"); + const Value& vout_value = find_value(o, "nValue"); + inputValue += AmountFromValue(vout_value); + CTxIn in(COutPoint(txid, nOutput)); rawTx.vin.push_back(in); } + CAmount outputValue = 0; + set setAddress; BOOST_FOREACH(const Pair& s, sendTo) { CBitcoinAddress address(s.name_); @@ -483,14 +519,171 @@ Value createrawtransaction(const Array& params, bool fHelp) CScript scriptPubKey = GetScriptForDestination(address.Get()); CAmount nAmount = AmountFromValue(s.value_); + outputValue += nAmount; 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); } + rawTx.nTxFee = inputValue - outputValue; + return EncodeHexTx(rawTx); } +Value rawblindrawtransaction(const Array& 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, list_of(str_type)(array_type)); + } else { + RPCTypeCheck(params, list_of(str_type)(array_type)(str_type)); + } + + CMutableTransaction rawTx; + + 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"); + } + + Array 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 nOut = 0; nOut < tx.vout.size(); nOut++) { + if (!tx.vout[nOut].nValue.IsAmount()) + throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter: transaction outputs must be unblinded")); + if (tx.vout[nOut].nValue.vchNonceCommitment.size() == 0) { + output_pubkeys.push_back(CPubKey()); + } 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)); + } + + BlindOutputs(input_blinds, output_blinds, output_pubkeys, tx); + + return EncodeHexTx(tx); +} + +#ifdef ENABLE_WALLET +Value blindrawtransaction(const Array& 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, list_of(str_type)); + } else { + RPCTypeCheck(params, list_of(str_type)(str_type)); + } + + CMutableTransaction rawTx; + + 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)); + } + + for (size_t nOut = 0; nOut < tx.vout.size(); nOut++) { + if (!tx.vout[nOut].nValue.IsAmount()) { + std::vector blinding_factor; + CAmount amount; + if (UnblindOutput(pwalletMain->blinding_key, tx.vout[nOut], amount, blinding_factor) != 0) { + output_blinds.push_back(blinding_factor); + output_pubkeys.push_back(CPubKey()); + } else { + throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter: transaction outputs must be unblinded or to wallet")); + } + } else if (tx.vout[nOut].nValue.vchNonceCommitment.size() == 0) { + output_pubkeys.push_back(CPubKey()); + output_blinds.push_back(std::vector(0, 0)); + } 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)); + } + } + + BlindOutputs(input_blinds, output_blinds, output_pubkeys, tx); + + return EncodeHexTx(tx); +} +#endif + Value decoderawtransaction(const Array& params, bool fHelp) { if (fHelp || params.size() != 1) diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index 3d6239458a..9c0562f161 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -297,7 +297,9 @@ static const CRPCCommand vRPCCommands[] = { "rawtransactions", "getrawtransaction", &getrawtransaction, true, false, false }, { "rawtransactions", "sendrawtransaction", &sendrawtransaction, false, false, false }, { "rawtransactions", "signrawtransaction", &signrawtransaction, false, false, false }, /* uses wallet if enabled */ + { "rawtransactions", "rawblindrawtransaction", &rawblindrawtransaction, false, false, false }, #ifdef ENABLE_WALLET + { "rawtransactions", "blindrawtransaction", &blindrawtransaction, true, false, false }, { "rawtransactions", "fundrawtransaction", &fundrawtransaction, false, false, true }, #endif diff --git a/src/rpcserver.h b/src/rpcserver.h index 8dd979b43f..7052378651 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -214,6 +214,8 @@ extern json_spirit::Value signrawtransaction(const json_spirit::Array& params, b extern json_spirit::Value sendrawtransaction(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value gettxoutproof(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value verifytxoutproof(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value blindrawtransaction(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value rawblindrawtransaction(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value getblockcount(const json_spirit::Array& params, bool fHelp); // in rpcblockchain.cpp extern json_spirit::Value getbestblockhash(const json_spirit::Array& params, bool fHelp); diff --git a/src/rpcwallet.cpp b/src/rpcwallet.cpp index 3adb69f051..ff54548b83 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).ToString(); + return CBitcoinAddress(keyID).AddBlindingKey(pwalletMain->blinding_pubkey).ToString(); } @@ -148,7 +148,7 @@ CBitcoinAddress GetAccountAddress(string strAccount, bool bForceNew=false) walletdb.WriteAccount(strAccount, account); } - return CBitcoinAddress(account.vchPubKey.GetID()); + return CBitcoinAddress(account.vchPubKey.GetID()).AddBlindingKey(pwalletMain->blinding_pubkey); } 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).ToString(); + return CBitcoinAddress(keyID).AddBlindingKey(pwalletMain->blinding_pubkey).ToString(); } @@ -310,7 +310,7 @@ Value getaddressesbyaccount(const Array& params, bool fHelp) return ret; } -void SendMoney(const CTxDestination &address, CAmount nValue, CWalletTx& wtxNew) +void SendMoney(const CTxDestination &address, CAmount nValue, const CPubKey &confidentiality_key, CWalletTx& wtxNew) { // Check amount if (nValue <= 0) @@ -333,7 +333,7 @@ void SendMoney(const CTxDestination &address, CAmount nValue, CWalletTx& wtxNew) // Create and send the transaction CReserveKey reservekey(pwalletMain); CAmount nFeeRequired; - if (!pwalletMain->CreateTransaction(scriptPubKey, nValue, wtxNew, reservekey, nFeeRequired, strError)) + if (!pwalletMain->CreateTransaction(scriptPubKey, nValue, confidentiality_key, wtxNew, reservekey, nFeeRequired, strError)) { if (nValue + nFeeRequired > pwalletMain->GetBalance()) strError = strprintf("Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds!", FormatMoney(nFeeRequired)); @@ -373,6 +373,10 @@ Value sendtoaddress(const Array& params, bool fHelp) // Amount CAmount nAmount = AmountFromValue(params[1]); + CPubKey confidentiality_pubkey; + if (address.IsBlinded()) { + confidentiality_pubkey = address.GetBlindingKey(); + } // Wallet comments CWalletTx wtx; @@ -383,7 +387,7 @@ Value sendtoaddress(const Array& params, bool fHelp) EnsureWalletIsUnlocked(); - SendMoney(address.Get(), nAmount, wtx); + SendMoney(address.Get(), nAmount, confidentiality_pubkey, wtx); return wtx.GetHash().GetHex(); } @@ -421,12 +425,14 @@ Value listaddressgroupings(const Array& params, bool fHelp) BOOST_FOREACH(CTxDestination address, grouping) { Array addressInfo; - addressInfo.push_back(CBitcoinAddress(address).ToString()); + CBitcoinAddress addr(address); + addr.AddBlindingKey(pwalletMain->blinding_pubkey); + addressInfo.push_back(addr.ToString()); addressInfo.push_back(ValueFromAmount(balances[address])); { LOCK(pwalletMain->cs_wallet); - 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); } @@ -515,6 +521,8 @@ 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; @@ -529,10 +537,10 @@ Value getreceivedbyaddress(const Array& params, bool fHelp) if (wtx.IsCoinBase() || CheckLockTime(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); @@ -578,12 +586,12 @@ Value getreceivedbyaccount(const Array& params, bool fHelp) if (wtx.IsCoinBase() || CheckLockTime(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); } } @@ -809,6 +817,10 @@ Value sendfrom(const Array& params, bool fHelp) int nMinDepth = 1; if (params.size() > 3) nMinDepth = params[3].get_int(); + CPubKey confidentiality_pubkey; + if (address.IsBlinded()) { + confidentiality_pubkey = address.GetBlindingKey(); + } CWalletTx wtx; wtx.strFromAccount = strAccount; @@ -824,7 +836,7 @@ Value sendfrom(const Array& params, bool fHelp) if (nAmount > nBalance) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Account has insufficient funds"); - SendMoney(address.Get(), nAmount, wtx); + SendMoney(address.Get(), nAmount, confidentiality_pubkey, wtx); return wtx.GetHash().GetHex(); } @@ -870,7 +882,7 @@ Value sendmany(const Array& params, bool fHelp) wtx.mapValue["comment"] = params[3].get_str(); set setAddress; - vector > vecSend; + vector vecSend; CAmount totalAmount = 0; BOOST_FOREACH(const Pair& s, sendTo) @@ -886,8 +898,12 @@ Value sendmany(const Array& params, bool fHelp) CScript scriptPubKey = GetScriptForDestination(address.Get()); CAmount nAmount = AmountFromValue(s.value_); totalAmount += nAmount; + CPubKey confidentiality_pubkey; + if (address.IsBlinded()) { + confidentiality_pubkey = address.GetBlindingKey(); + } - vecSend.push_back(make_pair(scriptPubKey, nAmount)); + vecSend.push_back(CSend(scriptPubKey, nAmount, confidentiality_pubkey)); } EnsureWalletIsUnlocked(); @@ -1002,10 +1018,10 @@ Value ListReceived(const Array& 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); @@ -1013,7 +1029,7 @@ Value ListReceived(const Array& params, bool fByAccounts) continue; tallyitem& item = mapTally[address]; - item.nAmount += txout.nValue; + item.nAmount += wtx.GetValueOut(i); item.nConf = min(item.nConf, nDepth); item.txids.push_back(wtx.GetHash()); if (mine & ISMINE_WATCH_ONLY) @@ -1153,11 +1169,15 @@ Value listreceivedbyaccount(const Array& params, bool fHelp) return ListReceived(params, true); } -static void MaybePushAddress(Object & entry, const CTxDestination &dest) +static void MaybePushAddress(Object & entry, const CTxDestination &dest, const CPubKey& pubkey) { CBitcoinAddress addr; - if (addr.Set(dest)) + if (addr.Set(dest)) { + if (pubkey.size() == 33) { + addr.AddBlindingKey(pubkey); + } entry.push_back(Pair("address", addr.ToString())); + } } void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDepth, bool fLong, Array& ret, const isminefilter& filter) @@ -1181,7 +1201,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))); entry.push_back(Pair("vout", s.vout)); @@ -1206,7 +1226,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) @@ -1574,7 +1594,7 @@ Value gettransaction(const Array& params, bool fHelp) CAmount nCredit = wtx.GetCredit(filter); CAmount nDebit = wtx.GetDebit(filter); CAmount nNet = nCredit - nDebit; - CAmount nFee = (wtx.IsFromMe(filter) ? wtx.GetValueOut() - nDebit : 0); + CAmount nFee = (wtx.IsFromMe(filter) ? wtx.nTxFee : 0); entry.push_back(Pair("amount", ValueFromAmount(nNet - nFee))); if (wtx.IsFromMe(filter)) diff --git a/src/test/blind_tests.cpp b/src/test/blind_tests.cpp new file mode 100644 index 0000000000..514de11562 --- /dev/null +++ b/src/test/blind_tests.cpp @@ -0,0 +1,129 @@ +// Copyright (c) 2011-2014 The Bitcoin Core developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "blind.h" +#include "coins.h" + +#include + +BOOST_AUTO_TEST_SUITE(bind_tests) + +BOOST_AUTO_TEST_CASE(naive_blinding_test) +{ + CCoinsView viewBase; + CCoinsViewCache cache(&viewBase); + + CKey key1; + CKey key2; + + unsigned char k1[32] = {1,2,3}; + unsigned char k2[32] = {22,33,44}; + key1.Set(&k1[0], &k1[32], true); + key2.Set(&k2[0], &k2[32], true); + CPubKey pubkey1 = key1.GetPubKey(); + CPubKey pubkey2 = key2.GetPubKey(); + + std::vector blind3, blind4; + + { + CCoinsModifier tx1 = cache.ModifyCoins(uint256(1)); + tx1->vout.resize(1); + tx1->vout[0].nValue = 11; + } + + { + CCoinsModifier tx2 = cache.ModifyCoins(uint256(2)); + tx2->vout.resize(2); + tx2->vout[0].nValue = 111; + } + + { + // Build a transaction that spends 2 unblinded coins (11, 111), and produces a single blinded one (100) and fee (22). + CMutableTransaction tx3; + tx3.vin.resize(2); + tx3.vin[0].prevout.hash = uint256(1); + + tx3.vin[0].prevout.n = 0; + tx3.vin[1].prevout.hash = uint256(2); + tx3.vin[1].prevout.n = 0; + tx3.vout.resize(1); + tx3.vout[0].nValue = 100; + tx3.nTxFee = 22; + BOOST_CHECK(cache.VerifyAmounts(tx3)); + + 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)); + output_pubkeys.push_back(pubkey1); + BlindOutputs(input_blinds, output_blinds, output_pubkeys, tx3); + BOOST_CHECK(!tx3.vout[0].nValue.IsAmount()); + BOOST_CHECK(cache.VerifyAmounts(tx3)); + + CAmount unblinded_amount; + BOOST_CHECK(UnblindOutput(key2, tx3.vout[0], unblinded_amount, blind3) == 0); + BOOST_CHECK(UnblindOutput(key1, tx3.vout[0], unblinded_amount, blind3) == 1); + BOOST_CHECK(unblinded_amount == 100); + + CCoinsModifier in3 = cache.ModifyCoins(uint256(3)); + in3->vout.resize(1); + in3->vout[0] = tx3.vout[0]; + + tx3.nTxFee--; + BOOST_CHECK(!cache.VerifyAmounts(tx3)); + } + + { + // Build a transactions that spends an unblinded (111) and blinded (100) coin, and produces a blinded (30), unblinded (40), and blinded (50) coin and fee (91) + CMutableTransaction tx4; + tx4.vin.resize(2); + tx4.vin[0].prevout.hash = uint256(2); + tx4.vin[0].prevout.n = 0; + tx4.vin[1].prevout.hash = uint256(3); + tx4.vin[1].prevout.n = 0; + tx4.vout.resize(3); + tx4.vout[0].nValue = 30; + tx4.vout[1].nValue = 40; + tx4.vout[2].nValue = 50; + tx4.nTxFee = 100 + 111 - 30 - 40 - 50; + BOOST_CHECK(cache.VerifyAmounts(tx4)); + + std::vector > input_blinds; + std::vector > output_blinds; + std::vector output_pubkeys; + input_blinds.push_back(std::vector(0, 0)); + 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_pubkeys.push_back(pubkey2); + output_pubkeys.push_back(CPubKey()); + output_pubkeys.push_back(pubkey2); + BlindOutputs(input_blinds, output_blinds, output_pubkeys, tx4); + BOOST_CHECK(!tx4.vout[0].nValue.IsAmount()); + BOOST_CHECK(tx4.vout[1].nValue.IsAmount()); + BOOST_CHECK(!tx4.vout[2].nValue.IsAmount()); + BOOST_CHECK(cache.VerifyAmounts(tx4)); + + CAmount unblinded_amount; + BOOST_CHECK(UnblindOutput(key1, tx4.vout[0], unblinded_amount, blind4) == 0); + BOOST_CHECK(UnblindOutput(key2, tx4.vout[0], unblinded_amount, blind4) == 1); + BOOST_CHECK(unblinded_amount == 30); + BOOST_CHECK(UnblindOutput(key2, tx4.vout[2], unblinded_amount, blind4) == 1); + BOOST_CHECK(unblinded_amount == 50); + + CCoinsModifier in4 = cache.ModifyCoins(uint256(4)); + in4->vout.resize(3); + in4->vout[0] = tx4.vout[0]; + in4->vout[1] = tx4.vout[1]; + in4->vout[2] = tx4.vout[2]; + + tx4.nTxFee--; + BOOST_CHECK(!cache.VerifyAmounts(tx4)); + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/test_bitcoin.cpp b/src/test/test_bitcoin.cpp index 9b5ca34ce9..1b875fa92e 100644 --- a/src/test/test_bitcoin.cpp +++ b/src/test/test_bitcoin.cpp @@ -4,6 +4,7 @@ #define BOOST_TEST_MODULE Bitcoin Test Suite +#include "blind.h" #include "key.h" #include "main.h" #include "pubkey.h" @@ -33,6 +34,7 @@ struct TestingSetup { TestingSetup() { ECC_Verify_Start(); + ECC_Blinding_Start(); ECC_Start(); SetupEnvironment(); fPrintToDebugLog = false; // don't want to write to debug.log file @@ -77,6 +79,7 @@ struct TestingSetup { #endif boost::filesystem::remove_all(pathTemp); ECC_Stop(); + ECC_Blinding_Stop(); ECC_Verify_Stop(); } }; diff --git a/src/test/wallet_tests.cpp b/src/test/wallet_tests.cpp index 90fc470e06..f30883c4ab 100644 --- a/src/test/wallet_tests.cpp +++ b/src/test/wallet_tests.cpp @@ -25,10 +25,9 @@ typedef set > CoinSet; BOOST_AUTO_TEST_SUITE(wallet_tests) -static CWallet wallet; static vector vCoins; -static void add_coin(const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0) +static void add_coin(CWallet &wallet, const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0) { static int nextLockTime = 0; CMutableTransaction tx; @@ -68,6 +67,7 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) CoinSet setCoinsRet, setCoinsRet2; CAmount nValueRet; + CWallet wallet; LOCK(wallet.cs_wallet); // test multiple times to allow for differences in the shuffle order @@ -78,7 +78,7 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) // with an empty wallet we can't even pay one cent BOOST_CHECK(!wallet.SelectCoinsMinConf( 1 * CENT, 1, 6, vCoins, setCoinsRet, nValueRet)); - add_coin(1*CENT, 4); // add a new 1 cent coin + add_coin(wallet, 1*CENT, 4); // add a new 1 cent coin // with a new 1 cent coin, we still can't find a mature 1 cent BOOST_CHECK(!wallet.SelectCoinsMinConf( 1 * CENT, 1, 6, vCoins, setCoinsRet, nValueRet)); @@ -87,7 +87,7 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) BOOST_CHECK( wallet.SelectCoinsMinConf( 1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); - add_coin(2*CENT); // add a mature 2 cent coin + add_coin(wallet, 2*CENT); // add a mature 2 cent coin // we can't make 3 cents of mature coins BOOST_CHECK(!wallet.SelectCoinsMinConf( 3 * CENT, 1, 6, vCoins, setCoinsRet, nValueRet)); @@ -96,9 +96,9 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) BOOST_CHECK( wallet.SelectCoinsMinConf( 3 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 3 * CENT); - add_coin(5*CENT); // add a mature 5 cent coin, - add_coin(10*CENT, 3, true); // a new 10 cent coin sent from one of our own addresses - add_coin(20*CENT); // and a mature 20 cent coin + add_coin(wallet, 5*CENT); // add a mature 5 cent coin, + add_coin(wallet, 10*CENT, 3, true); // a new 10 cent coin sent from one of our own addresses + add_coin(wallet, 20*CENT); // and a mature 20 cent coin // now we have new: 1+10=11 (of which 10 was self-sent), and mature: 2+5+20=27. total = 38 @@ -136,11 +136,11 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) // now clear out the wallet and start again to test choosing between subsets of smaller coins and the next biggest coin empty_wallet(); - add_coin( 6*CENT); - add_coin( 7*CENT); - add_coin( 8*CENT); - add_coin(20*CENT); - add_coin(30*CENT); // now we have 6+7+8+20+30 = 71 cents total + add_coin(wallet, 6*CENT); + add_coin(wallet, 7*CENT); + add_coin(wallet, 8*CENT); + add_coin(wallet, 20*CENT); + add_coin(wallet, 30*CENT); // now we have 6+7+8+20+30 = 71 cents total // check that we have 71 and not 72 BOOST_CHECK( wallet.SelectCoinsMinConf(71 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); @@ -151,14 +151,14 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) BOOST_CHECK_EQUAL(nValueRet, 20 * CENT); // we should get 20 in one coin BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); - add_coin( 5*CENT); // now we have 5+6+7+8+20+30 = 75 cents total + add_coin(wallet, 5*CENT); // now we have 5+6+7+8+20+30 = 75 cents total // now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, better than the next biggest coin, 20 BOOST_CHECK( wallet.SelectCoinsMinConf(16 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 3 coins BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); - add_coin( 18*CENT); // now we have 5+6+7+8+18+20+30 + add_coin(wallet, 18*CENT); // now we have 5+6+7+8+18+20+30 // and now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, the same as the next biggest coin, 18 BOOST_CHECK( wallet.SelectCoinsMinConf(16 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); @@ -171,10 +171,10 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); // check that the smallest bigger coin is used - add_coin( 1*COIN); - add_coin( 2*COIN); - add_coin( 3*COIN); - add_coin( 4*COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents + add_coin(wallet, 1*COIN); + add_coin(wallet, 2*COIN); + add_coin(wallet, 3*COIN); + add_coin(wallet, 4*COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents BOOST_CHECK( wallet.SelectCoinsMinConf(95 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 1 * COIN); // we should get 1 BTC in 1 coin BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); @@ -185,11 +185,11 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) // empty the wallet and start again, now with fractions of a cent, to test sub-cent change avoidance empty_wallet(); - add_coin(0.1*CENT); - add_coin(0.2*CENT); - add_coin(0.3*CENT); - add_coin(0.4*CENT); - add_coin(0.5*CENT); + add_coin(wallet, 0.1*CENT); + add_coin(wallet, 0.2*CENT); + add_coin(wallet, 0.3*CENT); + add_coin(wallet, 0.4*CENT); + add_coin(wallet, 0.5*CENT); // try making 1 cent from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 = 1.5 cents // we'll get sub-cent change whatever happens, so can expect 1.0 exactly @@ -197,15 +197,15 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // but if we add a bigger coin, making it possible to avoid sub-cent change, things change: - add_coin(1111*CENT); + add_coin(wallet, 1111*CENT); // try making 1 cent from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5 cents BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // we should get the exact amount // if we add more sub-cent coins: - add_coin(0.6*CENT); - add_coin(0.7*CENT); + add_coin(wallet, 0.6*CENT); + add_coin(wallet, 0.7*CENT); // and try again to make 1.0 cents, we can still make 1.0 cents BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); @@ -215,7 +215,7 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) // they tried to consolidate 10 50k coins into one 500k coin, and ended up with 50k in change empty_wallet(); for (int i = 0; i < 20; i++) - add_coin(50000 * COIN); + add_coin(wallet, 50000 * COIN); BOOST_CHECK( wallet.SelectCoinsMinConf(500000 * COIN, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 500000 * COIN); // we should get the exact amount @@ -226,29 +226,29 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) // sometimes it will fail, and so we use the next biggest coin: empty_wallet(); - add_coin(0.5 * CENT); - add_coin(0.6 * CENT); - add_coin(0.7 * CENT); - add_coin(1111 * CENT); + add_coin(wallet, 0.5 * CENT); + add_coin(wallet, 0.6 * CENT); + add_coin(wallet, 0.7 * CENT); + add_coin(wallet, 1111 * CENT); BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 1111 * CENT); // we get the bigger coin BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); // but sometimes it's possible, and we use an exact subset (0.4 + 0.6 = 1.0) empty_wallet(); - add_coin(0.4 * CENT); - add_coin(0.6 * CENT); - add_coin(0.8 * CENT); - add_coin(1111 * CENT); + add_coin(wallet, 0.4 * CENT); + add_coin(wallet, 0.6 * CENT); + add_coin(wallet, 0.8 * CENT); + add_coin(wallet, 1111 * CENT); BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet)); BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // we should get the exact amount BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); // in two coins 0.4+0.6 // test avoiding sub-cent change empty_wallet(); - add_coin(0.0005 * COIN); - add_coin(0.01 * COIN); - add_coin(1 * COIN); + add_coin(wallet, 0.0005 * COIN); + add_coin(wallet, 0.01 * COIN); + add_coin(wallet, 1 * COIN); // trying to make 1.0001 from these three coins BOOST_CHECK( wallet.SelectCoinsMinConf(1.0001 * COIN, 1, 1, vCoins, setCoinsRet, nValueRet)); @@ -264,7 +264,7 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) { empty_wallet(); for (int i2 = 0; i2 < 100; i2++) - add_coin(COIN); + add_coin(wallet, COIN); // picking 50 from 100 coins doesn't depend on the shuffle, // but does depend on randomness in the stochastic approximation code @@ -287,7 +287,7 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) // add 75 cents in small change. not enough to make 90 cents, // then try making 90 cents. there are multiple competing "smallest bigger" coins, // one of which should be picked at random - add_coin( 5*CENT); add_coin(10*CENT); add_coin(15*CENT); add_coin(20*CENT); add_coin(25*CENT); + add_coin(wallet, 5*CENT); add_coin(wallet, 10*CENT); add_coin(wallet, 15*CENT); add_coin(wallet, 20*CENT); add_coin(wallet, 25*CENT); fails = 0; for (int i = 0; i < RANDOM_REPEATS; i++) diff --git a/src/wallet.cpp b/src/wallet.cpp index a427dc96da..ef2a4cb0e0 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -20,8 +20,11 @@ #include #include +#include + using namespace std; + /** * Settings */ @@ -54,7 +57,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 @@ -753,12 +756,36 @@ 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; } +CAmount CWallet::GetCredit(const CWalletTx& tx, const isminefilter& filter) const +{ + CAmount nCredit = 0; + for (unsigned int i = 0; i < tx.vout.size(); i++) + { + nCredit += tx.GetCredit(i, filter); + if (!MoneyRange(nCredit)) + throw std::runtime_error("CWallet::GetCredit() : value out of range"); + } + return nCredit; +} + +CAmount CWallet::GetChange(const CWalletTx& tx) const +{ + CAmount nChange = 0; + for (unsigned int i = 0; i < tx.vout.size(); i++) + { + nChange += tx.GetChange(i); + if (!MoneyRange(nChange)) + throw std::runtime_error("CWallet::GetChange() : value out of range"); + } + return nChange; +} + bool CWallet::IsChange(const CTxOut& txout) const { // TODO: fix handling of 'change' outputs. The assumption is that any @@ -837,10 +864,7 @@ void CWalletTx::GetAmounts(list& listReceived, // Compute fee: CAmount nDebit = GetDebit(filter); if (nDebit > 0) // debit>0 means we signed/sent this transaction - { - CAmount nValueOut = GetValueOut(); - nFee = nDebit - nValueOut; - } + nFee = nTxFee; // Sent/received. for (unsigned int i = 0; i < vout.size(); ++i) @@ -868,7 +892,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 = pwallet->blinding_pubkey; + } // If we are debited by the transaction, add the output as a "sent" entry if (nDebit > 0) @@ -1179,7 +1207,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 && + !IsLockedCoin((*it).first, i) && pcoin->GetValueOut(i) > 0 && (!coinControl || !coinControl->HasSelected() || coinControl->IsSelected((*it).first, i))) vCoins.push_back(COutput(pcoin, i, nDepth, ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (includeWatching && ((mine & ISMINE_WATCH_ONLY) != ISMINE_NO)))); } @@ -1259,7 +1287,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)); @@ -1349,8 +1377,8 @@ bool CWallet::SelectCoins(const CAmount& nTargetValue, setvout[out.i].nValue; + continue; + nValueRet += out.tx->GetValueOut(out.i); setCoinsRet.insert(make_pair(out.tx, out.i)); } return (nValueRet >= nTargetValue); @@ -1369,7 +1397,7 @@ bool CWallet::SelectCoins(const CAmount& nTargetValue, setvout[out.i].nValue; + nValueTroughVINs += out.tx->GetValueOut(out.i); // temporarily keep the coin to add them later after SelectCoinsMinConf has added some setTempCoins.insert(make_pair(out.tx, out.i)); @@ -1407,12 +1435,16 @@ bool CWallet::SelectCoins(const CAmount& nTargetValue, set > vecSend; + vector vecSend; vector vin; BOOST_FOREACH (const CTxOut& txOut, txToFund.vout) { - vecSend.push_back(make_pair(txOut.scriptPubKey, txOut.nValue)); + if (!txOut.nValue.IsAmount()) { + strFailReason = _("Pre-funded amounts must be non-blinded"); + return false; + } + vecSend.push_back(CSend(txOut.scriptPubKey, txOut.nValue.GetAmount())); } BOOST_FOREACH (const CTxIn& txIn, txToFund.vin) @@ -1434,20 +1466,20 @@ bool CWallet::FundTransaction(const CTransaction& txToFund, CMutableTransaction& return result; } -bool CWallet::CreateTransaction(const vector >& vecSend, +bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wtxNew, CMutableTransaction& txNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl, bool sign, bool includeWatching) { vector vINs; return CreateTransaction(vecSend, vINs, wtxNew, txNew, reservekey, nFeeRet, strFailReason, coinControl, sign, includeWatching); } -bool CWallet::CreateTransaction(const vector >& vecSend, const vector vINs, +bool CWallet::CreateTransaction(const vector& vecSend, const vector vINs, CWalletTx& wtxNew, CMutableTransaction& txNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl, bool sign, bool includeWatching) { bool cannotFundFee = false; // used with includeWatching CAmount nValue = 0; - BOOST_FOREACH (const PAIRTYPE(CScript, CAmount)& s, vecSend) + BOOST_FOREACH (const CSend& s, vecSend) { if (nValue < 0) { @@ -1471,6 +1503,10 @@ bool CWallet::CreateTransaction(const vector >& vecSend, nFeeRet = 0; while (true) { + std::vector output_pubkeys; + bool fBlindedOuts = false; + CAmount nValueOut = 0; + txNew.vin.clear(); txNew.vout.clear(); wtxNew.fFromMe = true; @@ -1478,7 +1514,7 @@ bool CWallet::CreateTransaction(const vector >& vecSend, CAmount nTotalValue = nValue + nFeeRet; double dPriority = 0; // vouts to the payees - BOOST_FOREACH (const PAIRTYPE(CScript, CAmount)& s, vecSend) + BOOST_FOREACH (const CSend& s, vecSend) { CTxOut txout(s.second, s.first); if (txout.IsDust(::minRelayTxFee)) @@ -1487,6 +1523,11 @@ bool CWallet::CreateTransaction(const vector >& vecSend, return false; } txNew.vout.push_back(txout); + nValueOut += s.second; + output_pubkeys.push_back(s.confidentiality_key); + if (s.confidentiality_key.size() != 0) { + fBlindedOuts = true; + } } // Choose coins to use @@ -1511,7 +1552,7 @@ bool CWallet::CreateTransaction(const vector >& vecSend, } BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins) { - CAmount nCredit = pcoin.first->vout[pcoin.second].nValue; + 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. @@ -1566,8 +1607,11 @@ bool CWallet::CreateTransaction(const vector >& vecSend, else { // Insert change txn at random position: - vector::iterator position = txNew.vout.begin()+GetRandInt(txNew.vout.size()+1); + int pos = GetRandInt(txNew.vout.size()+1); + vector::iterator position = txNew.vout.begin()+pos; txNew.vout.insert(position, newTxOut); + output_pubkeys.insert(output_pubkeys.begin() + pos, blinding_pubkey); + nValueOut += nChange; } } else @@ -1577,7 +1621,30 @@ bool CWallet::CreateTransaction(const vector >& vecSend, BOOST_FOREACH(const PAIRTYPE(const CWalletTx*,unsigned int)& coin, setCoins) txNew.vin.push_back(CTxIn(coin.first->GetHash(),coin.second)); - // Sign (also calculate fee) + txNew.nTxFee = nValueIn - nValueOut; + LogPrintf("Created transaction (before blinding): %s", CTransaction(txNew).ToString()); + + // Create blinded outputs + bool fBlindedIns = false; + 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); + if (!blind.empty()) { + fBlindedIns = true; + } + input_blinds.push_back(blind); + } + for (size_t nOut = 0; nOut < txNew.vout.size(); nOut++) { + output_blinds.push_back(std::vector(0, 0)); + } + if (fBlindedIns && !fBlindedOuts) { + strFailReason = _("Confidential inputs without confidential outputs"); + return false; + } + BlindOutputs(input_blinds, output_blinds, output_pubkeys, txNew); + + // Sign int nIn = 0; BOOST_FOREACH(const PAIRTYPE(const CWalletTx*,unsigned int)& coin, setCoins) // when unsignable and watchonly enabled, this may be a watchonly so don't error @@ -1648,11 +1715,11 @@ bool CWallet::CreateTransaction(const vector >& vecSend, return true; } -bool CWallet::CreateTransaction(CScript scriptPubKey, const CAmount& nValue, +bool CWallet::CreateTransaction(CScript scriptPubKey, const CAmount& nValue, const CPubKey& confidentiality_key, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl, bool sign, bool includeWatching) { - vector< pair > vecSend; - vecSend.push_back(make_pair(scriptPubKey, nValue)); + vector vecSend; + vecSend.push_back(CSend(scriptPubKey, nValue, confidentiality_key)); CMutableTransaction txNew; vector vINs; return CreateTransaction(vecSend, vINs, wtxNew, txNew, reservekey, nFeeRet, strFailReason, coinControl, sign, includeWatching); @@ -2006,7 +2073,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; diff --git a/src/wallet.h b/src/wallet.h index 6f840081cf..7a4c0217cb 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -7,6 +7,7 @@ #define BITCOIN_WALLET_H #include "amount.h" +#include "blind.h" #include "primitives/block.h" #include "primitives/transaction.h" #include "crypter.h" @@ -103,6 +104,16 @@ public: StringMap destdata; }; +struct CSend +{ + CScript first; + CAmount second; + CPubKey confidentiality_key; + + CSend(const CScript& key_in, const CAmount& amount_in) : first(key_in), second(amount_in) {} + CSend(const CScript& key_in, const CAmount& amount_in, const CPubKey& pubkey_in) : first(key_in), second(amount_in), confidentiality_key(pubkey_in) {} +}; + /** * A CWallet is an extension of a keystore, which also maintains a set of transactions and balances, * and provides the ability to create new transactions. @@ -185,6 +196,9 @@ 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(); } std::map mapWallet; @@ -200,6 +214,9 @@ public: int64_t nTimeFirstKey; + CKey blinding_key; + CPubKey blinding_pubkey; + const CWalletTx* GetWalletTx(const uint256& hash) const; //! check whether we are allowed to upgrade (or already support) to the named feature @@ -289,11 +306,11 @@ public: CAmount GetUnconfirmedWatchOnlyBalance() const; CAmount GetImmatureWatchOnlyBalance() const; bool FundTransaction(const CTransaction& txToFund, CMutableTransaction& txNew, CAmount& nFeeRet, std::string& strFailReason, bool includeWatching = false); - bool CreateTransaction(const std::vector >& vecSend, const std::vector vins, + bool CreateTransaction(const std::vector& vecSend, const std::vector vins, CWalletTx& wtxNew, CMutableTransaction& txNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true, bool includeWatching = false); - bool CreateTransaction(const std::vector >& vecSend, + bool CreateTransaction(const std::vector& vecSend, CWalletTx& wtxNew, CMutableTransaction& txNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true, bool includeWatching = false); - bool CreateTransaction(CScript scriptPubKey, const CAmount& nValue, + bool CreateTransaction(CScript scriptPubKey, const CAmount& nValue, const CPubKey& confidentiality_key, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true, bool includeWatching = false); bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey); @@ -320,19 +337,9 @@ public: { return ::IsMine(*this, txout.scriptPubKey); } - CAmount GetCredit(const CTxOut& txout, const isminefilter& filter) const - { - if (!MoneyRange(txout.nValue)) - throw std::runtime_error("CWallet::GetCredit() : value out of range"); - return ((IsMine(txout) & filter) ? txout.nValue : 0); - } + bool IsChange(const CTxOut& txout) const; - CAmount GetChange(const CTxOut& txout) const - { - if (!MoneyRange(txout.nValue)) - throw std::runtime_error("CWallet::GetChange() : value out of range"); - return (IsChange(txout) ? txout.nValue : 0); - } + bool IsMine(const CTransaction& tx) const { BOOST_FOREACH(const CTxOut& txout, tx.vout) @@ -356,28 +363,8 @@ public: } return nDebit; } - CAmount GetCredit(const CTransaction& tx, const isminefilter& filter) const - { - CAmount nCredit = 0; - BOOST_FOREACH(const CTxOut& txout, tx.vout) - { - nCredit += GetCredit(txout, filter); - if (!MoneyRange(nCredit)) - throw std::runtime_error("CWallet::GetCredit() : value out of range"); - } - return nCredit; - } - CAmount GetChange(const CTransaction& tx) const - { - CAmount nChange = 0; - BOOST_FOREACH(const CTxOut& txout, tx.vout) - { - nChange += GetChange(txout); - if (!MoneyRange(nChange)) - throw std::runtime_error("CWallet::GetChange() : value out of range"); - } - return nChange; - } + CAmount GetCredit(const CWalletTx& tx, const isminefilter& filter) const; + CAmount GetChange(const CWalletTx& tx) const; void SetBestChain(const CBlockLocator& loc); DBErrors LoadWallet(bool& fFirstRunRet); @@ -493,6 +480,7 @@ struct COutputEntry CTxDestination destination; CAmount amount; int vout; + CPubKey confidentiality_pubkey; }; /** A transaction with a merkle branch linking it to the block chain. */ @@ -573,6 +561,9 @@ public: std::string strFromAccount; int64_t nOrderPos; //! position in ordered transaction list + mutable std::vector > vBlindingFactors; + mutable std::vector vAmountsOut; + // memory only mutable bool fDebitCached; mutable bool fCreditCached; @@ -671,6 +662,8 @@ public: READWRITE(nTimeReceived); READWRITE(fFromMe); READWRITE(fSpent); + READWRITE(vBlindingFactors); + READWRITE(vAmountsOut); if (ser_action.ForRead()) { @@ -739,6 +732,16 @@ public: return debit; } + CAmount GetCredit(unsigned int nTxOut, const isminefilter& filter) const + { + CAmount amount = 0; + if (pwallet->IsMine(vout[nTxOut]) & filter) + amount = GetValueOut(nTxOut); + if (!MoneyRange(amount)) + throw std::runtime_error("CWallet::GetCredit() : value out of range"); + return amount; + } + CAmount GetCredit(const isminefilter& filter) const { // Must wait until coinbase is safely deep enough in the chain before valuing it @@ -804,8 +807,7 @@ public: { 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"); } @@ -847,8 +849,7 @@ public: { 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"); } @@ -859,6 +860,16 @@ public: return nCredit; } + CAmount GetChange(unsigned int nTxOut) const + { + CAmount amount = 0; + if (pwallet->IsChange(vout[nTxOut])) + amount = GetValueOut(nTxOut); + if (!MoneyRange(amount)) + throw std::runtime_error("CWallet::GetCredit() : value out of range"); + return amount; + } + CAmount GetChange() const { if (fChangeCached) @@ -914,6 +925,33 @@ public: void RelayWalletTransaction(); 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; + } + } + } + +public: + //! Returns either the value out (if it is to us) or 0 + CAmount GetValueOut(unsigned int nOut) const { + FillValuesAndBlindingFactors(); + return vAmountsOut[nOut]; + } + + //! Returns either the blinding factor (if it is to us) or 0 + std::vector GetBlindingFactor(unsigned int nOut) const { + FillValuesAndBlindingFactors(); + return vBlindingFactors[nOut]; + } }; diff --git a/src/walletdb.cpp b/src/walletdb.cpp index ffddd8106b..7b848ffcac 100644 --- a/src/walletdb.cpp +++ b/src/walletdb.cpp @@ -193,6 +193,11 @@ bool CWalletDB::WriteAccountingEntry(const CAccountingEntry& acentry) return WriteAccountingEntry(++nAccountingEntryNumber, acentry); } +bool CWalletDB::WriteBlindingKey(const CKey& privKey) +{ + return Write(std::string("blindingkey"), std::vector(privKey.begin(), privKey.end())); +} + CAmount CWalletDB::GetAccountCreditDebit(const string& strAccount) { list entries; @@ -590,6 +595,16 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, return false; } } + 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(); + } + } } catch (...) { return false; @@ -701,6 +716,12 @@ 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); + } + return result; } diff --git a/src/walletdb.h b/src/walletdb.h index 7ff41c7c8d..87bf1644c6 100644 --- a/src/walletdb.h +++ b/src/walletdb.h @@ -123,6 +123,8 @@ public: CAmount GetAccountCreditDebit(const std::string& strAccount); void ListAccountCreditDebit(const std::string& strAccount, std::list& acentries); + bool WriteBlindingKey(const CKey& privKey); + DBErrors ReorderTransactions(CWallet* pwallet); DBErrors LoadWallet(CWallet* pwallet); DBErrors FindWalletTx(CWallet* pwallet, std::vector& vTxHash, std::vector& vWtx);