Wallet, RPC, bitcoin-tx, etc updates for confidential transactions

This commit is contained in:
Pieter Wuille 2015-06-08 19:28:47 -07:00 committed by Matt Corallo
parent 74987d30b1
commit 292f923d03
29 changed files with 968 additions and 175 deletions

View file

@ -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 \

View file

@ -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 \

View file

@ -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<unsigned char> 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<unsigned char>(&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);
}

View file

@ -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;
};
/**

View file

@ -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<unsigned char>(pubkey.begin(), pubkey.end());
}
tx.vout.push_back(txout);
}
static void MutateTxBlind(CMutableTransaction& tx, const string& strInput)
{
std::vector<std::string> 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<std::vector<unsigned char> > input_blinds;
std::vector<std::vector<unsigned char> > output_blinds;
std::vector<CPubKey> output_pubkeys;
for (size_t nIn = 0; nIn < tx.vin.size(); nIn++) {
std::vector<unsigned char> 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<unsigned char>(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);

137
src/blind.cpp Normal file
View file

@ -0,0 +1,137 @@
#include "blind.h"
#include "hash.h"
#include "primitives/transaction.h"
#include "random.h"
#include "util.h"
#include <secp256k1.h>
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<unsigned char>& 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<std::vector<unsigned char> >& input_blinding_factors, const std::vector<std::vector<unsigned char> >& output_blinding_factors, const std::vector<CPubKey>& 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<const unsigned char*> 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);
}
}
}

14
src/blind.h Normal file
View file

@ -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<unsigned char>& blinding_factor_out);
void BlindOutputs(const std::vector<std::vector<unsigned char> >& input_blinding_factors, const std::vector<std::vector<unsigned char> >& output_blinding_factors, const std::vector<CPubKey>& output_pubkeys, CMutableTransaction& tx);
#endif

View file

@ -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);

View file

@ -34,6 +34,7 @@ public:
enum Base58Type {
PUBKEY_ADDRESS,
SCRIPT_ADDRESS,
BLINDED_ADDRESS,
SECRET_KEY,
EXT_PUBLIC_KEY,
EXT_SECRET_KEY,

View file

@ -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();

View file

@ -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<unsigned char>& vchSig, uint32_t test_case) const {
if (!fValid)
return false;

View file

@ -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.

View file

@ -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++)

View file

@ -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

View file

@ -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 += "<b>" + tr("Credit") + ":</b> ";
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 += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -txout.nValue) + "<br>";
strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -wtx.GetValueOut(i)) + "<br>";
if(toSelf)
strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, txout.nValue) + "<br>";
strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, wtx.GetValueOut(i)) + "<br>";
}
if (fAllToMe)
@ -213,9 +212,8 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco
strHTML += "<b>" + tr("Total credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, nValue) + "<br>";
}
CAmount nTxFee = nDebit - wtx.GetValueOut();
if (nTxFee > 0)
strHTML += "<b>" + tr("Transaction fee") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -nTxFee) + "<br>";
if (wtx.nTxFee > 0)
strHTML += "<b>" + tr("Transaction fee") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -wtx.nTxFee) + "<br>";
}
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 += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -wallet->GetDebit(txin, ISMINE_ALL)) + "<br>";
BOOST_FOREACH(const CTxOut& txout, wtx.vout)
if (wallet->IsMine(txout))
strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, wallet->GetCredit(txout, ISMINE_ALL)) + "<br>";
for (unsigned int i = 0; i < wtx.vout.size(); i++)
if (wallet->IsMine(wtx.vout[i]) & ISMINE_ALL)
strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, wtx.GetValueOut(i)) + "<br>";
}
}
@ -278,9 +276,9 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx, TransactionReco
BOOST_FOREACH(const CTxIn& txin, wtx.vin)
if(wallet->IsMine(txin))
strHTML += "<b>" + tr("Debit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, -wallet->GetDebit(txin, ISMINE_ALL)) + "<br>";
BOOST_FOREACH(const CTxOut& txout, wtx.vout)
if(wallet->IsMine(txout))
strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, wallet->GetCredit(txout, ISMINE_ALL)) + "<br>";
for (unsigned int i = 0; i < wtx.vout.size(); i++)
if(wallet->IsMine(wtx.vout[i]) & ISMINE_ALL)
strHTML += "<b>" + tr("Credit") + ":</b> " + BitcoinUnits::formatHtmlWithUnit(unit, wtx.GetCredit(i)) + "<br>";
strHTML += "<br><b>" + tr("Transaction") + ":</b><br>";
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")) + "</li>";
strHTML = strHTML + " IsWatchOnly=" + (wallet->IsMine(vout) & ISMINE_WATCH_ONLY ? tr("true") : tr("false")) + "</li>";
}

View file

@ -43,15 +43,16 @@ QList<TransactionRecord> 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> 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> 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)
{

View file

@ -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<SendCoinsRecipient> recipients = transaction.getRecipients();
std::vector<std::pair<CScript, CAmount> > vecSend;
std::vector<CSend> 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<CScript, CAmount>(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<CScript, CAmount>(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;
}

View file

@ -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);

View file

@ -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 <boost/assign/list_of.hpp>
#include "json/json_spirit_utils.h"
#include "json/json_spirit_value.h"
#include <secp256k1.h>
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<CBitcoinAddress> 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<unsigned char>(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<unsigned char> 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<std::vector<unsigned char> > input_blinds;
std::vector<std::vector<unsigned char> > output_blinds;
std::vector<CPubKey> 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<unsigned char>(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<unsigned char> 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<std::vector<unsigned char> > input_blinds;
std::vector<std::vector<unsigned char> > output_blinds;
std::vector<CPubKey> output_pubkeys;
for (size_t nIn = 0; nIn < tx.vin.size(); nIn++) {
std::map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.find(tx.vin[nIn].prevout.hash);
if (it == pwalletMain->mapWallet.end()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter: transaction spends from non-wallet output"));
}
if (tx.vin[nIn].prevout.n >= it->second.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<unsigned char> 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<unsigned char>(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<unsigned char>(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)

View file

@ -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

View file

@ -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);

View file

@ -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<CBitcoinAddress> setAddress;
vector<pair<CScript, CAmount> > vecSend;
vector<CSend> 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))

129
src/test/blind_tests.cpp Normal file
View file

@ -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/test/unit_test.hpp>
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<unsigned char> 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<std::vector<unsigned char> > input_blinds;
std::vector<std::vector<unsigned char> > output_blinds;
std::vector<CPubKey> output_pubkeys;
input_blinds.push_back(std::vector<unsigned char>(0, 0));
input_blinds.push_back(std::vector<unsigned char>(0, 0));
output_blinds.push_back(std::vector<unsigned char>(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<std::vector<unsigned char> > input_blinds;
std::vector<std::vector<unsigned char> > output_blinds;
std::vector<CPubKey> output_pubkeys;
input_blinds.push_back(std::vector<unsigned char>(0, 0));
input_blinds.push_back(blind3);
output_blinds.push_back(std::vector<unsigned char>(0, 0));
output_blinds.push_back(std::vector<unsigned char>(0, 0));
output_blinds.push_back(std::vector<unsigned char>(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()

View file

@ -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();
}
};

View file

@ -25,10 +25,9 @@ typedef set<pair<const CWalletTx*,unsigned int> > CoinSet;
BOOST_AUTO_TEST_SUITE(wallet_tests)
static CWallet wallet;
static vector<COutput> 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++)

View file

@ -20,8 +20,11 @@
#include <boost/algorithm/string/replace.hpp>
#include <boost/thread.hpp>
#include <secp256k1.h>
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<COutputEntry>& 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<COutputEntry>& 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<COutput>& 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<CAmount,pair<const CWalletTx*,unsigned int> > coin = make_pair(n,make_pair(pcoin, i));
@ -1349,8 +1377,8 @@ bool CWallet::SelectCoins(const CAmount& nTargetValue, set<pair<const CWalletTx*
BOOST_FOREACH(const COutput& out, vCoins)
{
if (!out.fSpendable)
continue;
nValueRet += out.tx->vout[out.i].nValue;
continue;
nValueRet += out.tx->GetValueOut(out.i);
setCoinsRet.insert(make_pair(out.tx, out.i));
}
return (nValueRet >= nTargetValue);
@ -1369,7 +1397,7 @@ bool CWallet::SelectCoins(const CAmount& nTargetValue, set<pair<const CWalletTx*
if (!out.fSpendable)
continue;
nValueTroughVINs += out.tx->vout[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<pair<const CWalletTx*
bool CWallet::FundTransaction(const CTransaction& txToFund, CMutableTransaction& txNew, CAmount &nFeeRet, std::string& strFailReason, bool includeWatching)
{
vector<pair<CScript, CAmount> > vecSend;
vector<CSend> vecSend;
vector<CTxIn> 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<pair<CScript, CAmount> >& vecSend,
bool CWallet::CreateTransaction(const vector<CSend>& vecSend,
CWalletTx& wtxNew, CMutableTransaction& txNew, CReserveKey& reservekey, CAmount& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl, bool sign, bool includeWatching)
{
vector<CTxIn> vINs;
return CreateTransaction(vecSend, vINs, wtxNew, txNew, reservekey, nFeeRet, strFailReason, coinControl, sign, includeWatching);
}
bool CWallet::CreateTransaction(const vector<pair<CScript, CAmount> >& vecSend, const vector<CTxIn> vINs,
bool CWallet::CreateTransaction(const vector<CSend>& vecSend, const vector<CTxIn> 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<pair<CScript, CAmount> >& vecSend,
nFeeRet = 0;
while (true)
{
std::vector<CPubKey> 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<pair<CScript, CAmount> >& 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<pair<CScript, CAmount> >& 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<pair<CScript, CAmount> >& 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<pair<CScript, CAmount> >& vecSend,
else
{
// Insert change txn at random position:
vector<CTxOut>::iterator position = txNew.vout.begin()+GetRandInt(txNew.vout.size()+1);
int pos = GetRandInt(txNew.vout.size()+1);
vector<CTxOut>::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<pair<CScript, CAmount> >& 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<std::vector<unsigned char> > input_blinds;
std::vector<std::vector<unsigned char> > output_blinds;
BOOST_FOREACH(const PAIRTYPE(const CWalletTx*,unsigned int)& coin, setCoins) {
std::vector<unsigned char> 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<unsigned char>(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<pair<CScript, CAmount> >& 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<CScript, CAmount> > vecSend;
vecSend.push_back(make_pair(scriptPubKey, nValue));
vector<CSend> vecSend;
vecSend.push_back(CSend(scriptPubKey, nValue, confidentiality_key));
CMutableTransaction txNew;
vector<CTxIn> vINs;
return CreateTransaction(vecSend, vINs, wtxNew, txNew, reservekey, nFeeRet, strFailReason, coinControl, sign, includeWatching);
@ -2006,7 +2073,7 @@ std::map<CTxDestination, CAmount> 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;

View file

@ -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<uint256, CWalletTx> 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<std::pair<CScript, CAmount> >& vecSend, const std::vector<CTxIn> vins,
bool CreateTransaction(const std::vector<CSend>& vecSend, const std::vector<CTxIn> 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<std::pair<CScript, CAmount> >& vecSend,
bool CreateTransaction(const std::vector<CSend>& 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<std::vector<unsigned char> > vBlindingFactors;
mutable std::vector<CAmount> 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<uint256> 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<unsigned char> 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<unsigned char> GetBlindingFactor(unsigned int nOut) const {
FillValuesAndBlindingFactors();
return vBlindingFactors[nOut];
}
};

View file

@ -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<unsigned char>(privKey.begin(), privKey.end()));
}
CAmount CWalletDB::GetAccountCreditDebit(const string& strAccount)
{
list<CAccountingEntry> 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<unsigned char> 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;
}

View file

@ -123,6 +123,8 @@ public:
CAmount GetAccountCreditDebit(const std::string& strAccount);
void ListAccountCreditDebit(const std::string& strAccount, std::list<CAccountingEntry>& acentries);
bool WriteBlindingKey(const CKey& privKey);
DBErrors ReorderTransactions(CWallet* pwallet);
DBErrors LoadWallet(CWallet* pwallet);
DBErrors FindWalletTx(CWallet* pwallet, std::vector<uint256>& vTxHash, std::vector<CWalletTx>& vWtx);