Merge pull request #625 from Electric-Coin-Company/zc.v0.11.2.ticketXXX-calgary-implementation.2

Calgary Design Foundations
This commit is contained in:
Nathan Wilcox 2016-01-19 17:33:19 -08:00
commit 6f466aea69
38 changed files with 2667 additions and 542 deletions

View File

@ -6,7 +6,7 @@ SDK_PATH ?= $(BASEDIR)/SDKs
NO_QT ?=
NO_WALLET ?=
NO_UPNP ?=
FALLBACK_DOWNLOAD_PATH ?= https://bitcoincore.org/depends-sources
FALLBACK_DOWNLOAD_PATH ?= https://z.cash/depends-sources
BUILD = $(shell ./config.guess)
HOST ?= $(BUILD)

View File

@ -2,8 +2,8 @@ package=libzerocash
$(package)_download_path=https://github.com/Electric-Coin-Company/$(package)/archive/
$(package)_file_name=$(package)-$($(package)_git_commit).tar.gz
$(package)_download_file=$($(package)_git_commit).tar.gz
$(package)_sha256_hash=c758b1f2b3372fb0e228442745668d0498a183cd0a4bcc423271e4ff3ddde85e
$(package)_git_commit=69df6c95d97a1f1ee1fece0a6a7eef7d6a577dbc
$(package)_sha256_hash=1364a739751bcdda86cfd66d3d019844d116c374d7a7634bfb3e1a47c085f3c0
$(package)_git_commit=dd5db5815be70f0e4895784cc905df6f1c73cb17
$(package)_dependencies=libsnark crypto++ openssl boost libgmp
$(package)_patches=

View File

@ -27,6 +27,8 @@ testScripts=(
'merkle_blocks.py'
'signrawtransactions.py'
'walletbackup.py'
'zcpour.py'
'zcpourdoublespend.py'
);
testScriptsExt=(
'bipdersig-p2p.py'

View File

@ -3,6 +3,7 @@
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
#
ZCASH_LOAD_TIMEOUT=500
DATADIR="@abs_top_builddir@/.zcash"
rm -rf "$DATADIR"
mkdir -p "$DATADIR"/regtest
@ -14,7 +15,7 @@ PORT=`expr 10000 + $$ % 55536`
BITCOIND=$!
#Install a watchdog.
(sleep 10 && kill -0 $WAITER 2>/dev/null && kill -9 $BITCOIND $$)&
(sleep "$ZCASH_LOAD_TIMEOUT" && kill -0 $WAITER 2>/dev/null && kill -9 $BITCOIND $$)&
wait $WAITER
if [ -n "$TIMEOUT" ]; then
@ -25,7 +26,7 @@ else
RETURN=$?
fi
(sleep 15 && kill -0 $BITCOIND 2>/dev/null && kill -9 $BITCOIND $$)&
(sleep "$ZCASH_LOAD_TIMEOUT" && kill -0 $BITCOIND 2>/dev/null && kill -9 $BITCOIND $$)&
kill $BITCOIND && wait $BITCOIND
# timeout returns 124 on timeout, otherwise the return value of the child

View File

@ -49,7 +49,7 @@ except ImportError:
USER_AGENT = "AuthServiceProxy/0.1"
HTTP_TIMEOUT = 30
HTTP_TIMEOUT = 600
log = logging.getLogger("BitcoinRPC")

106
qa/rpc-tests/zcpour.py Executable file
View File

@ -0,0 +1,106 @@
#!/usr/bin/env python2
#
# Test Pour semantics
#
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import *
from decimal import Decimal
import os
import shutil
import sys
class PourTxTest(BitcoinTestFramework):
def setup_network(self):
# Start with split network:
return super(PourTxTest, self).setup_network(True)
def send_pours_around(self):
zckeypair = self.nodes[1].zcrawkeygen()
zcsecretkey = zckeypair["zcsecretkey"]
zcaddress = zckeypair["zcaddress"]
(total_in, inputs) = gather_inputs(self.nodes[1], 50)
protect_tx = self.nodes[1].createrawtransaction(inputs, {})
pour_result = self.nodes[1].zcrawpour(protect_tx, {}, {zcaddress:49.9}, 50, 0.1)
receive_result = self.nodes[1].zcrawreceive(zcsecretkey, pour_result["encryptedbucket1"])
assert_equal(receive_result["exists"], False)
protect_tx = self.nodes[1].signrawtransaction(pour_result["rawtxn"])
self.nodes[1].sendrawtransaction(protect_tx["hex"])
self.nodes[1].generate(1)
receive_result = self.nodes[1].zcrawreceive(zcsecretkey, pour_result["encryptedbucket1"])
assert_equal(receive_result["exists"], True)
pour_tx = self.nodes[1].createrawtransaction([], {})
pour_result = self.nodes[1].zcrawpour(pour_tx, {receive_result["bucket"] : zcsecretkey}, {zcaddress: 49.8}, 0, 0.1)
self.nodes[1].sendrawtransaction(pour_result["rawtxn"])
self.nodes[1].generate(1)
print "Syncing blocks..."
connect_nodes(self.nodes[1], 0)
sync_blocks(self.nodes[0:2])
print "Done!"
receive_result = self.nodes[0].zcrawreceive(zcsecretkey, pour_result["encryptedbucket1"])
assert_equal(receive_result["exists"], True)
def run_test(self):
# All nodes should start with 1,250 BTC:
starting_balance = 1250
for i in range(4):
assert_equal(self.nodes[i].getbalance(), starting_balance)
self.nodes[i].getnewaddress("") # bug workaround, coins generated assigned to first getnewaddress!
# Generate zcaddress keypairs
zckeypair1 = self.nodes[0].zcrawkeygen()
zcsecretkey1 = zckeypair1["zcsecretkey"]
zcaddress1 = zckeypair1["zcaddress"]
zckeypair2 = self.nodes[0].zcrawkeygen()
zcsecretkey2 = zckeypair2["zcsecretkey"]
zcaddress2 = zckeypair2["zcaddress"]
self.nodes[0].move("", "foo", 1220)
self.nodes[0].move("", "bar", 30)
assert_equal(self.nodes[0].getbalance(""), 0)
change_address = self.nodes[0].getnewaddress("foo")
# Pour some of our money into this address
(total_in, inputs) = gather_inputs(self.nodes[0], 1210)
outputs = {}
outputs[change_address] = 78
rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
pour_inputs = {}
pour_outputs = {}
pour_outputs[zcaddress1] = 100
pour_outputs[zcaddress2] = 800
exception_triggered = False
try:
pour_result = self.nodes[0].zcrawpour(rawtx, pour_inputs, pour_outputs, 0, 0)
except JSONRPCException:
exception_triggered = True
# We expect it to fail; the pour's balance equation isn't adding up.
assert_equal(exception_triggered, True)
pour_outputs[zcaddress1] = 370
pour_result = self.nodes[0].zcrawpour(rawtx, pour_inputs, pour_outputs, 1200, 30)
# This should succeed to construct a pour: the math adds up!
signed_tx_pour = self.nodes[0].signrawtransaction(pour_result["rawtxn"])
print signed_tx_pour
self.nodes[0].sendrawtransaction(signed_tx_pour["hex"])
self.send_pours_around()
if __name__ == '__main__':
PourTxTest().main()

135
qa/rpc-tests/zcpourdoublespend.py Executable file
View File

@ -0,0 +1,135 @@
#!/usr/bin/env python2
#
# Tests a Pour double-spend and a subsequent reorg.
#
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import *
from decimal import Decimal
import os
import shutil
import sys
class PourTxTest(BitcoinTestFramework):
def setup_network(self):
# Start with split network:
return super(PourTxTest, self).setup_network(True)
def expect_cannot_pour(self, node, txn):
exception_triggered = False
try:
node.sendrawtransaction(txn)
except JSONRPCException:
exception_triggered = True
assert_equal(exception_triggered, True)
def run_test(self):
# All nodes should start with 1,250 BTC:
starting_balance = 1250
for i in range(4):
assert_equal(self.nodes[i].getbalance(), starting_balance)
self.nodes[i].getnewaddress("") # bug workaround, coins generated assigned to first getnewaddress!
# Generate zcaddress keypairs
zckeypair = self.nodes[0].zcrawkeygen()
zcsecretkey = zckeypair["zcsecretkey"]
zcaddress = zckeypair["zcaddress"]
pool = [0, 1, 2, 3]
for i in range(4):
(total_in, inputs) = gather_inputs(self.nodes[i], 50)
pool[i] = self.nodes[i].createrawtransaction(inputs, {})
pool[i] = self.nodes[i].zcrawpour(pool[i], {}, {zcaddress:49.9}, 50, 0.1)
signed = self.nodes[i].signrawtransaction(pool[i]["rawtxn"])
self.nodes[0].sendrawtransaction(signed["hex"])
self.nodes[0].generate(1)
self.nodes[1].sendrawtransaction(signed["hex"])
self.nodes[1].generate(1)
pool[i] = pool[i]["encryptedbucket1"]
# Confirm that the protects have taken place
for i in range(4):
enc_bucket = pool[i]
receive_result = self.nodes[0].zcrawreceive(zcsecretkey, enc_bucket)
assert_equal(receive_result["exists"], True)
pool[i] = receive_result["bucket"]
# Extra confirmation on Node 1
receive_result = self.nodes[1].zcrawreceive(zcsecretkey, enc_bucket)
assert_equal(receive_result["exists"], True)
blank_tx = self.nodes[0].createrawtransaction([], {})
# Create pour {A, B}->{*}
pour_AB = self.nodes[0].zcrawpour(blank_tx,
{pool[0] : zcsecretkey, pool[1] : zcsecretkey},
{zcaddress:(49.9*2)-0.1},
0, 0.1)
# Create pour {B, C}->{*}
pour_BC = self.nodes[0].zcrawpour(blank_tx,
{pool[1] : zcsecretkey, pool[2] : zcsecretkey},
{zcaddress:(49.9*2)-0.1},
0, 0.1)
# Create pour {C, D}->{*}
pour_CD = self.nodes[0].zcrawpour(blank_tx,
{pool[2] : zcsecretkey, pool[3] : zcsecretkey},
{zcaddress:(49.9*2)-0.1},
0, 0.1)
# Create pour {A, D}->{*}
pour_AD = self.nodes[0].zcrawpour(blank_tx,
{pool[0] : zcsecretkey, pool[3] : zcsecretkey},
{zcaddress:(49.9*2)-0.1},
0, 0.1)
# (a) Node 1 will spend pour AB, then attempt to
# double-spend it with BC. It should fail before and
# after Node 1 mines blocks.
#
# (b) Then, Node 2 will spend BC, and mine 5 blocks.
# Node 1 connects, and AB will be reorg'd from the chain.
# Any attempts to spend AB or CD should fail for
# both nodes.
#
# (c) Then, Node 1 will spend AD, which should work
# because the previous spend for A (AB) is considered
# invalid.
# (a)
self.nodes[0].sendrawtransaction(pour_AB["rawtxn"])
self.expect_cannot_pour(self.nodes[0], pour_BC["rawtxn"])
# Generate a block
self.nodes[0].generate(1)
self.expect_cannot_pour(self.nodes[0], pour_BC["rawtxn"])
# (b)
self.nodes[1].sendrawtransaction(pour_BC["rawtxn"])
self.nodes[1].generate(5)
# Connect the two nodes
connect_nodes(self.nodes[1], 0)
sync_blocks(self.nodes[0:2])
# AB, BC, CD should all be impossible to spend for each node.
self.expect_cannot_pour(self.nodes[0], pour_AB["rawtxn"])
self.expect_cannot_pour(self.nodes[0], pour_CD["rawtxn"])
self.expect_cannot_pour(self.nodes[1], pour_AB["rawtxn"])
self.expect_cannot_pour(self.nodes[1], pour_CD["rawtxn"])
# (c)
self.nodes[0].sendrawtransaction(pour_AD["rawtxn"])
self.nodes[0].generate(1)
if __name__ == '__main__':
PourTxTest().main()

View File

@ -140,11 +140,12 @@ public:
consensus.nMajorityEnforceBlockUpgrade = 51;
consensus.nMajorityRejectBlockOutdated = 75;
consensus.nMajorityWindow = 100;
consensus.powLimit = uint256S("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
consensus.fPowAllowMinDifficultyBlocks = true;
pchMessageStart[0] = 0x0b;
pchMessageStart[1] = 0x11;
pchMessageStart[2] = 0x09;
pchMessageStart[3] = 0x07;
pchMessageStart[0] = 0x0c;
pchMessageStart[1] = 0x12;
pchMessageStart[2] = 0x99;
pchMessageStart[3] = 0x17;
vAlertPubKey = ParseHex("04302390343f91cc401d56d68b123028bf52e5fca1939df127f63c6467cdf9c8e2c14b61104cf817d0b780da337893ecc4aaff1309e536162dabbdb45200ca2b0a");
nDefaultPort = 18333;
nMinerThreads = 0;
@ -153,16 +154,18 @@ public:
//! Modify the testnet genesis block so the timestamp is valid for a later start.
genesis.nTime = 1296688602;
genesis.nNonce = 414098458;
genesis.nBits = 0x207fffff;
genesis.nNonce = 2;
consensus.hashGenesisBlock = genesis.GetHash();
assert(consensus.hashGenesisBlock == uint256S("0x000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"));
assert(consensus.hashGenesisBlock == uint256S("0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206"));
vFixedSeeds.clear();
vSeeds.clear();
vSeeds.push_back(CDNSSeedData("alexykot.me", "testnet-seed.alexykot.me"));
vSeeds.push_back(CDNSSeedData("bitcoin.petertodd.org", "testnet-seed.bitcoin.petertodd.org"));
vSeeds.push_back(CDNSSeedData("bluematt.me", "testnet-seed.bluematt.me"));
vSeeds.push_back(CDNSSeedData("bitcoin.schildbach.de", "testnet-seed.bitcoin.schildbach.de"));
// TODO: set up bootstrapping
//vSeeds.push_back(CDNSSeedData("alexykot.me", "testnet-seed.alexykot.me"));
//vSeeds.push_back(CDNSSeedData("bitcoin.petertodd.org", "testnet-seed.bitcoin.petertodd.org"));
//vSeeds.push_back(CDNSSeedData("bluematt.me", "testnet-seed.bluematt.me"));
//vSeeds.push_back(CDNSSeedData("bitcoin.schildbach.de", "testnet-seed.bitcoin.schildbach.de"));
base58Prefixes[PUBKEY_ADDRESS] = std::vector<unsigned char>(1,111);
base58Prefixes[SCRIPT_ADDRESS] = std::vector<unsigned char>(1,196);
@ -181,10 +184,10 @@ public:
checkpointData = (Checkpoints::CCheckpointData) {
boost::assign::map_list_of
( 546, uint256S("000000002a936ca763904c3c35fce2f3556c559c0214345d31b1bcebf76acb70")),
1337966069,
1488,
300
( 0, consensus.hashGenesisBlock),
genesis.nTime,
0,
0
};
}

View File

@ -40,20 +40,34 @@ bool CCoins::Spend(uint32_t nPos)
Cleanup();
return true;
}
bool CCoinsView::GetAnchorAt(const uint256 &rt, libzerocash::IncrementalMerkleTree &tree) const { return false; }
bool CCoinsView::GetSerial(const uint256 &serial) const { return false; }
bool CCoinsView::GetCoins(const uint256 &txid, CCoins &coins) const { return false; }
bool CCoinsView::HaveCoins(const uint256 &txid) const { return false; }
uint256 CCoinsView::GetBestBlock() const { return uint256(); }
bool CCoinsView::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return false; }
uint256 CCoinsView::GetBestAnchor() const { return uint256(); };
bool CCoinsView::BatchWrite(CCoinsMap &mapCoins,
const uint256 &hashBlock,
const uint256 &hashAnchor,
CAnchorsMap &mapAnchors,
CSerialsMap &mapSerials) { return false; }
bool CCoinsView::GetStats(CCoinsStats &stats) const { return false; }
CCoinsViewBacked::CCoinsViewBacked(CCoinsView *viewIn) : base(viewIn) { }
bool CCoinsViewBacked::GetAnchorAt(const uint256 &rt, libzerocash::IncrementalMerkleTree &tree) const { return base->GetAnchorAt(rt, tree); }
bool CCoinsViewBacked::GetSerial(const uint256 &serial) const { return base->GetSerial(serial); }
bool CCoinsViewBacked::GetCoins(const uint256 &txid, CCoins &coins) const { return base->GetCoins(txid, coins); }
bool CCoinsViewBacked::HaveCoins(const uint256 &txid) const { return base->HaveCoins(txid); }
uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); }
uint256 CCoinsViewBacked::GetBestAnchor() const { return base->GetBestAnchor(); }
void CCoinsViewBacked::SetBackend(CCoinsView &viewIn) { base = &viewIn; }
bool CCoinsViewBacked::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) { return base->BatchWrite(mapCoins, hashBlock); }
bool CCoinsViewBacked::BatchWrite(CCoinsMap &mapCoins,
const uint256 &hashBlock,
const uint256 &hashAnchor,
CAnchorsMap &mapAnchors,
CSerialsMap &mapSerials) { return base->BatchWrite(mapCoins, hashBlock, hashAnchor, mapAnchors, mapSerials); }
bool CCoinsViewBacked::GetStats(CCoinsStats &stats) const { return base->GetStats(stats); }
CCoinsKeyHasher::CCoinsKeyHasher() : salt(GetRandHash()) {}
@ -87,6 +101,90 @@ CCoinsMap::const_iterator CCoinsViewCache::FetchCoins(const uint256 &txid) const
return ret;
}
bool CCoinsViewCache::GetAnchorAt(const uint256 &rt, libzerocash::IncrementalMerkleTree &tree) const {
CAnchorsMap::const_iterator it = cacheAnchors.find(rt);
if (it != cacheAnchors.end()) {
if (it->second.entered) {
tree.setTo(it->second.tree);
return true;
} else {
return false;
}
}
if (!base->GetAnchorAt(rt, tree)) {
return false;
}
CAnchorsMap::iterator ret = cacheAnchors.insert(std::make_pair(rt, CAnchorsCacheEntry())).first;
ret->second.entered = true;
ret->second.tree.setTo(tree);
return true;
}
bool CCoinsViewCache::GetSerial(const uint256 &serial) const {
CSerialsMap::iterator it = cacheSerials.find(serial);
if (it != cacheSerials.end())
return it->second.entered;
CSerialsCacheEntry entry;
bool tmp = base->GetSerial(serial);
entry.entered = tmp;
cacheSerials.insert(std::make_pair(serial, entry));
// TODO: cache usage
return tmp;
}
void CCoinsViewCache::PushAnchor(const libzerocash::IncrementalMerkleTree &tree) {
std::vector<unsigned char> newrt_v(32);
tree.getRootValue(newrt_v);
uint256 newrt(newrt_v);
auto currentRoot = GetBestAnchor();
// We don't want to overwrite an anchor we already have.
// This occurs when a block doesn't modify mapAnchors at all,
// because there are no pours. We could get around this a
// different way (make all blocks modify mapAnchors somehow)
// but this is simpler to reason about.
if (currentRoot != newrt) {
CAnchorsMap::iterator ret = cacheAnchors.insert(std::make_pair(newrt, CAnchorsCacheEntry())).first;
ret->second.entered = true;
ret->second.tree.setTo(tree);
ret->second.flags = CAnchorsCacheEntry::DIRTY;
hashAnchor = newrt;
}
}
void CCoinsViewCache::PopAnchor(const uint256 &newrt) {
auto currentRoot = GetBestAnchor();
// Blocks might not change the commitment tree, in which
// case restoring the "old" anchor during a reorg must
// have no effect.
if (currentRoot != newrt) {
CAnchorsMap::iterator ret = cacheAnchors.insert(std::make_pair(currentRoot, CAnchorsCacheEntry())).first;
ret->second.entered = false;
ret->second.flags = CAnchorsCacheEntry::DIRTY;
hashAnchor = newrt;
}
}
void CCoinsViewCache::SetSerial(const uint256 &serial, bool spent) {
std::pair<CSerialsMap::iterator, bool> ret = cacheSerials.insert(std::make_pair(serial, CSerialsCacheEntry()));
ret.first->second.entered = spent;
ret.first->second.flags |= CSerialsCacheEntry::DIRTY;
}
bool CCoinsViewCache::GetCoins(const uint256 &txid, CCoins &coins) const {
CCoinsMap::const_iterator it = FetchCoins(txid);
if (it != cacheCoins.end()) {
@ -141,11 +239,22 @@ uint256 CCoinsViewCache::GetBestBlock() const {
return hashBlock;
}
uint256 CCoinsViewCache::GetBestAnchor() const {
if (hashAnchor.IsNull())
hashAnchor = base->GetBestAnchor();
return hashAnchor;
}
void CCoinsViewCache::SetBestBlock(const uint256 &hashBlockIn) {
hashBlock = hashBlockIn;
}
bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn) {
bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins,
const uint256 &hashBlockIn,
const uint256 &hashAnchorIn,
CAnchorsMap &mapAnchors,
CSerialsMap &mapSerials) {
assert(!hasModifier);
for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end();) {
if (it->second.flags & CCoinsCacheEntry::DIRTY) { // Ignore non-dirty entries (optimization).
@ -181,13 +290,73 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlockIn
CCoinsMap::iterator itOld = it++;
mapCoins.erase(itOld);
}
for (CAnchorsMap::iterator child_it = mapAnchors.begin(); child_it != mapAnchors.end();)
{
if (child_it->second.flags & CAnchorsCacheEntry::DIRTY) {
CAnchorsMap::iterator parent_it = cacheAnchors.find(child_it->first);
if (parent_it == cacheAnchors.end()) {
if (child_it->second.entered) {
// Parent doesn't have an entry, but child has a new commitment root.
CAnchorsCacheEntry& entry = cacheAnchors[child_it->first];
entry.entered = true;
entry.tree.setTo(child_it->second.tree);
entry.flags = CAnchorsCacheEntry::DIRTY;
// TODO: cache usage
}
} else {
if (parent_it->second.entered != child_it->second.entered) {
// The parent may have removed the entry.
parent_it->second.entered = child_it->second.entered;
parent_it->second.flags |= CAnchorsCacheEntry::DIRTY;
}
}
}
CAnchorsMap::iterator itOld = child_it++;
mapAnchors.erase(itOld);
}
for (CSerialsMap::iterator child_it = mapSerials.begin(); child_it != mapSerials.end();)
{
if (child_it->second.flags & CSerialsCacheEntry::DIRTY) { // Ignore non-dirty entries (optimization).
CSerialsMap::iterator parent_it = cacheSerials.find(child_it->first);
if (parent_it == cacheSerials.end()) {
if (child_it->second.entered) {
// Parent doesn't have an entry, but child has a SPENT serial.
// Move the spent serial up.
CSerialsCacheEntry& entry = cacheSerials[child_it->first];
entry.entered = true;
entry.flags = CSerialsCacheEntry::DIRTY;
// TODO: cache usage
}
} else {
if (parent_it->second.entered != child_it->second.entered) {
parent_it->second.entered = child_it->second.entered;
parent_it->second.flags |= CSerialsCacheEntry::DIRTY;
}
}
}
CSerialsMap::iterator itOld = child_it++;
mapSerials.erase(itOld);
}
hashAnchor = hashAnchorIn;
hashBlock = hashBlockIn;
return true;
}
bool CCoinsViewCache::Flush() {
bool fOk = base->BatchWrite(cacheCoins, hashBlock);
bool fOk = base->BatchWrite(cacheCoins, hashBlock, hashAnchor, cacheAnchors, cacheSerials);
cacheCoins.clear();
cacheAnchors.clear();
cacheSerials.clear();
cachedCoinsUsage = 0;
return fOk;
}
@ -212,9 +381,35 @@ CAmount CCoinsViewCache::GetValueIn(const CTransaction& tx) const
for (unsigned int i = 0; i < tx.vin.size(); i++)
nResult += GetOutputFor(tx.vin[i]).nValue;
nResult += tx.GetPourValueIn();
return nResult;
}
bool CCoinsViewCache::HavePourRequirements(const CTransaction& tx) const
{
BOOST_FOREACH(const CPourTx &pour, tx.vpour)
{
BOOST_FOREACH(const uint256& serial, pour.serials)
{
if (GetSerial(serial)) {
// If the serial is set, this transaction
// double-spends!
return false;
}
}
libzerocash::IncrementalMerkleTree tree(INCREMENTAL_MERKLE_TREE_DEPTH);
if (!GetAnchorAt(pour.anchor, tree)) {
// If we do not have the anchor for the pour,
// it is invalid.
return false;
}
}
return true;
}
bool CCoinsViewCache::HaveInputs(const CTransaction& tx) const
{
if (!tx.IsCoinBase()) {

View File

@ -16,6 +16,9 @@
#include <boost/foreach.hpp>
#include <boost/unordered_map.hpp>
#include "libzerocash/IncrementalMerkleTree.h"
static const unsigned int INCREMENTAL_MERKLE_TREE_DEPTH = 20;
/**
* Pruned version of CTransaction: only retains metadata and unspent transaction outputs
@ -295,7 +298,34 @@ struct CCoinsCacheEntry
CCoinsCacheEntry() : coins(), flags(0) {}
};
struct CAnchorsCacheEntry
{
bool entered; // This will be false if the anchor is removed from the cache
libzerocash::IncrementalMerkleTree tree; // The tree itself
unsigned char flags;
enum Flags {
DIRTY = (1 << 0), // This cache entry is potentially different from the version in the parent view.
};
CAnchorsCacheEntry() : entered(false), flags(0), tree(INCREMENTAL_MERKLE_TREE_DEPTH) {}
};
struct CSerialsCacheEntry
{
bool entered; // If the serial is spent or not
unsigned char flags;
enum Flags {
DIRTY = (1 << 0), // This cache entry is potentially different from the version in the parent view.
};
CSerialsCacheEntry() : entered(false), flags(0) {}
};
typedef boost::unordered_map<uint256, CCoinsCacheEntry, CCoinsKeyHasher> CCoinsMap;
typedef boost::unordered_map<uint256, CAnchorsCacheEntry, CCoinsKeyHasher> CAnchorsMap;
typedef boost::unordered_map<uint256, CSerialsCacheEntry, CCoinsKeyHasher> CSerialsMap;
struct CCoinsStats
{
@ -315,6 +345,12 @@ struct CCoinsStats
class CCoinsView
{
public:
//! Retrieve the tree at a particular anchored root in the chain
virtual bool GetAnchorAt(const uint256 &rt, libzerocash::IncrementalMerkleTree &tree) const;
//! Determine whether a serial is spent or not
virtual bool GetSerial(const uint256 &serial) const;
//! Retrieve the CCoins (unspent transaction outputs) for a given txid
virtual bool GetCoins(const uint256 &txid, CCoins &coins) const;
@ -325,9 +361,16 @@ public:
//! Retrieve the block hash whose state this CCoinsView currently represents
virtual uint256 GetBestBlock() const;
//! Get the current "tip" or the latest anchored tree root in the chain
virtual uint256 GetBestAnchor() const;
//! Do a bulk modification (multiple CCoins changes + BestBlock change).
//! The passed mapCoins can be modified.
virtual bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);
virtual bool BatchWrite(CCoinsMap &mapCoins,
const uint256 &hashBlock,
const uint256 &hashAnchor,
CAnchorsMap &mapAnchors,
CSerialsMap &mapSerials);
//! Calculate statistics about the unspent transaction output set
virtual bool GetStats(CCoinsStats &stats) const;
@ -345,11 +388,18 @@ protected:
public:
CCoinsViewBacked(CCoinsView *viewIn);
bool GetAnchorAt(const uint256 &rt, libzerocash::IncrementalMerkleTree &tree) const;
bool GetSerial(const uint256 &serial) const;
bool GetCoins(const uint256 &txid, CCoins &coins) const;
bool HaveCoins(const uint256 &txid) const;
uint256 GetBestBlock() const;
uint256 GetBestAnchor() const;
void SetBackend(CCoinsView &viewIn);
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);
bool BatchWrite(CCoinsMap &mapCoins,
const uint256 &hashBlock,
const uint256 &hashAnchor,
CAnchorsMap &mapAnchors,
CSerialsMap &mapSerials);
bool GetStats(CCoinsStats &stats) const;
};
@ -390,6 +440,9 @@ protected:
*/
mutable uint256 hashBlock;
mutable CCoinsMap cacheCoins;
mutable uint256 hashAnchor;
mutable CAnchorsMap cacheAnchors;
mutable CSerialsMap cacheSerials;
/* Cached dynamic memory usage for the inner CCoins objects. */
mutable size_t cachedCoinsUsage;
@ -399,11 +452,30 @@ public:
~CCoinsViewCache();
// Standard CCoinsView methods
bool GetAnchorAt(const uint256 &rt, libzerocash::IncrementalMerkleTree &tree) const;
bool GetSerial(const uint256 &serial) const;
bool GetCoins(const uint256 &txid, CCoins &coins) const;
bool HaveCoins(const uint256 &txid) const;
uint256 GetBestBlock() const;
uint256 GetBestAnchor() const;
void SetBestBlock(const uint256 &hashBlock);
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);
bool BatchWrite(CCoinsMap &mapCoins,
const uint256 &hashBlock,
const uint256 &hashAnchor,
CAnchorsMap &mapAnchors,
CSerialsMap &mapSerials);
// Adds the tree to mapAnchors and sets the current commitment
// root to this root.
void PushAnchor(const libzerocash::IncrementalMerkleTree &tree);
// Removes the current commitment root from mapAnchors and sets
// the new current root.
void PopAnchor(const uint256 &rt);
// Marks a serial as spent or not.
void SetSerial(const uint256 &serial, bool spent);
/**
* Return a pointer to CCoins in the cache, or NULL if not found. This is
@ -445,6 +517,9 @@ public:
//! Check whether all prevouts of the transaction are present in the UTXO set represented by this view
bool HaveInputs(const CTransaction& tx) const;
//! Check whether all pour requirements (anchors/serials) are satisfied
bool HavePourRequirements(const CTransaction& tx) const;
//! Return priority of tx at height nHeight
double GetPriority(const CTransaction &tx, int nHeight) const;

View File

@ -30,8 +30,9 @@ private:
std::string strRejectReason;
unsigned char chRejectCode;
bool corruptionPossible;
bool pourVerify;
public:
CValidationState() : mode(MODE_VALID), nDoS(0), chRejectCode(0), corruptionPossible(false) {}
CValidationState() : mode(MODE_VALID), nDoS(0), chRejectCode(0), corruptionPossible(false), pourVerify(true) {}
bool DoS(int level, bool ret = false,
unsigned char chRejectCodeIn=0, std::string strRejectReasonIn="",
bool corruptionIn=false) {
@ -44,6 +45,12 @@ public:
mode = MODE_INVALID;
return ret;
}
bool SetPerformPourVerification(bool pourVerifyIn) {
pourVerify = pourVerifyIn;
}
bool PerformPourVerification() {
return pourVerify;
}
bool Invalid(bool ret = false,
unsigned char _chRejectCode=0, std::string _strRejectReason="") {
return DoS(0, ret, _chRejectCode, _strRejectReason);

View File

@ -47,8 +47,12 @@
#include <boost/thread.hpp>
#include <openssl/crypto.h>
#include "libsnark/common/profiling.hpp"
using namespace std;
libzerocash::ZerocashParams *pzerocashParams = NULL;
#ifdef ENABLE_WALLET
CWallet* pwalletMain = NULL;
#endif
@ -591,11 +595,52 @@ bool InitSanityCheck(void)
return true;
}
static void ZC_LoadParams()
{
struct timeval tv_start, tv_end;
float elapsed;
boost::filesystem::path pk_path = ZC_GetParamsDir() / "zc-testnet-public-alpha-proving.key";
boost::filesystem::path vk_path = ZC_GetParamsDir() / "zc-testnet-public-alpha-verification.key";
LogPrintf("Loading proving key from %s\n", pk_path.string().c_str());
gettimeofday(&tv_start, 0);
libzerocash::ZerocashParams::zerocash_pp::init_public_params();
auto pk_loaded = libzerocash::ZerocashParams::LoadProvingKeyFromFile(
pk_path.string(),
INCREMENTAL_MERKLE_TREE_DEPTH
);
gettimeofday(&tv_end, 0);
elapsed = float(tv_end.tv_sec-tv_start.tv_sec) + (tv_end.tv_usec-tv_start.tv_usec)/float(1000000);
LogPrintf("Loaded proving key in %fs seconds.\n", elapsed);
LogPrintf("Loading verification key from %s\n", vk_path.string().c_str());
gettimeofday(&tv_start, 0);
auto vk_loaded = libzerocash::ZerocashParams::LoadVerificationKeyFromFile(
vk_path.string(),
INCREMENTAL_MERKLE_TREE_DEPTH
);
gettimeofday(&tv_end, 0);
elapsed = float(tv_end.tv_sec-tv_start.tv_sec) + (tv_end.tv_usec-tv_start.tv_usec)/float(1000000);
LogPrintf("Loaded verification key in %fs seconds.\n", elapsed);
pzerocashParams = new libzerocash::ZerocashParams(
INCREMENTAL_MERKLE_TREE_DEPTH,
&pk_loaded,
&vk_loaded
);
}
/** Initialize bitcoin.
* @pre Parameters should be parsed and config file should be read.
*/
bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
{
// ********************************************************* Step 0: Load zcash params
ZC_LoadParams();
// ********************************************************* Step 1: setup
#ifdef _MSC_VER
// Turn off Microsoft heap dump noise
@ -1223,6 +1268,11 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
mempool.ReadFeeEstimates(est_filein);
fFeeEstimatesInitialized = true;
// These must be disabled for now, they are buggy and we probably don't
// want any of libsnark's profiling in production anyway.
libsnark::inhibit_profiling_info = true;
libsnark::inhibit_profiling_counters = true;
// ********************************************************* Step 8: load wallet
#ifdef ENABLE_WALLET
if (fDisableWallet) {

View File

@ -8,6 +8,8 @@
#include <string>
#include "libzerocash/ZerocashParams.h"
class CScheduler;
class CWallet;
@ -17,6 +19,7 @@ class thread_group;
} // namespace boost
extern CWallet* pwalletMain;
extern libzerocash::ZerocashParams* pzerocashParams;
void StartShutdown();
bool ShutdownRequested();

View File

@ -847,12 +847,16 @@ unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& in
bool CheckTransaction(const CTransaction& tx, CValidationState &state)
{
// Basic checks that don't depend on any context
if (tx.vin.empty())
// Transactions can contain empty `vin` and `vout` so long as
// `vpour` is non-empty.
if (tx.vin.empty() && tx.vpour.empty())
return state.DoS(10, error("CheckTransaction(): vin empty"),
REJECT_INVALID, "bad-txns-vin-empty");
if (tx.vout.empty())
if (tx.vout.empty() && tx.vpour.empty())
return state.DoS(10, error("CheckTransaction(): vout empty"),
REJECT_INVALID, "bad-txns-vout-empty");
// Size limits
if (::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION) > MAX_BLOCK_SIZE)
return state.DoS(100, error("CheckTransaction(): size limits failed"),
@ -874,6 +878,32 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state)
REJECT_INVALID, "bad-txns-txouttotal-toolarge");
}
// Ensure that pour values are well-formed
BOOST_FOREACH(const CPourTx& pour, tx.vpour)
{
if (pour.vpub_old < 0)
return state.DoS(100, error("CheckTransaction(): pour.vpub_old negative"),
REJECT_INVALID, "bad-txns-vpub_old-negative");
if (pour.vpub_new < 0)
return state.DoS(100, error("CheckTransaction(): pour.vpub_new negative"),
REJECT_INVALID, "bad-txns-vpub_new-negative");
if (pour.vpub_old > MAX_MONEY)
return state.DoS(100, error("CheckTransaction(): pour.vpub_old too high"),
REJECT_INVALID, "bad-txns-vpub_old-toolarge");
if (pour.vpub_new > MAX_MONEY)
return state.DoS(100, error("CheckTransaction(): pour.vpub_new too high"),
REJECT_INVALID, "bad-txns-vpub_new-toolarge");
nValueOut += pour.vpub_new;
if (!MoneyRange(nValueOut))
return state.DoS(100, error("CheckTransaction(): txout total out of range"),
REJECT_INVALID, "bad-txns-txouttotal-toolarge");
}
// Check for duplicate inputs
set<COutPoint> vInOutPoints;
BOOST_FOREACH(const CTxIn& txin, tx.vin)
@ -884,8 +914,27 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state)
vInOutPoints.insert(txin.prevout);
}
// Check for duplicate pour serials in this transaction
set<uint256> vPourSerials;
BOOST_FOREACH(const CPourTx& pour, tx.vpour)
{
BOOST_FOREACH(const uint256& serial, pour.serials)
{
if (vPourSerials.count(serial))
return state.DoS(100, error("CheckTransaction(): duplicate serials"),
REJECT_INVALID, "bad-pours-serials-duplicate");
vPourSerials.insert(serial);
}
}
if (tx.IsCoinBase())
{
// There should be no pours in a coinbase transaction
if (tx.vpour.size() > 0)
return state.DoS(100, error("CheckTransaction(): coinbase has pours"),
REJECT_INVALID, "bad-cb-has-pours");
if (tx.vin[0].scriptSig.size() < 2 || tx.vin[0].scriptSig.size() > 100)
return state.DoS(100, error("CheckTransaction(): coinbase script size"),
REJECT_INVALID, "bad-cb-length");
@ -896,6 +945,17 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state)
if (txin.prevout.IsNull())
return state.DoS(10, error("CheckTransaction(): prevout is null"),
REJECT_INVALID, "bad-txns-prevout-null");
// Ensure that zk-SNARKs verify
if (state.PerformPourVerification()) {
BOOST_FOREACH(const CPourTx &pour, tx.vpour) {
if (!pour.Verify(*pzerocashParams)) {
return state.DoS(100, error("CheckTransaction(): pour does not verify"),
REJECT_INVALID, "bad-txns-pour-verification-failed");
}
}
}
}
return true;
@ -976,6 +1036,14 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
return false;
}
}
BOOST_FOREACH(const CPourTx &pour, tx.vpour) {
BOOST_FOREACH(const uint256 &serial, pour.serials) {
if (pool.mapSerials.count(serial))
{
return false;
}
}
}
}
{
@ -1008,6 +1076,11 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
return state.Invalid(error("AcceptToMemoryPool: inputs already spent"),
REJECT_DUPLICATE, "bad-txns-inputs-spent");
// are the pour's requirements met?
if (!view.HavePourRequirements(tx))
return state.Invalid(error("AcceptToMemoryPool: pour requirements not met"),
REJECT_DUPLICATE, "bad-txns-pour-requirements-not-met");
// Bring the best block into scope
view.GetBestBlock();
@ -1426,6 +1499,13 @@ void UpdateCoins(const CTransaction& tx, CValidationState &state, CCoinsViewCach
}
}
// spend serials
BOOST_FOREACH(const CPourTx &pour, tx.vpour) {
BOOST_FOREACH(const uint256 &serial, pour.serials) {
inputs.SetSerial(serial, true);
}
}
// add outputs
inputs.ModifyCoins(tx.GetHash())->FromTx(tx, nHeight);
}
@ -1456,6 +1536,10 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi
if (!inputs.HaveInputs(tx))
return state.Invalid(error("CheckInputs(): %s inputs unavailable", tx.GetHash().ToString()));
// are the pour's requirements met?
if (!inputs.HavePourRequirements(tx))
return state.Invalid(error("CheckInputs(): %s pour requirements not met", tx.GetHash().ToString()));
// While checking, GetBestBlock() refers to the parent block.
// This is also true for mempool checks.
CBlockIndex *pindexPrev = mapBlockIndex.find(inputs.GetBestBlock())->second;
@ -1484,6 +1568,11 @@ bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsVi
}
nValueIn += tx.GetPourValueIn();
if (!MoneyRange(nValueIn))
return state.DoS(100, error("CheckInputs(): vpub_old values out of range"),
REJECT_INVALID, "bad-txns-inputvalues-outofrange");
if (nValueIn < tx.GetValueOut())
return state.DoS(100, error("CheckInputs(): %s value in (%s) < value out (%s)",
tx.GetHash().ToString(), FormatMoney(nValueIn), FormatMoney(tx.GetValueOut())),
@ -1698,6 +1787,13 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex
outs->Clear();
}
// unspend serials
BOOST_FOREACH(const CPourTx &pour, tx.vpour) {
BOOST_FOREACH(const uint256 &serial, pour.serials) {
view.SetSerial(serial, false);
}
}
// restore inputs
if (i > 0) { // not coinbases
const CTxUndo &txundo = blockUndo.vtxundo[i-1];
@ -1712,6 +1808,9 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex
}
}
// set the old best anchor back
view.PopAnchor(blockUndo.old_tree_root);
// move best block pointer to prevout block
view.SetBestBlock(pindex->pprev->GetBlockHash());
@ -1899,6 +1998,25 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
std::vector<std::pair<uint256, CDiskTxPos> > vPos;
vPos.reserve(block.vtx.size());
blockundo.vtxundo.reserve(block.vtx.size() - 1);
// Construct the incremental merkle tree at the current
// block position,
auto old_tree_root = view.GetBestAnchor();
libzerocash::IncrementalMerkleTree tree(INCREMENTAL_MERKLE_TREE_DEPTH);
// This should never fail: we should always be able to get the root
// that is on the tip of our chain
assert(view.GetAnchorAt(old_tree_root, tree));
{
// Consistency check: the root of the tree we're given should
// match what we asked for.
std::vector<unsigned char> newrt_v(32);
tree.getRootValue(newrt_v);
uint256 anchor_received = uint256(newrt_v);
assert(anchor_received == old_tree_root);
}
for (unsigned int i = 0; i < block.vtx.size(); i++)
{
const CTransaction &tx = block.vtx[i];
@ -1915,6 +2033,11 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
return state.DoS(100, error("ConnectBlock(): inputs missing/spent"),
REJECT_INVALID, "bad-txns-inputs-missingorspent");
// are the pour's requirements met?
if (!view.HavePourRequirements(tx))
return state.DoS(100, error("ConnectBlock(): pour requirements not met"),
REJECT_INVALID, "bad-txns-pour-requirements-not-met");
if (fStrictPayToScriptHash)
{
// Add in sigops done by pay-to-script-hash inputs;
@ -1940,9 +2063,26 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
}
UpdateCoins(tx, state, view, i == 0 ? undoDummy : blockundo.vtxundo.back(), pindex->nHeight);
BOOST_FOREACH(const CPourTx &pour, tx.vpour) {
BOOST_FOREACH(const uint256 &bucket_commitment, pour.commitments) {
// Insert the bucket commitments into our temporary tree.
std::vector<bool> index;
std::vector<unsigned char> commitment_value(bucket_commitment.begin(), bucket_commitment.end());
std::vector<bool> commitment_bv(ZC_CM_SIZE * 8);
libzerocash::convertBytesVectorToVector(commitment_value, commitment_bv);
tree.insertElement(commitment_bv, index);
}
}
vPos.push_back(std::make_pair(tx.GetHash(), pos));
pos.nTxOffset += ::GetSerializeSize(tx, SER_DISK, CLIENT_VERSION);
}
tree.prune(); // prune it, so we don't cache intermediate states we don't need
view.PushAnchor(tree);
blockundo.old_tree_root = old_tree_root;
int64_t nTime1 = GetTimeMicros(); nTimeConnect += nTime1 - nTimeStart;
LogPrint("bench", " - Connect %u transactions: %.2fms (%.3fms/tx, %.3fms/txin) [%.2fs]\n", (unsigned)block.vtx.size(), 0.001 * (nTime1 - nTimeStart), 0.001 * (nTime1 - nTimeStart) / block.vtx.size(), nInputs <= 1 ? 0 : 0.001 * (nTime1 - nTimeStart) / (nInputs-1), nTimeConnect * 0.000001);
@ -2171,6 +2311,7 @@ bool static DisconnectTip(CValidationState &state) {
if (!ReadBlockFromDisk(block, pindexDelete))
return AbortNode(state, "Failed to read block");
// Apply the block atomically to the chain state.
uint256 anchorBeforeDisconnect = pcoinsTip->GetBestAnchor();
int64_t nStart = GetTimeMicros();
{
CCoinsViewCache view(pcoinsTip);
@ -2179,6 +2320,7 @@ bool static DisconnectTip(CValidationState &state) {
assert(view.Flush());
}
LogPrint("bench", "- Disconnect block: %.2fms\n", (GetTimeMicros() - nStart) * 0.001);
uint256 anchorAfterDisconnect = pcoinsTip->GetBestAnchor();
// Write the chain state to disk, if necessary.
if (!FlushStateToDisk(state, FLUSH_STATE_IF_NEEDED))
return false;
@ -2190,6 +2332,11 @@ bool static DisconnectTip(CValidationState &state) {
if (tx.IsCoinBase() || !AcceptToMemoryPool(mempool, stateDummy, tx, false, NULL))
mempool.remove(tx, removed, true);
}
if (anchorBeforeDisconnect != anchorAfterDisconnect) {
// The anchor may not change between block disconnects,
// in which case we don't want to evict from the mempool yet!
mempool.removeWithAnchor(anchorBeforeDisconnect);
}
mempool.removeCoinbaseSpends(pcoinsTip, pindexDelete->nHeight);
mempool.check(pcoinsTip);
// Update chainActive and related variables.
@ -4482,7 +4629,8 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
BOOST_FOREACH(uint256 hash, vEraseQueue)
EraseOrphanTx(hash);
}
else if (fMissingInputs)
// TODO: currently, prohibit pours from entering mapOrphans
else if (fMissingInputs && tx.vpour.size() == 0)
{
AddOrphanTx(tx, pfrom->GetId());

View File

@ -9,6 +9,87 @@
#include "tinyformat.h"
#include "utilstrencodings.h"
#include "libzerocash/PourProver.h"
#include "libzerocash/PourTransaction.h"
template<std::size_t N>
boost::array<std::vector<unsigned char>, N> uint256_to_array(const boost::array<uint256, N>& in) {
boost::array<std::vector<unsigned char>, N> result;
for (size_t i = 0; i < N; i++) {
result[i] = std::vector<unsigned char>(in[i].begin(), in[i].end());
}
return result;
}
template<std::size_t N>
boost::array<uint256, N> unsigned_char_vector_array_to_uint256_array(const boost::array<std::vector<unsigned char>, N>& in) {
boost::array<uint256, N> result;
for (size_t i = 0; i < N; i++) {
result[i] = uint256(in[i]);
}
return result;
}
CPourTx::CPourTx(ZerocashParams& params,
const CScript& scriptPubKey,
const uint256& anchor,
const boost::array<PourInput, NUM_POUR_INPUTS>& inputs,
const boost::array<PourOutput, NUM_POUR_OUTPUTS>& outputs,
CAmount vpub_old,
CAmount vpub_new) : scriptSig(), scriptPubKey(scriptPubKey), vpub_old(vpub_old), vpub_new(vpub_new), anchor(anchor)
{
uint256 scriptPubKeyHash;
{
CHashWriter ss(SER_GETHASH, 0);
ss << scriptPubKey;
scriptPubKeyHash = ss.GetHash();
}
PourTransaction pourtx(params,
std::vector<unsigned char>(scriptPubKeyHash.begin(), scriptPubKeyHash.end()),
std::vector<unsigned char>(anchor.begin(), anchor.end()),
std::vector<PourInput>(inputs.begin(), inputs.end()),
std::vector<PourOutput>(outputs.begin(), outputs.end()),
vpub_old,
vpub_new);
boost::array<std::vector<unsigned char>, NUM_POUR_INPUTS> serials_bv;
boost::array<std::vector<unsigned char>, NUM_POUR_OUTPUTS> commitments_bv;
boost::array<std::vector<unsigned char>, NUM_POUR_INPUTS> macs_bv;
boost::array<std::string, NUM_POUR_OUTPUTS> ciphertexts_bv;
proof = pourtx.unpack(serials_bv, commitments_bv, macs_bv, ciphertexts_bv);
serials = unsigned_char_vector_array_to_uint256_array(serials_bv);
commitments = unsigned_char_vector_array_to_uint256_array(commitments_bv);
macs = unsigned_char_vector_array_to_uint256_array(macs_bv);
ciphertexts = ciphertexts_bv;
}
bool CPourTx::Verify(ZerocashParams& params) const {
// Compute the hash of the scriptPubKey.
uint256 scriptPubKeyHash;
{
CHashWriter ss(SER_GETHASH, 0);
ss << scriptPubKey;
scriptPubKeyHash = ss.GetHash();
}
return PourProver::VerifyProof(
params,
std::vector<unsigned char>(scriptPubKeyHash.begin(), scriptPubKeyHash.end()),
std::vector<unsigned char>(anchor.begin(), anchor.end()),
vpub_old,
vpub_new,
uint256_to_array<NUM_POUR_INPUTS>(serials),
uint256_to_array<NUM_POUR_OUTPUTS>(commitments),
uint256_to_array<NUM_POUR_INPUTS>(macs),
proof
);
}
std::string COutPoint::ToString() const
{
return strprintf("COutPoint(%s, %u)", hash.ToString().substr(0,10), n);
@ -60,7 +141,7 @@ std::string CTxOut::ToString() const
}
CMutableTransaction::CMutableTransaction() : nVersion(CTransaction::CURRENT_VERSION), nLockTime(0) {}
CMutableTransaction::CMutableTransaction(const CTransaction& tx) : nVersion(tx.nVersion), vin(tx.vin), vout(tx.vout), nLockTime(tx.nLockTime) {}
CMutableTransaction::CMutableTransaction(const CTransaction& tx) : nVersion(tx.nVersion), vin(tx.vin), vout(tx.vout), nLockTime(tx.nLockTime), vpour(tx.vpour) {}
uint256 CMutableTransaction::GetHash() const
{
@ -72,9 +153,9 @@ void CTransaction::UpdateHash() const
*const_cast<uint256*>(&hash) = SerializeHash(*this);
}
CTransaction::CTransaction() : nVersion(CTransaction::CURRENT_VERSION), vin(), vout(), nLockTime(0) { }
CTransaction::CTransaction() : nVersion(CTransaction::CURRENT_VERSION), vin(), vout(), nLockTime(0), vpour() { }
CTransaction::CTransaction(const CMutableTransaction &tx) : nVersion(tx.nVersion), vin(tx.vin), vout(tx.vout), nLockTime(tx.nLockTime) {
CTransaction::CTransaction(const CMutableTransaction &tx) : nVersion(tx.nVersion), vin(tx.vin), vout(tx.vout), nLockTime(tx.nLockTime), vpour(tx.vpour) {
UpdateHash();
}
@ -83,6 +164,7 @@ CTransaction& CTransaction::operator=(const CTransaction &tx) {
*const_cast<std::vector<CTxIn>*>(&vin) = tx.vin;
*const_cast<std::vector<CTxOut>*>(&vout) = tx.vout;
*const_cast<unsigned int*>(&nLockTime) = tx.nLockTime;
*const_cast<std::vector<CPourTx>*>(&vpour) = tx.vpour;
*const_cast<uint256*>(&hash) = tx.hash;
return *this;
}
@ -96,9 +178,33 @@ CAmount CTransaction::GetValueOut() const
if (!MoneyRange(it->nValue) || !MoneyRange(nValueOut))
throw std::runtime_error("CTransaction::GetValueOut(): value out of range");
}
for (std::vector<CPourTx>::const_iterator it(vpour.begin()); it != vpour.end(); ++it)
{
// NB: vpub_old "takes" money from the value pool just as outputs do
nValueOut += it->vpub_old;
if (!MoneyRange(it->vpub_old) || !MoneyRange(nValueOut))
throw std::runtime_error("CTransaction::GetValueOut(): value out of range");
}
return nValueOut;
}
CAmount CTransaction::GetPourValueIn() const
{
CAmount nValue = 0;
for (std::vector<CPourTx>::const_iterator it(vpour.begin()); it != vpour.end(); ++it)
{
// NB: vpub_new "gives" money to the value pool just as inputs do
nValue += it->vpub_new;
if (!MoneyRange(it->vpub_new) || !MoneyRange(nValue))
throw std::runtime_error("CTransaction::GetPourValueIn(): value out of range");
}
return nValue;
}
double CTransaction::ComputePriority(double dPriorityInputs, unsigned int nTxSize) const
{
nTxSize = CalculateModifiedSize(nTxSize);

View File

@ -11,6 +11,121 @@
#include "serialize.h"
#include "uint256.h"
#include <boost/array.hpp>
#include "libzerocash/ZerocashParams.h"
#include "libzerocash/PourInput.h"
#include "libzerocash/PourOutput.h"
using namespace libzerocash;
static const unsigned int NUM_POUR_INPUTS = 2;
static const unsigned int NUM_POUR_OUTPUTS = 2;
class CPourTx
{
public:
// These values 'enter from' and 'exit to' the value
// pool, respectively.
CAmount vpub_old;
CAmount vpub_new;
// These scripts are used to bind a Pour to the outer
// transaction it is placed in. The Pour will
// authenticate the hash of the scriptPubKey, and the
// provided scriptSig with be appended during
// transaction verification.
CScript scriptPubKey;
CScript scriptSig;
// Pours are always anchored to a root in the bucket
// commitment tree at some point in the blockchain
// history or in the history of the current
// transaction.
uint256 anchor;
// Serials are used to prevent double-spends. They
// are derived from the secrets placed in the bucket
// and the secret spend-authority key known by the
// spender.
boost::array<uint256, NUM_POUR_INPUTS> serials;
// Bucket commitments are introduced into the commitment
// tree, blinding the public about the values and
// destinations involved in the Pour. The presence of a
// commitment in the bucket commitment tree is required
// to spend it.
boost::array<uint256, NUM_POUR_OUTPUTS> commitments;
// Ciphertexts
// These are encrypted using ECIES. They are used to
// transfer metadata and seeds to generate trapdoors
// for the recipient to spend the value.
boost::array<std::string, NUM_POUR_OUTPUTS> ciphertexts;
// MACs
// The verification of the pour requires these MACs
// to be provided as an input.
boost::array<uint256, NUM_POUR_INPUTS> macs;
// Pour proof
// This is a zk-SNARK which ensures that this pour is valid.
std::string proof;
CPourTx(): vpub_old(0), vpub_new(0), scriptPubKey(), scriptSig(), anchor(), serials(), commitments(), ciphertexts(), macs(), proof() {
}
CPourTx(ZerocashParams& params,
const CScript& scriptPubKey,
const uint256& rt,
const boost::array<PourInput, NUM_POUR_INPUTS>& inputs,
const boost::array<PourOutput, NUM_POUR_OUTPUTS>& outputs,
CAmount vpub_old,
CAmount vpub_new
);
// Verifies that the pour proof is correct.
bool Verify(ZerocashParams& params) const;
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
READWRITE(vpub_old);
READWRITE(vpub_new);
READWRITE(scriptPubKey);
READWRITE(scriptSig);
READWRITE(anchor);
READWRITE(serials);
READWRITE(commitments);
READWRITE(ciphertexts);
READWRITE(macs);
READWRITE(proof);
}
friend bool operator==(const CPourTx& a, const CPourTx& b)
{
return (
a.vpub_old == b.vpub_old &&
a.vpub_new == b.vpub_new &&
a.scriptPubKey == b.scriptPubKey &&
a.scriptSig == b.scriptSig &&
a.anchor == b.anchor &&
a.serials == b.serials &&
a.commitments == b.commitments &&
a.ciphertexts == b.ciphertexts &&
a.macs == b.macs &&
a.proof == b.proof
);
}
friend bool operator!=(const CPourTx& a, const CPourTx& b)
{
return !(a == b);
}
};
/** An outpoint - a combination of a transaction hash and an index n into its vout */
class COutPoint
{
@ -192,6 +307,7 @@ public:
const std::vector<CTxIn> vin;
const std::vector<CTxOut> vout;
const uint32_t nLockTime;
const std::vector<CPourTx> vpour;
/** Construct a CTransaction that qualifies as IsNull() */
CTransaction();
@ -210,6 +326,9 @@ public:
READWRITE(*const_cast<std::vector<CTxIn>*>(&vin));
READWRITE(*const_cast<std::vector<CTxOut>*>(&vout));
READWRITE(*const_cast<uint32_t*>(&nLockTime));
if (nVersion >= 2) {
READWRITE(*const_cast<std::vector<CPourTx>*>(&vpour));
}
if (ser_action.ForRead())
UpdateHash();
}
@ -227,6 +346,9 @@ public:
// GetValueIn() is a method on CCoinsViewCache, because
// inputs must be known to compute value in.
// Return sum of pour vpub_new
CAmount GetPourValueIn() const;
// Compute priority, given priority of inputs and (optionally) tx size
double ComputePriority(double dPriorityInputs, unsigned int nTxSize=0) const;
@ -258,6 +380,7 @@ struct CMutableTransaction
std::vector<CTxIn> vin;
std::vector<CTxOut> vout;
uint32_t nLockTime;
std::vector<CPourTx> vpour;
CMutableTransaction();
CMutableTransaction(const CTransaction& tx);
@ -271,6 +394,9 @@ struct CMutableTransaction
READWRITE(vin);
READWRITE(vout);
READWRITE(nLockTime);
if (nVersion >= 2) {
READWRITE(vpour);
}
}
/** Compute the hash of this CMutableTransaction. This is computed on the

View File

@ -91,6 +91,10 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "estimatepriority", 0 },
{ "prioritisetransaction", 1 },
{ "prioritisetransaction", 2 },
{ "zcrawpour", 1 },
{ "zcrawpour", 2 },
{ "zcrawpour", 3 },
{ "zcrawpour", 4 }
};
class CRPCConvertTable

View File

@ -376,6 +376,9 @@ static const CRPCCommand vRPCCommands[] =
{ "wallet", "walletlock", &walletlock, true },
{ "wallet", "walletpassphrasechange", &walletpassphrasechange, true },
{ "wallet", "walletpassphrase", &walletpassphrase, true },
{ "wallet", "zcrawkeygen", &zc_raw_keygen, true },
{ "wallet", "zcrawpour", &zc_raw_pour, true },
{ "wallet", "zcrawreceive", &zc_raw_receive, true }
#endif // ENABLE_WALLET
};

View File

@ -208,6 +208,9 @@ extern json_spirit::Value getblockchaininfo(const json_spirit::Array& params, bo
extern json_spirit::Value getnetworkinfo(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value setmocktime(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value resendwallettransactions(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value zc_raw_keygen(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value zc_raw_pour(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value zc_raw_receive(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getrawtransaction(const json_spirit::Array& params, bool fHelp); // in rcprawtransaction.cpp
extern json_spirit::Value listunspent(const json_spirit::Array& params, bool fHelp);

View File

@ -1070,6 +1070,26 @@ public:
SerializeOutput(s, nOutput, nType, nVersion);
// Serialize nLockTime
::Serialize(s, txTo.nLockTime, nType, nVersion);
// Serialize vpour
if (txTo.nVersion >= 2) {
// TODO:
//
// SIGHASH_* functions will hash portions of
// the transaction for use in signatures. This
// keeps the pour cryptographically bound to
// the transaction from the perspective of the
// inputs (but not from the perspective of the
// pour).
//
// This must be rectified in the future.
// See zcash/#529
//
// It will be necessary to change this API to
// be abstract over whether an input script is
// being skipped or a pour is being skipped.
::Serialize(s, txTo.vpour, nType, nVersion);
}
}
};

View File

@ -20,6 +20,8 @@
#include <utility>
#include <vector>
#include <boost/array.hpp>
class CScript;
static const unsigned int MAX_SIZE = 0x02000000;
@ -506,6 +508,13 @@ extern inline unsigned int GetSerializeSize(const CScript& v, int nType, int nVe
template<typename Stream> void Serialize(Stream& os, const CScript& v, int nType, int nVersion);
template<typename Stream> void Unserialize(Stream& is, CScript& v, int nType, int nVersion);
/**
* array
*/
template<typename T, std::size_t N> unsigned int GetSerializeSize(const boost::array<T, N> &item, int nType, int nVersion);
template<typename Stream, typename T, std::size_t N> void Serialize(Stream& os, const boost::array<T, N>& item, int nType, int nVersion);
template<typename Stream, typename T, std::size_t N> void Unserialize(Stream& is, boost::array<T, N>& item, int nType, int nVersion);
/**
* pair
*/
@ -698,6 +707,35 @@ void Unserialize(Stream& is, CScript& v, int nType, int nVersion)
}
/**
* array
*/
template<typename T, std::size_t N>
unsigned int GetSerializeSize(const boost::array<T, N> &item, int nType, int nVersion)
{
unsigned int size = 0;
for (size_t i = 0; i < N; i++) {
size += GetSerializeSize(item[0], nType, nVersion);
}
return size;
}
template<typename Stream, typename T, std::size_t N>
void Serialize(Stream& os, const boost::array<T, N>& item, int nType, int nVersion)
{
for (size_t i = 0; i < N; i++) {
Serialize(os, item[i], nType, nVersion);
}
}
template<typename Stream, typename T, std::size_t N>
void Unserialize(Stream& is, boost::array<T, N>& item, int nType, int nVersion)
{
for (size_t i = 0; i < N; i++) {
Unserialize(is, item[i], nType, nVersion);
}
}
/**
* pair

View File

@ -11,15 +11,50 @@
#include <map>
#include <boost/test/unit_test.hpp>
#include "libzerocash/IncrementalMerkleTree.h"
namespace
{
class CCoinsViewTest : public CCoinsView
{
uint256 hashBestBlock_;
uint256 hashBestAnchor_;
std::map<uint256, CCoins> map_;
std::map<uint256, libzerocash::IncrementalMerkleTree> mapAnchors_;
std::map<uint256, bool> mapSerials_;
public:
bool GetAnchorAt(const uint256& rt, libzerocash::IncrementalMerkleTree &tree) const {
if (rt.IsNull()) {
IncrementalMerkleTree new_tree(INCREMENTAL_MERKLE_TREE_DEPTH);
tree.setTo(new_tree);
return true;
}
std::map<uint256, libzerocash::IncrementalMerkleTree>::const_iterator it = mapAnchors_.find(rt);
if (it == mapAnchors_.end()) {
return false;
} else {
tree.setTo(it->second);
return true;
}
}
bool GetSerial(const uint256 &serial) const
{
std::map<uint256, bool>::const_iterator it = mapSerials_.find(serial);
if (it == mapSerials_.end()) {
return false;
} else {
// The map shouldn't contain any false entries.
assert(it->second);
return true;
}
}
uint256 GetBestAnchor() const { return hashBestAnchor_; }
bool GetCoins(const uint256& txid, CCoins& coins) const
{
std::map<uint256, CCoins>::const_iterator it = map_.find(txid);
@ -42,7 +77,11 @@ public:
uint256 GetBestBlock() const { return hashBestBlock_; }
bool BatchWrite(CCoinsMap& mapCoins, const uint256& hashBlock)
bool BatchWrite(CCoinsMap& mapCoins,
const uint256& hashBlock,
const uint256& hashAnchor,
CAnchorsMap& mapAnchors,
CSerialsMap& mapSerials)
{
for (CCoinsMap::iterator it = mapCoins.begin(); it != mapCoins.end(); ) {
map_[it->first] = it->second.coins;
@ -52,8 +91,30 @@ public:
}
mapCoins.erase(it++);
}
for (CAnchorsMap::iterator it = mapAnchors.begin(); it != mapAnchors.end(); ) {
if (it->second.entered) {
std::map<uint256, libzerocash::IncrementalMerkleTree>::iterator ret =
mapAnchors_.insert(std::make_pair(it->first, IncrementalMerkleTree(INCREMENTAL_MERKLE_TREE_DEPTH))).first;
ret->second.setTo(it->second.tree);
} else {
mapAnchors_.erase(it->first);
}
mapAnchors.erase(it++);
}
for (CSerialsMap::iterator it = mapSerials.begin(); it != mapSerials.end(); ) {
if (it->second.entered) {
mapSerials_[it->first] = true;
} else {
mapSerials_.erase(it->first);
}
mapSerials.erase(it++);
}
mapCoins.clear();
mapAnchors.clear();
mapSerials.clear();
hashBestBlock_ = hashBlock;
hashBestAnchor_ = hashAnchor;
return true;
}
@ -79,6 +140,196 @@ public:
}
BOOST_AUTO_TEST_CASE(serials_test)
{
CCoinsViewTest base;
CCoinsViewCacheTest cache(&base);
uint256 myserial = GetRandHash();
BOOST_CHECK(!cache.GetSerial(myserial));
cache.SetSerial(myserial, true);
BOOST_CHECK(cache.GetSerial(myserial));
cache.Flush();
CCoinsViewCacheTest cache2(&base);
BOOST_CHECK(cache2.GetSerial(myserial));
cache2.SetSerial(myserial, false);
BOOST_CHECK(!cache2.GetSerial(myserial));
cache2.Flush();
CCoinsViewCacheTest cache3(&base);
BOOST_CHECK(!cache3.GetSerial(myserial));
}
void appendRandomCommitment(IncrementalMerkleTree &tree)
{
Address addr = Address::CreateNewRandomAddress();
Coin coin(addr.getPublicAddress(), 100);
std::vector<bool> commitment(ZC_CM_SIZE * 8);
convertBytesVectorToVector(coin.getCoinCommitment().getCommitmentValue(), commitment);
std::vector<bool> index;
tree.insertElement(commitment, index);
}
BOOST_AUTO_TEST_CASE(anchors_flush_test)
{
CCoinsViewTest base;
uint256 newrt;
{
CCoinsViewCacheTest cache(&base);
IncrementalMerkleTree tree(INCREMENTAL_MERKLE_TREE_DEPTH);
BOOST_CHECK(cache.GetAnchorAt(cache.GetBestAnchor(), tree));
appendRandomCommitment(tree);
{
std::vector<unsigned char> newrt_v(32);
tree.getRootValue(newrt_v);
newrt = uint256(newrt_v);
}
cache.PushAnchor(tree);
cache.Flush();
}
{
CCoinsViewCacheTest cache(&base);
IncrementalMerkleTree tree(INCREMENTAL_MERKLE_TREE_DEPTH);
BOOST_CHECK(cache.GetAnchorAt(cache.GetBestAnchor(), tree));
// Get the cached entry.
BOOST_CHECK(cache.GetAnchorAt(cache.GetBestAnchor(), tree));
uint256 check_rt;
{
std::vector<unsigned char> newrt_v(32);
tree.getRootValue(newrt_v);
check_rt = uint256(newrt_v);
}
BOOST_CHECK(check_rt == newrt);
}
}
BOOST_AUTO_TEST_CASE(anchors_test)
{
// TODO: These tests should be more methodical.
// Or, integrate with Bitcoin's tests later.
CCoinsViewTest base;
CCoinsViewCacheTest cache(&base);
BOOST_CHECK(cache.GetBestAnchor() == uint256());
{
IncrementalMerkleTree tree(INCREMENTAL_MERKLE_TREE_DEPTH);
BOOST_CHECK(cache.GetAnchorAt(cache.GetBestAnchor(), tree));
appendRandomCommitment(tree);
appendRandomCommitment(tree);
appendRandomCommitment(tree);
appendRandomCommitment(tree);
appendRandomCommitment(tree);
appendRandomCommitment(tree);
appendRandomCommitment(tree);
tree.prune();
IncrementalMerkleTree save_tree_for_later(INCREMENTAL_MERKLE_TREE_DEPTH);
save_tree_for_later.setTo(tree);
uint256 newrt;
uint256 newrt2;
{
std::vector<unsigned char> newrt_v(32);
tree.getRootValue(newrt_v);
newrt = uint256(newrt_v);
}
cache.PushAnchor(tree);
BOOST_CHECK(cache.GetBestAnchor() == newrt);
{
IncrementalMerkleTree confirm_same(INCREMENTAL_MERKLE_TREE_DEPTH);
BOOST_CHECK(cache.GetAnchorAt(cache.GetBestAnchor(), confirm_same));
uint256 confirm_rt;
{
std::vector<unsigned char> newrt_v(32);
confirm_same.getRootValue(newrt_v);
confirm_rt = uint256(newrt_v);
}
BOOST_CHECK(confirm_rt == newrt);
}
appendRandomCommitment(tree);
appendRandomCommitment(tree);
tree.prune();
{
std::vector<unsigned char> newrt_v(32);
tree.getRootValue(newrt_v);
newrt2 = uint256(newrt_v);
}
cache.PushAnchor(tree);
BOOST_CHECK(cache.GetBestAnchor() == newrt2);
IncrementalMerkleTree test_tree(INCREMENTAL_MERKLE_TREE_DEPTH);
BOOST_CHECK(cache.GetAnchorAt(cache.GetBestAnchor(), test_tree));
{
std::vector<unsigned char> a(32);
std::vector<unsigned char> b(32);
tree.getRootValue(a);
test_tree.getRootValue(b);
BOOST_CHECK(a == b);
}
{
std::vector<unsigned char> a(32);
std::vector<unsigned char> b(32);
IncrementalMerkleTree test_tree2(INCREMENTAL_MERKLE_TREE_DEPTH);
cache.GetAnchorAt(newrt, test_tree2);
uint256 recovered_rt;
{
std::vector<unsigned char> newrt_v(32);
test_tree2.getRootValue(newrt_v);
recovered_rt = uint256(newrt_v);
}
BOOST_CHECK(recovered_rt == newrt);
}
{
cache.PopAnchor(newrt);
IncrementalMerkleTree obtain_tree(INCREMENTAL_MERKLE_TREE_DEPTH);
assert(!cache.GetAnchorAt(newrt2, obtain_tree)); // should have been popped off
assert(cache.GetAnchorAt(newrt, obtain_tree));
uint256 recovered_rt;
{
std::vector<unsigned char> newrt_v(32);
obtain_tree.getRootValue(newrt_v);
recovered_rt = uint256(newrt_v);
}
assert(recovered_rt == newrt);
}
}
}
BOOST_FIXTURE_TEST_SUITE(coins_tests, BasicTestingSetup)
static const unsigned int NUM_SIMULATION_ITERATIONS = 40000;

File diff suppressed because one or more lines are too long

View File

@ -6,6 +6,7 @@
#include "streams.h"
#include "hash.h"
#include "test/test_bitcoin.h"
#include "utilstrencodings.h"
#include <stdint.h>
@ -15,6 +16,39 @@ using namespace std;
BOOST_FIXTURE_TEST_SUITE(serialize_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(boost_arrays)
{
boost::array<std::string, 2> test_case = {string("zub"), string("baz")};
CDataStream ss(SER_DISK, 0);
ss << test_case;
auto hash = Hash(ss.begin(), ss.end());
BOOST_CHECK_MESSAGE("037a75620362617a" == HexStr(ss.begin(), ss.end()), HexStr(ss.begin(), ss.end()));
BOOST_CHECK_MESSAGE(hash == uint256S("13cb12b2dd098dced0064fe4897c97f907ba3ed36ae470c2e7fc2b1111eba35a"), "actually got: " << hash.ToString());
{
// note: boost array of size 2 should serialize to be the same as a tuple
std::pair<std::string, std::string> test_case_2 = {string("zub"), string("baz")};
CDataStream ss2(SER_DISK, 0);
ss2 << test_case_2;
auto hash2 = Hash(ss2.begin(), ss2.end());
BOOST_CHECK(hash == hash2);
}
boost::array<std::string, 2> decoded_test_case;
ss >> decoded_test_case;
BOOST_CHECK(decoded_test_case == test_case);
boost::array<int32_t, 2> test = {100, 200};
BOOST_CHECK_EQUAL(GetSerializeSize(test, 0, 0), 8);
}
BOOST_AUTO_TEST_CASE(sizes)
{
BOOST_CHECK_EQUAL(sizeof(char), GetSerializeSize(char(0), 0));

View File

@ -101,6 +101,7 @@ void static RandomTransaction(CMutableTransaction &tx, bool fSingle) {
tx.nLockTime = (insecure_rand() % 2) ? insecure_rand() : 0;
int ins = (insecure_rand() % 4) + 1;
int outs = fSingle ? ins : (insecure_rand() % 4) + 1;
int pours = (insecure_rand() % 4);
for (int in = 0; in < ins; in++) {
tx.vin.push_back(CTxIn());
CTxIn &txin = tx.vin.back();
@ -115,6 +116,32 @@ void static RandomTransaction(CMutableTransaction &tx, bool fSingle) {
txout.nValue = insecure_rand() % 100000000;
RandomScript(txout.scriptPubKey);
}
if (tx.nVersion >= 2) {
for (int pour = 0; pour < pours; pour++) {
CPourTx pourtx;
pourtx.vpub_old = insecure_rand() % 100000000;
pourtx.vpub_new = insecure_rand() % 100000000;
RandomScript(pourtx.scriptPubKey);
RandomScript(pourtx.scriptSig);
pourtx.anchor = GetRandHash();
pourtx.serials[0] = GetRandHash();
pourtx.serials[1] = GetRandHash();
pourtx.ciphertexts[0] = {insecure_rand() % 100, insecure_rand() % 100};
pourtx.ciphertexts[1] = {insecure_rand() % 100, insecure_rand() % 100};
pourtx.macs[0] = GetRandHash();
pourtx.macs[1] = GetRandHash();
{
std::vector<unsigned char> txt;
int prooflen = insecure_rand() % 1000;
for (int i = 0; i < prooflen; i++) {
txt.push_back(insecure_rand() % 256);
}
pourtx.proof = std::string(txt.begin(), txt.end());
}
tx.vpour.push_back(pourtx);
}
}
}
BOOST_FIXTURE_TEST_SUITE(sighash_tests, BasicTestingSetup)
@ -200,6 +227,7 @@ BOOST_AUTO_TEST_CASE(sighash_from_data)
stream >> tx;
CValidationState state;
state.SetPerformPourVerification(false); // don't verify the snark
BOOST_CHECK_MESSAGE(CheckTransaction(tx, state), strTest);
BOOST_CHECK(state.IsValid());

View File

@ -23,6 +23,7 @@
CClientUIInterface uiInterface; // Declared but not defined in ui_interface.h
CWallet* pwalletMain;
libzerocash::ZerocashParams *pzerocashParams;
extern bool fPrintToConsole;
extern void noui_connect();

View File

@ -14,6 +14,7 @@
#include "main.h"
#include "script/script.h"
#include "script/script_error.h"
#include "primitives/transaction.h"
#include <map>
#include <string>
@ -24,8 +25,16 @@
#include <boost/assign/list_of.hpp>
#include "json/json_spirit_writer_template.h"
#include "libzerocash/ZerocashParams.h"
#include "libzerocash/IncrementalMerkleTree.h"
#include "libzerocash/PourInput.h"
#include "libzerocash/PourOutput.h"
#include "libzerocash/Address.h"
#include "libzerocash/Coin.h"
using namespace std;
using namespace json_spirit;
using namespace libzerocash;
// In script_tests.cpp
extern Array read_json(const std::string& jsondata);
@ -285,6 +294,209 @@ SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsViewCache& coinsRet)
return dummyTransactions;
}
BOOST_AUTO_TEST_CASE(test_basic_pour_verification)
{
// We only check that pours are constructed properly
// and verify properly here. libsnark tends to segfault
// when our snarks or what-have-you are invalid, so
// we can't really catch everything here.
//
// See #471, #520, #459 and probably others.
//
// There may be ways to use boost tests to catch failing
// threads or processes (?) but they appear to not work
// on all platforms and would gently push us down an ugly
// path. We should just fix the assertions.
//
// Also, it's generally libzerocash's job to ensure
// the integrity of the scheme through its own tests.
static const unsigned int TEST_TREE_DEPTH = 3;
// construct the r1cs keypair
auto keypair = ZerocashParams::GenerateNewKeyPair(TEST_TREE_DEPTH);
ZerocashParams p(
TEST_TREE_DEPTH,
&keypair
);
// construct a merkle tree
IncrementalMerkleTree merkleTree(TEST_TREE_DEPTH);
Address addr = Address::CreateNewRandomAddress();
Coin coin(addr.getPublicAddress(), 100);
// commitment from coin
std::vector<bool> commitment(ZC_CM_SIZE * 8);
convertBytesVectorToVector(coin.getCoinCommitment().getCommitmentValue(), commitment);
// insert commitment into the merkle tree
std::vector<bool> index;
merkleTree.insertElement(commitment, index);
// compute the merkle root we will be working with
vector<unsigned char> rt(ZC_ROOT_SIZE);
{
vector<bool> root_bv(ZC_ROOT_SIZE * 8);
merkleTree.getRootValue(root_bv);
convertVectorToBytesVector(root_bv, rt);
}
merkle_authentication_path path(TEST_TREE_DEPTH);
merkleTree.getWitness(index, path);
// create CPourTx
CScript scriptPubKey;
boost::array<PourInput, NUM_POUR_INPUTS> inputs = {
PourInput(coin, addr, convertVectorToInt(index), path),
PourInput(TEST_TREE_DEPTH) // dummy input of zero value
};
boost::array<PourOutput, NUM_POUR_OUTPUTS> outputs = {
PourOutput(50),
PourOutput(50)
};
{
CPourTx pourtx(p, scriptPubKey, uint256(rt), inputs, outputs, 0, 0);
BOOST_CHECK(pourtx.Verify(p));
CDataStream ss(SER_DISK, CLIENT_VERSION);
ss << pourtx;
CPourTx pourtx_deserialized;
ss >> pourtx_deserialized;
BOOST_CHECK(pourtx_deserialized == pourtx);
BOOST_CHECK(pourtx_deserialized.Verify(p));
}
{
// Ensure that the balance equation is working.
BOOST_CHECK_THROW(CPourTx(p, scriptPubKey, uint256(rt), inputs, outputs, 10, 0), std::invalid_argument);
BOOST_CHECK_THROW(CPourTx(p, scriptPubKey, uint256(rt), inputs, outputs, 0, 10), std::invalid_argument);
}
{
// Ensure that it won't verify if the root is changed.
auto test = CPourTx(p, scriptPubKey, uint256(rt), inputs, outputs, 0, 0);
test.anchor = GetRandHash();
BOOST_CHECK(!test.Verify(p));
}
}
BOOST_AUTO_TEST_CASE(test_simple_pour_invalidity)
{
CMutableTransaction tx;
tx.nVersion = 2;
{
// Ensure that empty vin/vout remain invalid without
// pours.
CMutableTransaction newTx(tx);
CValidationState state;
state.SetPerformPourVerification(false); // don't verify the snark
// No pours, vin and vout, means it should be invalid.
BOOST_CHECK(!CheckTransaction(newTx, state));
BOOST_CHECK(state.GetRejectReason() == "bad-txns-vin-empty");
newTx.vin.push_back(CTxIn(uint256S("0000000000000000000000000000000000000000000000000000000000000001"), 0));
BOOST_CHECK(!CheckTransaction(newTx, state));
BOOST_CHECK(state.GetRejectReason() == "bad-txns-vout-empty");
newTx.vpour.push_back(CPourTx());
CPourTx *pourtx = &newTx.vpour[0];
pourtx->serials[0] = GetRandHash();
pourtx->serials[1] = GetRandHash();
BOOST_CHECK_MESSAGE(CheckTransaction(newTx, state), state.GetRejectReason());
}
{
// Ensure that values within the pour are well-formed.
CMutableTransaction newTx(tx);
CValidationState state;
newTx.vpour.push_back(CPourTx());
CPourTx *pourtx = &newTx.vpour[0];
pourtx->vpub_old = -1;
BOOST_CHECK(!CheckTransaction(newTx, state));
BOOST_CHECK(state.GetRejectReason() == "bad-txns-vpub_old-negative");
pourtx->vpub_old = MAX_MONEY + 1;
BOOST_CHECK(!CheckTransaction(newTx, state));
BOOST_CHECK(state.GetRejectReason() == "bad-txns-vpub_old-toolarge");
pourtx->vpub_old = 0;
pourtx->vpub_new = -1;
BOOST_CHECK(!CheckTransaction(newTx, state));
BOOST_CHECK(state.GetRejectReason() == "bad-txns-vpub_new-negative");
pourtx->vpub_new = MAX_MONEY + 1;
BOOST_CHECK(!CheckTransaction(newTx, state));
BOOST_CHECK(state.GetRejectReason() == "bad-txns-vpub_new-toolarge");
pourtx->vpub_new = (MAX_MONEY / 2) + 10;
newTx.vpour.push_back(CPourTx());
CPourTx *pourtx2 = &newTx.vpour[1];
pourtx2->vpub_new = (MAX_MONEY / 2) + 10;
BOOST_CHECK(!CheckTransaction(newTx, state));
BOOST_CHECK(state.GetRejectReason() == "bad-txns-txouttotal-toolarge");
}
{
// Ensure that serials are never duplicated within a transaction.
CMutableTransaction newTx(tx);
CValidationState state;
newTx.vpour.push_back(CPourTx());
CPourTx *pourtx = &newTx.vpour[0];
pourtx->serials[0] = GetRandHash();
pourtx->serials[1] = pourtx->serials[0];
BOOST_CHECK(!CheckTransaction(newTx, state));
BOOST_CHECK(state.GetRejectReason() == "bad-pours-serials-duplicate");
pourtx->serials[1] = GetRandHash();
newTx.vpour.push_back(CPourTx());
CPourTx *pourtx2 = &newTx.vpour[1];
pourtx2->serials[0] = GetRandHash();
pourtx2->serials[1] = pourtx->serials[0];
BOOST_CHECK(!CheckTransaction(newTx, state));
BOOST_CHECK(state.GetRejectReason() == "bad-pours-serials-duplicate");
}
{
// Ensure that coinbase transactions do not have pours.
CMutableTransaction newTx(tx);
CValidationState state;
newTx.vpour.push_back(CPourTx());
CPourTx *pourtx = &newTx.vpour[0];
pourtx->serials[0] = GetRandHash();
pourtx->serials[1] = GetRandHash();
newTx.vin.push_back(CTxIn(uint256(), -1));
{
CTransaction finalNewTx(newTx);
BOOST_CHECK(finalNewTx.IsCoinBase());
}
BOOST_CHECK(!CheckTransaction(newTx, state));
BOOST_CHECK(state.GetRejectReason() == "bad-cb-has-pours");
}
}
BOOST_AUTO_TEST_CASE(test_Get)
{
CBasicKeyStore keystore;

View File

@ -17,17 +17,39 @@
using namespace std;
static const char DB_ANCHOR = 'A';
static const char DB_SERIAL = 's';
static const char DB_COINS = 'c';
static const char DB_BLOCK_FILES = 'f';
static const char DB_TXINDEX = 't';
static const char DB_BLOCK_INDEX = 'b';
static const char DB_BEST_BLOCK = 'B';
static const char DB_BEST_ANCHOR = 'a';
static const char DB_FLAG = 'F';
static const char DB_REINDEX_FLAG = 'R';
static const char DB_LAST_BLOCK = 'l';
void static BatchWriteAnchor(CLevelDBBatch &batch,
const uint256 &croot,
const libzerocash::IncrementalMerkleTree &tree,
const bool &entered)
{
if (!entered)
batch.Erase(make_pair(DB_ANCHOR, croot));
else {
batch.Write(make_pair(DB_ANCHOR, croot), tree.serialize());
}
}
void static BatchWriteSerial(CLevelDBBatch &batch, const uint256 &serial, const bool &entered) {
if (!entered)
batch.Erase(make_pair(DB_SERIAL, serial));
else
batch.Write(make_pair(DB_SERIAL, serial), true);
}
void static BatchWriteCoins(CLevelDBBatch &batch, const uint256 &hash, const CCoins &coins) {
if (coins.IsPruned())
batch.Erase(make_pair(DB_COINS, hash));
@ -39,9 +61,41 @@ void static BatchWriteHashBestChain(CLevelDBBatch &batch, const uint256 &hash) {
batch.Write(DB_BEST_BLOCK, hash);
}
void static BatchWriteHashBestAnchor(CLevelDBBatch &batch, const uint256 &hash) {
batch.Write(DB_BEST_ANCHOR, hash);
}
CCoinsViewDB::CCoinsViewDB(size_t nCacheSize, bool fMemory, bool fWipe) : db(GetDataDir() / "chainstate", nCacheSize, fMemory, fWipe) {
}
bool CCoinsViewDB::GetAnchorAt(const uint256 &rt, libzerocash::IncrementalMerkleTree &tree) const {
if (rt.IsNull()) {
IncrementalMerkleTree new_tree(INCREMENTAL_MERKLE_TREE_DEPTH);
tree.setTo(new_tree);
return true;
}
std::vector<unsigned char> tree_serialized;
bool read = db.Read(make_pair(DB_ANCHOR, rt), tree_serialized);
if (!read) return read;
auto tree_deserialized = IncrementalMerkleTreeCompact::deserialize(tree_serialized);
tree.fromCompactRepresentation(tree_deserialized);
return true;
}
bool CCoinsViewDB::GetSerial(const uint256 &serial) const {
bool spent = false;
bool read = db.Read(make_pair(DB_SERIAL, serial), spent);
return read;
}
bool CCoinsViewDB::GetCoins(const uint256 &txid, CCoins &coins) const {
return db.Read(make_pair(DB_COINS, txid), coins);
}
@ -57,7 +111,18 @@ uint256 CCoinsViewDB::GetBestBlock() const {
return hashBestChain;
}
bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) {
uint256 CCoinsViewDB::GetBestAnchor() const {
uint256 hashBestAnchor;
if (!db.Read(DB_BEST_ANCHOR, hashBestAnchor))
return uint256();
return hashBestAnchor;
}
bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins,
const uint256 &hashBlock,
const uint256 &hashAnchor,
CAnchorsMap &mapAnchors,
CSerialsMap &mapSerials) {
CLevelDBBatch batch;
size_t count = 0;
size_t changed = 0;
@ -70,8 +135,29 @@ bool CCoinsViewDB::BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock) {
CCoinsMap::iterator itOld = it++;
mapCoins.erase(itOld);
}
for (CAnchorsMap::iterator it = mapAnchors.begin(); it != mapAnchors.end();) {
if (it->second.flags & CAnchorsCacheEntry::DIRTY) {
BatchWriteAnchor(batch, it->first, it->second.tree, it->second.entered);
// TODO: changed++?
}
CAnchorsMap::iterator itOld = it++;
mapAnchors.erase(itOld);
}
for (CSerialsMap::iterator it = mapSerials.begin(); it != mapSerials.end();) {
if (it->second.flags & CSerialsCacheEntry::DIRTY) {
BatchWriteSerial(batch, it->first, it->second.entered);
// TODO: changed++?
}
CSerialsMap::iterator itOld = it++;
mapSerials.erase(itOld);
}
if (!hashBlock.IsNull())
BatchWriteHashBestChain(batch, hashBlock);
if (!hashAnchor.IsNull())
BatchWriteHashBestAnchor(batch, hashAnchor);
LogPrint("coindb", "Committing %u changed transactions (out of %u) to coin database...\n", (unsigned int)changed, (unsigned int)count);
return db.WriteBatch(batch);

View File

@ -14,6 +14,8 @@
#include <utility>
#include <vector>
#include "libzerocash/IncrementalMerkleTree.h"
class CBlockFileInfo;
class CBlockIndex;
struct CDiskTxPos;
@ -34,10 +36,17 @@ protected:
public:
CCoinsViewDB(size_t nCacheSize, bool fMemory = false, bool fWipe = false);
bool GetAnchorAt(const uint256 &rt, libzerocash::IncrementalMerkleTree &tree) const;
bool GetSerial(const uint256 &serial) const;
bool GetCoins(const uint256 &txid, CCoins &coins) const;
bool HaveCoins(const uint256 &txid) const;
uint256 GetBestBlock() const;
bool BatchWrite(CCoinsMap &mapCoins, const uint256 &hashBlock);
uint256 GetBestAnchor() const;
bool BatchWrite(CCoinsMap &mapCoins,
const uint256 &hashBlock,
const uint256 &hashAnchor,
CAnchorsMap &mapAnchors,
CSerialsMap &mapSerials);
bool GetStats(CCoinsStats &stats) const;
};

View File

@ -99,6 +99,11 @@ bool CTxMemPool::addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry,
const CTransaction& tx = mapTx[hash].GetTx();
for (unsigned int i = 0; i < tx.vin.size(); i++)
mapNextTx[tx.vin[i].prevout] = CInPoint(&tx, i);
BOOST_FOREACH(const CPourTx &pour, tx.vpour) {
BOOST_FOREACH(const uint256 &serial, pour.serials) {
mapSerials[serial] = &tx;
}
}
nTransactionsUpdated++;
totalTxSize += entry.GetTxSize();
minerPolicyEstimator->processTransaction(entry, fCurrentEstimate);
@ -143,6 +148,11 @@ void CTxMemPool::remove(const CTransaction &origTx, std::list<CTransaction>& rem
}
BOOST_FOREACH(const CTxIn& txin, tx.vin)
mapNextTx.erase(txin.prevout);
BOOST_FOREACH(const CPourTx& pour, tx.vpour) {
BOOST_FOREACH(const uint256& serial, pour.serials) {
mapSerials.erase(serial);
}
}
removed.push_back(tx);
totalTxSize -= mapTx[hash].GetTxSize();
@ -178,6 +188,32 @@ void CTxMemPool::removeCoinbaseSpends(const CCoinsViewCache *pcoins, unsigned in
}
}
void CTxMemPool::removeWithAnchor(const uint256 &invalidRoot)
{
// If a block is disconnected from the tip, and the root changed,
// we must invalidate transactions from the mempool which spend
// from that root -- almost as though they were spending coinbases
// which are no longer valid to spend due to coinbase maturity.
LOCK(cs);
list<CTransaction> transactionsToRemove;
for (std::map<uint256, CTxMemPoolEntry>::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) {
const CTransaction& tx = it->second.GetTx();
BOOST_FOREACH(const CPourTx& pour, tx.vpour) {
if (pour.anchor == invalidRoot) {
transactionsToRemove.push_back(tx);
break;
}
}
}
BOOST_FOREACH(const CTransaction& tx, transactionsToRemove) {
list<CTransaction> removed;
remove(tx, removed, true);
}
}
void CTxMemPool::removeConflicts(const CTransaction &tx, std::list<CTransaction>& removed)
{
// Remove transactions which depend on inputs of tx, recursively
@ -193,6 +229,19 @@ void CTxMemPool::removeConflicts(const CTransaction &tx, std::list<CTransaction>
}
}
}
BOOST_FOREACH(const CPourTx &pour, tx.vpour) {
BOOST_FOREACH(const uint256 &serial, pour.serials) {
std::map<uint256, const CTransaction*>::iterator it = mapSerials.find(serial);
if (it != mapSerials.end()) {
const CTransaction &txConflict = *it->second;
if (txConflict != tx)
{
remove(txConflict, removed, true);
}
}
}
}
}
/**
@ -265,6 +314,15 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const
assert(it3->second.n == i);
i++;
}
BOOST_FOREACH(const CPourTx &pour, tx.vpour) {
BOOST_FOREACH(const uint256 &serial, pour.serials) {
assert(!pcoins->GetSerial(serial));
}
// TODO: chained pours
libzerocash::IncrementalMerkleTree tree(INCREMENTAL_MERKLE_TREE_DEPTH);
assert(pcoins->GetAnchorAt(pour.anchor, tree));
}
if (fDependsWait)
waitingOnDependants.push_back(&it->second);
else {
@ -298,6 +356,14 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const
assert(it->first == it->second.ptx->vin[it->second.n].prevout);
}
for (std::map<uint256, const CTransaction*>::const_iterator it = mapSerials.begin(); it != mapSerials.end(); it++) {
uint256 hash = it->second->GetHash();
map<uint256, CTxMemPoolEntry>::const_iterator it2 = mapTx.find(hash);
const CTransaction& tx = it2->second.GetTx();
assert(it2 != mapTx.end());
assert(&tx == it->second);
}
assert(totalTxSize == checkTotal);
}
@ -404,6 +470,13 @@ bool CTxMemPool::HasNoInputsOf(const CTransaction &tx) const
CCoinsViewMemPool::CCoinsViewMemPool(CCoinsView *baseIn, CTxMemPool &mempoolIn) : CCoinsViewBacked(baseIn), mempool(mempoolIn) { }
bool CCoinsViewMemPool::GetSerial(const uint256 &serial) const {
if (mempool.mapSerials.count(serial))
return true;
return base->GetSerial(serial);
}
bool CCoinsViewMemPool::GetCoins(const uint256 &txid, CCoins &coins) const {
// If an entry in the mempool exists, always return that one, as it's guaranteed to never
// conflict with the underlying cache, and it cannot have pruned entries (as it contains full)

View File

@ -98,6 +98,7 @@ public:
mutable CCriticalSection cs;
std::map<uint256, CTxMemPoolEntry> mapTx;
std::map<COutPoint, CInPoint> mapNextTx;
std::map<uint256, const CTransaction*> mapSerials;
std::map<uint256, std::pair<double, CAmount> > mapDeltas;
CTxMemPool(const CFeeRate& _minRelayFee);
@ -114,6 +115,7 @@ public:
bool addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry, bool fCurrentEstimate = true);
void remove(const CTransaction &tx, std::list<CTransaction>& removed, bool fRecursive = false);
void removeWithAnchor(const uint256 &invalidRoot);
void removeCoinbaseSpends(const CCoinsViewCache *pcoins, unsigned int nMemPoolHeight);
void removeConflicts(const CTransaction &tx, std::list<CTransaction>& removed);
void removeForBlock(const std::vector<CTransaction>& vtx, unsigned int nBlockHeight,
@ -175,6 +177,7 @@ protected:
public:
CCoinsViewMemPool(CCoinsView *baseIn, CTxMemPool &mempoolIn);
bool GetSerial(const uint256 &txid) const;
bool GetCoins(const uint256 &txid, CCoins &coins) const;
bool HaveCoins(const uint256 &txid) const;
};

View File

@ -73,12 +73,14 @@ class CBlockUndo
{
public:
std::vector<CTxUndo> vtxundo; // for all but the coinbase
uint256 old_tree_root;
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
READWRITE(vtxundo);
READWRITE(old_tree_root);
}
};

View File

@ -430,8 +430,59 @@ boost::filesystem::path GetDefaultDataDir()
static boost::filesystem::path pathCached;
static boost::filesystem::path pathCachedNetSpecific;
static boost::filesystem::path zc_paramsPathCached;
static CCriticalSection csPathCached;
static boost::filesystem::path ZC_GetBaseParamsDir()
{
// Copied from GetDefaultDataDir and adapter for zcash params.
namespace fs = boost::filesystem;
// Windows < Vista: C:\Documents and Settings\Username\Application Data\ZcashParams
// Windows >= Vista: C:\Users\Username\AppData\Roaming\ZcashParams
// Mac: ~/Library/Application Support/ZcashParams
// Unix: ~/.zcash-params
#ifdef WIN32
// Windows
return GetSpecialFolderPath(CSIDL_APPDATA) / "ZcashParams";
#else
fs::path pathRet;
char* pszHome = getenv("HOME");
if (pszHome == NULL || strlen(pszHome) == 0)
pathRet = fs::path("/");
else
pathRet = fs::path(pszHome);
#ifdef MAC_OSX
// Mac
pathRet /= "Library/Application Support";
TryCreateDirectory(pathRet);
return pathRet / "ZcashParams";
#else
// Unix
return pathRet / ".zcash-params";
#endif
#endif
}
const boost::filesystem::path &ZC_GetParamsDir()
{
namespace fs = boost::filesystem;
LOCK(csPathCached); // Reuse the same lock as upstream.
fs::path &path = zc_paramsPathCached;
// This can be called during exceptions by LogPrintf(), so we cache the
// value so we don't have to do memory allocations after that.
if (!path.empty())
return path;
path = ZC_GetBaseParamsDir();
path /= BaseParams().DataDir();
return path;
}
const boost::filesystem::path &GetDataDir(bool fNetSpecific)
{
namespace fs = boost::filesystem;

View File

@ -104,6 +104,8 @@ static inline bool error(const char* format)
return false;
}
const boost::filesystem::path &ZC_GetParamsDir();
void PrintExceptionContinue(const std::exception *pex, const char* pszThread);
void ParseParameters(int argc, const char*const argv[]);
void FileCommit(FILE *fileout);

View File

@ -16,6 +16,7 @@
#include "utilmoneystr.h"
#include "wallet.h"
#include "walletdb.h"
#include "primitives/transaction.h"
#include <stdint.h>
@ -2343,3 +2344,275 @@ Value listunspent(const Array& params, bool fHelp)
return results;
}
Value zc_raw_receive(const json_spirit::Array& params, bool fHelp)
{
if (!EnsureWalletIsAvailable(fHelp)) {
return Value::null;
}
if (fHelp || params.size() != 2) {
throw runtime_error(
"zcrawreceive zcsecretkey encryptedbucket\n"
"\n"
"Decrypts encryptedbucket and checks if the coin commitments\n"
"are in the blockchain as indicated by the \"exists\" result.\n"
"\n"
"Output: {\n"
" \"amount\": value,\n"
" \"bucket\": cleartextbucket,\n"
" \"exists\": exists\n"
"}\n"
);
}
RPCTypeCheck(params, boost::assign::list_of(str_type)(str_type));
LOCK(cs_main);
std::vector<unsigned char> a_sk;
std::string sk_enc;
{
CDataStream ssData(ParseHexV(params[0], "zcsecretkey"), SER_NETWORK, PROTOCOL_VERSION);
try {
ssData >> a_sk;
ssData >> sk_enc;
} catch(const std::exception &) {
throw runtime_error(
"zcsecretkey could not be decoded"
);
}
}
libzerocash::PrivateAddress zcsecretkey(a_sk, sk_enc);
libzerocash::Address zcaddress(zcsecretkey);
auto encrypted_bucket_vec = ParseHexV(params[1], "encrypted_bucket");
std::string encrypted_bucket(encrypted_bucket_vec.begin(), encrypted_bucket_vec.end());
libzerocash::Coin decrypted_bucket(encrypted_bucket, zcaddress);
std::vector<unsigned char> commitment_v = decrypted_bucket.getCoinCommitment().getCommitmentValue();
uint256 commitment = uint256(commitment_v);
assert(pwalletMain != NULL);
libsnark::merkle_authentication_path path(INCREMENTAL_MERKLE_TREE_DEPTH); // We don't care during receive... yet! :)
size_t path_index = 0;
uint256 anchor;
auto found_in_chain = pwalletMain->WitnessBucketCommitment(commitment, path, path_index, anchor);
CAmount value_of_bucket = decrypted_bucket.getValue();
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
{
ss << decrypted_bucket.getValue();
ss << decrypted_bucket.getRho();
ss << decrypted_bucket.getR();
}
Object result;
result.push_back(Pair("amount", ValueFromAmount(value_of_bucket)));
result.push_back(Pair("bucket", HexStr(ss.begin(), ss.end())));
result.push_back(Pair("exists", found_in_chain));
return result;
}
Value zc_raw_pour(const json_spirit::Array& params, bool fHelp)
{
if (!EnsureWalletIsAvailable(fHelp)) {
return Value::null;
}
if (fHelp || params.size() != 5) {
throw runtime_error(
"zcrawpour rawtx inputs outputs vpub_old vpub_new\n"
" inputs: a JSON object mapping {bucket: zcsecretkey, ...}\n"
" outputs: a JSON object mapping {zcaddr: value, ...}\n"
"\n"
"Splices a Pour into rawtx. Inputs are unilaterally confidential.\n"
"Outputs are confidential between sender/receiver. The vpub_old and\n"
"vpub_new values are globally public and move transparent value into\n"
"or out of the confidential value store, respectively.\n"
"\n"
"Note: The caller is responsible for delivering the output enc1 and\n"
"enc2 to the appropriate recipients, as well as signing rawtxout and\n"
"ensuring it is mined. (A future RPC call will deliver the confidential\n"
"payments in-band on the blockchain.)\n"
"\n"
"Output: {\n"
" \"encryptedbucket1\": enc1,\n"
" \"encryptedbucket2\": enc2,\n"
" \"rawtxn\": rawtxout\n"
"}\n"
);
}
RPCTypeCheck(params, boost::assign::list_of(str_type)(obj_type)(obj_type)(int_type)(int_type));
LOCK(cs_main);
CTransaction tx;
if (!DecodeHexTx(tx, params[0].get_str()))
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
Object inputs = params[1].get_obj();
Object outputs = params[2].get_obj();
CAmount vpub_old(0);
CAmount vpub_new(0);
if (params[3].get_real() != 0.0)
vpub_old = AmountFromValue(params[3]);
if (params[4].get_real() != 0.0)
vpub_new = AmountFromValue(params[4]);
std::vector<PourInput> vpourin;
std::vector<PourOutput> vpourout;
uint256 anchor;
BOOST_FOREACH(const Pair& s, inputs)
{
CDataStream ssData(ParseHexV(s.name_, "bucket"), SER_NETWORK, PROTOCOL_VERSION);
uint64_t value;
std::vector<unsigned char> rho;
std::vector<unsigned char> r;
ssData >> value;
ssData >> rho;
ssData >> r;
std::vector<unsigned char> a_sk;
std::string sk_enc;
{
CDataStream ssData2(ParseHexV(s.value_, "zcsecretkey"), SER_NETWORK, PROTOCOL_VERSION);
try {
ssData2 >> a_sk;
ssData2 >> sk_enc;
} catch(const std::exception &) {
throw runtime_error(
"zcsecretkey could not be decoded"
);
}
}
libzerocash::PrivateAddress zcsecretkey(a_sk, sk_enc);
libzerocash::Address zcaddress(zcsecretkey);
libzerocash::Coin input_coin(zcaddress.getPublicAddress(), value, rho, r);
std::vector<unsigned char> commitment_v = input_coin.getCoinCommitment().getCommitmentValue();
uint256 commitment = uint256(commitment_v);
libsnark::merkle_authentication_path path(INCREMENTAL_MERKLE_TREE_DEPTH);
size_t path_index = 0;
assert(pwalletMain != NULL);
if (!pwalletMain->WitnessBucketCommitment(commitment, path, path_index, anchor)) {
throw std::runtime_error("Couldn't find bucket in the blockchain");
}
vpourin.push_back(PourInput(input_coin, zcaddress, path_index, path));
}
while (vpourin.size() < NUM_POUR_INPUTS) {
vpourin.push_back(PourInput(INCREMENTAL_MERKLE_TREE_DEPTH));
}
BOOST_FOREACH(const Pair& s, outputs)
{
libzerocash::PublicAddress addrTo;
{
CDataStream ssData(ParseHexV(s.name_, "to_address"), SER_NETWORK, PROTOCOL_VERSION);
std::vector<unsigned char> pubAddressSecret;
std::string encryptionPublicKey;
ssData >> pubAddressSecret;
ssData >> encryptionPublicKey;
addrTo = libzerocash::PublicAddress(pubAddressSecret, encryptionPublicKey);
}
CAmount nAmount = AmountFromValue(s.value_);
libzerocash::Coin coin(addrTo, nAmount);
libzerocash::PourOutput output(coin, addrTo);
vpourout.push_back(output);
}
while (vpourout.size() < NUM_POUR_OUTPUTS) {
vpourout.push_back(PourOutput(0));
}
// TODO
if (vpourout.size() != NUM_POUR_INPUTS || vpourin.size() != NUM_POUR_OUTPUTS) {
throw runtime_error("unsupported pour input/output counts");
}
CScript scriptPubKey;
CPourTx pourtx(*pzerocashParams,
scriptPubKey,
anchor,
{vpourin[0], vpourin[1]},
{vpourout[0], vpourout[1]},
vpub_old,
vpub_new);
assert(pourtx.Verify(*pzerocashParams));
CMutableTransaction mtx(tx);
mtx.nVersion = 2;
mtx.vpour.push_back(pourtx);
CTransaction rawTx(mtx);
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << rawTx;
Object result;
result.push_back(Pair("encryptedbucket1", HexStr(pourtx.ciphertexts[0].begin(), pourtx.ciphertexts[0].end())));
result.push_back(Pair("encryptedbucket2", HexStr(pourtx.ciphertexts[1].begin(), pourtx.ciphertexts[1].end())));
result.push_back(Pair("rawtxn", HexStr(ss.begin(), ss.end())));
return result;
}
Value zc_raw_keygen(const json_spirit::Array& params, bool fHelp)
{
if (!EnsureWalletIsAvailable(fHelp)) {
return Value::null;
}
if (fHelp || params.size() != 0) {
throw runtime_error(
"zcrawkeygen\n"
"\n"
"Generate a zcaddr which can send and receive confidential values.\n"
"\n"
"Output: {\n"
" \"zcaddress\": zcaddr,\n"
" \"zcsecretkey\": zcsecretkey,\n"
"}\n"
);
}
auto zckeypair = libzerocash::Address::CreateNewRandomAddress();
CDataStream pub(SER_NETWORK, PROTOCOL_VERSION);
CDataStream priv(SER_NETWORK, PROTOCOL_VERSION);
pub << zckeypair.getPublicAddress().getPublicAddressSecret(); // a_pk
pub << zckeypair.getPublicAddress().getEncryptionPublicKey(); // pk_enc
priv << zckeypair.getPrivateAddress().getAddressSecret(); // a_sk
priv << zckeypair.getPrivateAddress().getEncryptionSecretKey(); // sk_enc
std::string pub_hex = HexStr(pub.begin(), pub.end());
std::string priv_hex = HexStr(priv.begin(), priv.end());
Object result;
result.push_back(Pair("zcaddress", pub_hex));
result.push_back(Pair("zcsecretkey", priv_hex));
return result;
}

View File

@ -1051,6 +1051,67 @@ bool CWalletTx::WriteToDisk(CWalletDB *pwalletdb)
return pwalletdb->WriteTx(GetHash(), *this);
}
bool CWallet::WitnessBucketCommitment(uint256 &commitment,
libsnark::merkle_authentication_path& path,
size_t &path_index,
uint256 &final_anchor)
{
bool res = false;
std::vector<bool> commitment_index;
CBlockIndex* pindex = chainActive.Genesis();
libzerocash::IncrementalMerkleTree tree(INCREMENTAL_MERKLE_TREE_DEPTH);
uint256 current_anchor;
while (pindex) {
CBlock block;
ReadBlockFromDisk(block, pindex);
BOOST_FOREACH(const CTransaction& tx, block.vtx)
{
BOOST_FOREACH(const CPourTx& pour, tx.vpour)
{
BOOST_FOREACH(const uint256 &bucket_commitment, pour.commitments)
{
std::vector<bool> commitment_bv(ZC_CM_SIZE * 8);
std::vector<bool> index;
std::vector<unsigned char> commitment_value(bucket_commitment.begin(), bucket_commitment.end());
libzerocash::convertBytesVectorToVector(commitment_value, commitment_bv);
assert(tree.insertElement(commitment_bv, index));
if (bucket_commitment == commitment) {
// We've found it! Now, we construct a witness.
res = true;
commitment_index = index;
}
}
}
}
{
std::vector<unsigned char> newrt_v(32);
tree.getRootValue(newrt_v);
current_anchor = uint256(newrt_v);
}
// Consistency check: we should be able to find the current tree
// in our CCoins view.
libzerocash::IncrementalMerkleTree dummy_tree(INCREMENTAL_MERKLE_TREE_DEPTH);
assert(pcoinsTip->GetAnchorAt(current_anchor, dummy_tree));
pindex = chainActive.Next(pindex);
}
if (res) {
assert(tree.getWitness(commitment_index, path));
}
path_index = libzerocash::convertVectorToInt(commitment_index);
final_anchor = current_anchor;
return res;
}
/**
* Scan the block chain (starting in pindexStart) for transactions
* from or to us. If fUpdate is true, found transactions that already

View File

@ -616,6 +616,7 @@ public:
void SyncTransaction(const CTransaction& tx, const CBlock* pblock);
bool AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pblock, bool fUpdate);
void EraseFromWallet(const uint256 &hash);
bool WitnessBucketCommitment(uint256 &commitment, libsnark::merkle_authentication_path& path, size_t &path_index, uint256 &final_anchor);
int ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate = false);
void ReacceptWalletTransactions();
void ResendWalletTransactions(int64_t nBestBlockTime);

View File

@ -6,10 +6,13 @@ PARAMS_DIR="$HOME/.zcash-params"
REGTEST_PKEY_NAME='zc-testnet-public-alpha-proving.key'
REGTEST_VKEY_NAME='zc-testnet-public-alpha-verification.key'
REGTEST_PKEY_URL="https://zca.sh/downloads/$REGTEST_PKEY_NAME"
REGTEST_VKEY_URL="https://zca.sh/downloads/$REGTEST_VKEY_NAME"
REGTEST_PKEY_URL="https://z.cash/downloads/$REGTEST_PKEY_NAME"
REGTEST_VKEY_URL="https://z.cash/downloads/$REGTEST_VKEY_NAME"
REGTEST_DIR="$PARAMS_DIR/regtest"
# This should have the same params as regtest. We use symlinks for now.
TESTNET3_DIR="$PARAMS_DIR/testnet3"
# Note: This assumes cwd is set appropriately!
function fetch_params {
@ -61,9 +64,19 @@ cd "$REGTEST_DIR"
fetch_params "$REGTEST_PKEY_URL"
fetch_params "$REGTEST_VKEY_URL"
cd ..
echo 'Updating testnet3 symlinks to regtest parameters.'
mkdir -p "$TESTNET3_DIR"
ln -sf "../regtest/$REGTEST_PKEY_NAME" "$TESTNET3_DIR/$REGTEST_PKEY_NAME"
ln -sf "../regtest/$REGTEST_VKEY_NAME" "$TESTNET3_DIR/$REGTEST_VKEY_NAME"
# Now verify their hashes:
echo 'Verifying parameter file integrity via sha256sum...'
sha256sum --check - <<EOF
7844a96933979158886a5b69fb163f49de76120fa1dcfc33b16c83c134e61817 $REGTEST_PKEY_NAME
6902fd687bface72e572a7cda57f6da5a0c606c7b9769f30becd255e57924f41 $REGTEST_VKEY_NAME
7844a96933979158886a5b69fb163f49de76120fa1dcfc33b16c83c134e61817 regtest/$REGTEST_PKEY_NAME
7844a96933979158886a5b69fb163f49de76120fa1dcfc33b16c83c134e61817 testnet3/$REGTEST_PKEY_NAME
6902fd687bface72e572a7cda57f6da5a0c606c7b9769f30becd255e57924f41 regtest/$REGTEST_VKEY_NAME
6902fd687bface72e572a7cda57f6da5a0c606c7b9769f30becd255e57924f41 testnet3/$REGTEST_VKEY_NAME
EOF