Auto merge of #1561 - str4d:778-randomise-note-order, r=daira

Randomize JoinSplit input and output orders

Closes #778.
This commit is contained in:
zkbot 2016-10-20 03:55:33 -04:00
commit 0dfe612ff9
10 changed files with 243 additions and 10 deletions

View File

@ -4,6 +4,7 @@ bin_PROGRAMS += zcash-gtest
# tool for generating our public parameters
zcash_gtest_SOURCES = \
gtest/main.cpp \
gtest/utils.cpp \
gtest/test_checktransaction.cpp \
gtest/json_test_vectors.cpp \
gtest/json_test_vectors.h \
@ -17,7 +18,9 @@ zcash_gtest_SOURCES = \
gtest/test_noteencryption.cpp \
gtest/test_merkletree.cpp \
gtest/test_pow.cpp \
gtest/test_random.cpp \
gtest/test_rpc.cpp \
gtest/test_transaction.cpp \
gtest/test_circuit.cpp \
gtest/test_txid.cpp \
gtest/test_libzcash_utils.cpp \

27
src/gtest/test_random.cpp Normal file
View File

@ -0,0 +1,27 @@
#include <gtest/gtest.h>
#include "random.h"
extern int GenZero(int n);
extern int GenMax(int n);
TEST(Random, MappedShuffle) {
std::vector<int> a {8, 4, 6, 3, 5};
std::vector<int> m {0, 1, 2, 3, 4};
auto a1 = a;
auto m1 = m;
MappedShuffle(a1.begin(), m1.begin(), a1.size(), GenZero);
std::vector<int> ea1 {4, 6, 3, 5, 8};
std::vector<int> em1 {1, 2, 3, 4, 0};
EXPECT_EQ(ea1, a1);
EXPECT_EQ(em1, m1);
auto a2 = a;
auto m2 = m;
MappedShuffle(a2.begin(), m2.begin(), a2.size(), GenMax);
std::vector<int> ea2 {8, 4, 6, 3, 5};
std::vector<int> em2 {0, 1, 2, 3, 4};
EXPECT_EQ(ea2, a2);
EXPECT_EQ(em2, m2);
}

View File

@ -0,0 +1,85 @@
#include <gtest/gtest.h>
#include "primitives/transaction.h"
#include "zcash/Note.hpp"
#include "zcash/Address.hpp"
extern ZCJoinSplit* params;
extern int GenZero(int n);
extern int GenMax(int n);
TEST(Transaction, JSDescriptionRandomized) {
// construct a merkle tree
ZCIncrementalMerkleTree merkleTree;
libzcash::SpendingKey k = libzcash::SpendingKey::random();
libzcash::PaymentAddress addr = k.address();
libzcash::Note note(addr.a_pk, 100, uint256(), uint256());
// commitment from coin
uint256 commitment = note.cm();
// insert commitment into the merkle tree
merkleTree.append(commitment);
// compute the merkle root we will be working with
uint256 rt = merkleTree.root();
auto witness = merkleTree.witness();
// create JSDescription
uint256 pubKeyHash;
boost::array<libzcash::JSInput, ZC_NUM_JS_INPUTS> inputs = {
libzcash::JSInput(witness, note, k),
libzcash::JSInput() // dummy input of zero value
};
boost::array<libzcash::JSOutput, ZC_NUM_JS_OUTPUTS> outputs = {
libzcash::JSOutput(addr, 50),
libzcash::JSOutput(addr, 50)
};
boost::array<size_t, ZC_NUM_JS_INPUTS> inputMap;
boost::array<size_t, ZC_NUM_JS_OUTPUTS> outputMap;
{
auto jsdesc = JSDescription::Randomized(
*params, pubKeyHash, rt,
inputs, outputs,
inputMap, outputMap,
0, 0, false);
std::set<size_t> inputSet(inputMap.begin(), inputMap.end());
std::set<size_t> expectedInputSet {0, 1};
EXPECT_EQ(expectedInputSet, inputSet);
std::set<size_t> outputSet(outputMap.begin(), outputMap.end());
std::set<size_t> expectedOutputSet {0, 1};
EXPECT_EQ(expectedOutputSet, outputSet);
}
{
auto jsdesc = JSDescription::Randomized(
*params, pubKeyHash, rt,
inputs, outputs,
inputMap, outputMap,
0, 0, false, GenZero);
boost::array<size_t, ZC_NUM_JS_INPUTS> expectedInputMap {1, 0};
boost::array<size_t, ZC_NUM_JS_OUTPUTS> expectedOutputMap {1, 0};
EXPECT_EQ(expectedInputMap, inputMap);
EXPECT_EQ(expectedOutputMap, outputMap);
}
{
auto jsdesc = JSDescription::Randomized(
*params, pubKeyHash, rt,
inputs, outputs,
inputMap, outputMap,
0, 0, false, GenMax);
boost::array<size_t, ZC_NUM_JS_INPUTS> expectedInputMap {0, 1};
boost::array<size_t, ZC_NUM_JS_OUTPUTS> expectedOutputMap {0, 1};
EXPECT_EQ(expectedInputMap, inputMap);
EXPECT_EQ(expectedOutputMap, outputMap);
}
}

13
src/gtest/utils.cpp Normal file
View File

@ -0,0 +1,13 @@
#include "zcash/JoinSplit.hpp"
ZCJoinSplit* params = ZCJoinSplit::Unopened();
int GenZero(int n)
{
return 0;
}
int GenMax(int n)
{
return n-1;
}

View File

@ -41,6 +41,30 @@ JSDescription::JSDescription(ZCJoinSplit& params,
);
}
JSDescription JSDescription::Randomized(
ZCJoinSplit& params,
const uint256& pubKeyHash,
const uint256& anchor,
boost::array<libzcash::JSInput, ZC_NUM_JS_INPUTS>& inputs,
boost::array<libzcash::JSOutput, ZC_NUM_JS_OUTPUTS>& outputs,
boost::array<size_t, ZC_NUM_JS_INPUTS>& inputMap,
boost::array<size_t, ZC_NUM_JS_OUTPUTS>& outputMap,
CAmount vpub_old,
CAmount vpub_new,
bool computeProof,
std::function<int(int)> gen)
{
// Randomize the order of the inputs and outputs
inputMap = {0, 1};
outputMap = {0, 1};
MappedShuffle(inputs.begin(), inputMap.begin(), ZC_NUM_JS_INPUTS, gen);
MappedShuffle(outputs.begin(), outputMap.begin(), ZC_NUM_JS_OUTPUTS, gen);
return JSDescription(
params, pubKeyHash, anchor, inputs, outputs,
vpub_old, vpub_new, computeProof);
}
bool JSDescription::Verify(
ZCJoinSplit& params,
const uint256& pubKeyHash

View File

@ -7,6 +7,7 @@
#define BITCOIN_PRIMITIVES_TRANSACTION_H
#include "amount.h"
#include "random.h"
#include "script/script.h"
#include "serialize.h"
#include "uint256.h"
@ -78,6 +79,20 @@ public:
bool computeProof = true // Set to false in some tests
);
static JSDescription Randomized(
ZCJoinSplit& params,
const uint256& pubKeyHash,
const uint256& rt,
boost::array<libzcash::JSInput, ZC_NUM_JS_INPUTS>& inputs,
boost::array<libzcash::JSOutput, ZC_NUM_JS_OUTPUTS>& outputs,
boost::array<size_t, ZC_NUM_JS_INPUTS>& inputMap,
boost::array<size_t, ZC_NUM_JS_OUTPUTS>& outputMap,
CAmount vpub_old,
CAmount vpub_new,
bool computeProof = true, // Set to false in some tests
std::function<int(int)> gen = GetRandInt
);
// Verifies that the JoinSplit proof is correct.
bool Verify(ZCJoinSplit& params, const uint256& pubKeyHash) const;

View File

@ -8,6 +8,7 @@
#include "uint256.h"
#include <functional>
#include <stdint.h>
/**
@ -24,6 +25,31 @@ uint64_t GetRand(uint64_t nMax);
int GetRandInt(int nMax);
uint256 GetRandHash();
/**
* Rearranges the elements in the range [first,first+len) randomly, assuming
* that gen is a uniform random number generator. Follows the same algorithm as
* std::shuffle in C++11 (a Durstenfeld shuffle).
*
* The elements in the range [mapFirst,mapFirst+len) are rearranged according to
* the same permutation, enabling the permutation to be tracked by the caller.
*
* gen takes an integer n and produces a uniform random output in [0,n).
*/
template <typename RandomAccessIterator, typename MapRandomAccessIterator>
void MappedShuffle(RandomAccessIterator first,
MapRandomAccessIterator mapFirst,
size_t len,
std::function<int(int)> gen)
{
for (size_t i = len-1; i > 0; --i) {
auto r = gen(i+1);
assert(r >= 0);
assert(r <= i);
std::swap(first[i], first[r]);
std::swap(mapFirst[i], mapFirst[r]);
}
}
/**
* Seed insecure_rand using the random pool.
* @param Deterministic Use a deterministic seed

View File

@ -367,8 +367,6 @@ BOOST_AUTO_TEST_CASE(test_basic_joinsplit_verification)
test.anchor = GetRandHash();
BOOST_CHECK(!test.Verify(*p, pubKeyHash));
}
delete p;
}
BOOST_AUTO_TEST_CASE(test_simple_joinsplit_invalidity)

View File

@ -29,6 +29,23 @@
using namespace libzcash;
int find_output(Object obj, int n) {
Value outputMapValue = find_value(obj, "outputmap");
if (outputMapValue.type() != array_type) {
throw JSONRPCError(RPC_WALLET_ERROR, "Missing outputmap for JoinSplit operation");
}
Array outputMap = outputMapValue.get_array();
assert(outputMap.size() == ZC_NUM_JS_OUTPUTS);
for (size_t i = 0; i < outputMap.size(); i++) {
if (outputMap[i] == n) {
return i;
}
}
throw std::logic_error("n is not present in outputmap");
}
AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany(
std::string fromAddress,
std::vector<SendManyRecipient> tOutputs,
@ -372,6 +389,7 @@ bool AsyncRPCOperation_sendmany::main_impl() {
*/
Object obj;
CAmount jsChange = 0; // this is updated after each joinsplit
int changeOutputIndex = -1; // this is updated after each joinsplit if jsChange > 0
bool minersFeeProcessed = false;
if (t_outputs_total > 0) {
@ -429,6 +447,10 @@ bool AsyncRPCOperation_sendmany::main_impl() {
}
obj = perform_joinsplit(info, outPoints);
if (jsChange > 0) {
changeOutputIndex = find_output(obj, 1);
}
}
}
@ -442,9 +464,6 @@ bool AsyncRPCOperation_sendmany::main_impl() {
// Keep track of treestate within this transaction
boost::unordered_map<uint256, ZCIncrementalMerkleTree, CCoinsKeyHasher> intermediates;
std::vector<uint256> previousCommitments;
// NOTE: Randomization of input and output order could break this in future
const int changeOutputIndex = 1;
while (zOutputsDeque.size() > 0) {
AsyncJoinSplitInfo info;
@ -483,7 +502,6 @@ bool AsyncRPCOperation_sendmany::main_impl() {
throw JSONRPCError(RPC_WALLET_ERROR, "Could not find previous JoinSplit anchor");
}
// NOTE: We assume the last commitment, output 1, is the change we want
for (const uint256& commitment : prevJoinSplit.commitments) {
tree.append(commitment);
previousCommitments.push_back(commitment);
@ -645,6 +663,10 @@ bool AsyncRPCOperation_sendmany::main_impl() {
}
obj = perform_joinsplit(info, witnesses, jsAnchor);
if (jsChange > 0) {
changeOutputIndex = find_output(obj, 1);
}
}
}
@ -845,11 +867,20 @@ Object AsyncRPCOperation_sendmany::perform_joinsplit(
);
// Generate the proof, this can take over a minute.
JSDescription jsdesc(*pzcashParams,
boost::array<libzcash::JSInput, ZC_NUM_JS_INPUTS> inputs
{info.vjsin[0], info.vjsin[1]};
boost::array<libzcash::JSOutput, ZC_NUM_JS_OUTPUTS> outputs
{info.vjsout[0], info.vjsout[1]};
boost::array<size_t, ZC_NUM_JS_INPUTS> inputMap;
boost::array<size_t, ZC_NUM_JS_OUTPUTS> outputMap;
JSDescription jsdesc = JSDescription::Randomized(
*pzcashParams,
joinSplitPubKey_,
anchor,
{info.vjsin[0], info.vjsin[1]},
{info.vjsout[0], info.vjsout[1]},
inputs,
outputs,
inputMap,
outputMap,
info.vpub_old,
info.vpub_new,
!this->testmode);
@ -910,10 +941,21 @@ Object AsyncRPCOperation_sendmany::perform_joinsplit(
encryptedNote2 = HexStr(ss2.begin(), ss2.end());
}
Array arrInputMap;
Array arrOutputMap;
for (size_t i = 0; i < ZC_NUM_JS_INPUTS; i++) {
arrInputMap.push_back(inputMap[i]);
}
for (size_t i = 0; i < ZC_NUM_JS_OUTPUTS; i++) {
arrOutputMap.push_back(outputMap[i]);
}
Object obj;
obj.push_back(Pair("encryptednote1", encryptedNote1));
obj.push_back(Pair("encryptednote2", encryptedNote2));
obj.push_back(Pair("rawtxn", HexStr(ss.begin(), ss.end())));
obj.push_back(Pair("inputmap", arrInputMap));
obj.push_back(Pair("outputmap", arrOutputMap));
return obj;
}

View File

@ -13,7 +13,7 @@
using ::testing::Return;
ZCJoinSplit* params = ZCJoinSplit::Unopened();
extern ZCJoinSplit* params;
ACTION(ThrowLogicError) {
throw std::logic_error("Boom");