Browse Source

Implement zkSNARK compression.

pull/145/head
Sean Bowe 8 years ago
parent
commit
f0dab51cf3
  1. 1
      src/Makefile.am
  2. 4
      src/Makefile.gtest.include
  3. 4
      src/Makefile.test.include
  4. 14
      src/gtest/json_test_vectors.cpp
  5. 54
      src/gtest/json_test_vectors.h
  6. 2
      src/gtest/test_joinsplit.cpp
  7. 59
      src/gtest/test_merkletree.cpp
  8. 553
      src/gtest/test_proofs.cpp
  9. 3
      src/primitives/transaction.h
  10. 10002
      src/test/data/g1_compressed.json
  11. 10002
      src/test/data/g2_compressed.json
  12. 1000
      src/test/data/sighash.json
  13. 2
      src/test/sighash_tests.cpp
  14. 1
      src/test/test_bitcoin.cpp
  15. 25
      src/zcash/JoinSplit.cpp
  16. 7
      src/zcash/JoinSplit.hpp
  17. 258
      src/zcash/Proof.cpp
  18. 241
      src/zcash/Proof.hpp
  19. 2
      src/zcash/Zcash.h

1
src/Makefile.am

@ -411,6 +411,7 @@ libzcash_a_SOURCES = \
zcash/NoteEncryption.cpp \
zcash/Address.cpp \
zcash/JoinSplit.cpp \
zcash/Proof.cpp \
zcash/Note.cpp \
zcash/prf.cpp \
zcash/util.cpp

4
src/Makefile.gtest.include

@ -4,6 +4,7 @@ bin_PROGRAMS += zcash-gtest
# tool for generating our public parameters
zcash_gtest_SOURCES = \
gtest/main.cpp \
gtest/json_test_vectors.cpp \
gtest/test_tautology.cpp \
gtest/test_checktransaction.cpp \
gtest/test_equihash.cpp \
@ -13,7 +14,8 @@ zcash_gtest_SOURCES = \
gtest/test_merkletree.cpp \
gtest/test_circuit.cpp \
gtest/test_txid.cpp \
gtest/test_libzcash_utils.cpp
gtest/test_libzcash_utils.cpp \
gtest/test_proofs.cpp
zcash_gtest_CPPFLAGS = -DMULTICORE -fopenmp -DBINARY_OUTPUT -DCURVE_ALT_BN128 -DSTATIC

4
src/Makefile.test.include

@ -30,7 +30,9 @@ JSON_TEST_FILES = \
test/data/merkle_roots_empty.json \
test/data/merkle_serialization.json \
test/data/merkle_witness_serialization.json \
test/data/merkle_path.json
test/data/merkle_path.json \
test/data/g1_compressed.json \
test/data/g2_compressed.json
RAW_TEST_FILES = test/data/alertTests.raw

14
src/gtest/json_test_vectors.cpp

@ -0,0 +1,14 @@
#include "json_test_vectors.h"
Array
read_json(const std::string& jsondata)
{
Value v;
if (!read_string(jsondata, v) || v.type() != array_type)
{
ADD_FAILURE();
return Array();
}
return v.get_array();
}

54
src/gtest/json_test_vectors.h

@ -0,0 +1,54 @@
#include <gtest/gtest.h>
#include "utilstrencodings.h"
#include "version.h"
#include "serialize.h"
#include "streams.h"
#include "json/json_spirit_reader_template.h"
#include "json/json_spirit_utils.h"
#include "json/json_spirit_writer_template.h"
using namespace json_spirit;
Array
read_json(const std::string& jsondata);
// #define PRINT_JSON 1
template<typename T>
void expect_deser_same(const T& expected)
{
CDataStream ss1(SER_NETWORK, PROTOCOL_VERSION);
ss1 << expected;
auto serialized_size = ss1.size();
T object;
ss1 >> object;
CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION);
ss2 << object;
ASSERT_TRUE(serialized_size == ss2.size());
ASSERT_TRUE(memcmp(&*ss1.begin(), &*ss2.begin(), serialized_size) == 0);
}
template<typename T, typename U>
void expect_test_vector(T& it, const U& expected)
{
expect_deser_same(expected);
CDataStream ss1(SER_NETWORK, PROTOCOL_VERSION);
ss1 << expected;
#ifdef PRINT_JSON
std::cout << "\t\"" ;
std::cout << HexStr(ss1.begin(), ss1.end()) << "\",\n";
#else
std::string raw = (it++)->get_str();
CDataStream ss2(ParseHex(raw), SER_NETWORK, PROTOCOL_VERSION);
ASSERT_TRUE(ss1.size() == ss2.size());
ASSERT_TRUE(memcmp(&*ss1.begin(), &*ss2.begin(), ss1.size()) == 0);
#endif
}

2
src/gtest/test_joinsplit.cpp

@ -33,7 +33,7 @@ void test_full_api(ZCJoinSplit* js)
boost::array<uint256, 2> commitments;
uint256 rt = tree.root();
boost::array<ZCNoteEncryption::Ciphertext, 2> ciphertexts;
boost::array<unsigned char, ZKSNARK_PROOF_SIZE> proof;
ZCProof proof;
{
boost::array<JSInput, 2> inputs = {

59
src/gtest/test_merkletree.cpp

@ -25,48 +25,11 @@
#include <boost/foreach.hpp>
#include "json/json_spirit_reader_template.h"
#include "json/json_spirit_utils.h"
#include "json/json_spirit_writer_template.h"
using namespace json_spirit;
Array
read_json(const std::string& jsondata)
{
Value v;
if (!read_string(jsondata, v) || v.type() != array_type)
{
ADD_FAILURE();
return Array();
}
return v.get_array();
}
//#define PRINT_JSON 1
#include "json_test_vectors.h"
using namespace std;
using namespace libsnark;
template<typename T>
void expect_deser_same(const T& expected)
{
CDataStream ss1(SER_NETWORK, PROTOCOL_VERSION);
ss1 << expected;
auto serialized_size = ss1.size();
T object;
ss1 >> object;
CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION);
ss2 << object;
ASSERT_TRUE(serialized_size == ss2.size());
ASSERT_TRUE(memcmp(&*ss1.begin(), &*ss2.begin(), serialized_size) == 0);
}
template<>
void expect_deser_same(const ZCTestingIncrementalWitness& expected)
{
@ -86,26 +49,6 @@ void expect_deser_same(const libzcash::MerklePath& expected)
// deserialized by Bitcoin's serialization code.
}
template<typename T, typename U>
void expect_test_vector(T& it, const U& expected)
{
expect_deser_same(expected);
CDataStream ss1(SER_NETWORK, PROTOCOL_VERSION);
ss1 << expected;
#ifdef PRINT_JSON
std::cout << "\t\"" ;
std::cout << HexStr(ss1.begin(), ss1.end()) << "\",\n";
#else
std::string raw = (it++)->get_str();
CDataStream ss2(ParseHex(raw), SER_NETWORK, PROTOCOL_VERSION);
ASSERT_TRUE(ss1.size() == ss2.size());
ASSERT_TRUE(memcmp(&*ss1.begin(), &*ss2.begin(), ss1.size()) == 0);
#endif
}
template<typename A, typename B, typename C>
void expect_ser_test_vector(B& b, const C& c, const A& tree) {
expect_test_vector<B, C>(b, c);

553
src/gtest/test_proofs.cpp

@ -0,0 +1,553 @@
#include <gtest/gtest.h>
#include "zcash/Proof.hpp"
#include <iostream>
#include "libsnark/common/default_types/r1cs_ppzksnark_pp.hpp"
#include "libsnark/zk_proof_systems/ppzksnark/r1cs_ppzksnark/r1cs_ppzksnark.hpp"
#include "zk_proof_systems/ppzksnark/r1cs_ppzksnark/r1cs_ppzksnark.hpp"
#include "relations/constraint_satisfaction_problems/r1cs/examples/r1cs_examples.hpp"
using namespace libzcash;
typedef libsnark::default_r1cs_ppzksnark_pp curve_pp;
typedef libsnark::default_r1cs_ppzksnark_pp::G1_type curve_G1;
typedef libsnark::default_r1cs_ppzksnark_pp::G2_type curve_G2;
typedef libsnark::default_r1cs_ppzksnark_pp::GT_type curve_GT;
typedef libsnark::default_r1cs_ppzksnark_pp::Fp_type curve_Fr;
typedef libsnark::default_r1cs_ppzksnark_pp::Fq_type curve_Fq;
typedef libsnark::default_r1cs_ppzksnark_pp::Fqe_type curve_Fq2;
#include "streams.h"
#include "version.h"
#include "utilstrencodings.h"
TEST(proofs, sqrt_fq)
{
// Poor man's PRNG
curve_Fq acc = curve_Fq("348957923485290374852379485") ^ 1000;
size_t quadratic_residues = 0;
size_t quadratic_nonresidues = 0;
for (size_t i = 1; i < 1000; i++) {
try {
acc += curve_Fq("45634563456") ^ i;
curve_Fq x = acc.sqrt();
ASSERT_TRUE((x*x) == acc);
quadratic_residues += 1;
} catch (std::runtime_error &e) {
quadratic_nonresidues += 1;
}
}
// Half of all nonzero elements in Fp are quadratic residues
ASSERT_TRUE(quadratic_residues == 511);
ASSERT_TRUE(quadratic_nonresidues == 488);
for (size_t i = 0; i < 1000; i++) {
curve_Fq x = curve_Fq::random_element();
curve_Fq x2 = x * x;
ASSERT_TRUE((x2.sqrt() == x) || (x2.sqrt() == -x));
}
// Test vectors
ASSERT_TRUE(
curve_Fq("5204065062716160319596273903996315000119019512886596366359652578430118331601")
==
curve_Fq("348579348568").sqrt()
);
ASSERT_THROW(curve_Fq("348579348569").sqrt(), std::runtime_error);
}
TEST(proofs, sqrt_fq2)
{
curve_Fq2 acc = curve_Fq2(
curve_Fq("3456293840592348059238409578239048769348760238476029347885092384059238459834") ^ 1000,
curve_Fq("2394578084760439457823945729347502374590283479582739485723945729384759823745") ^ 1000
);
size_t quadratic_residues = 0;
size_t quadratic_nonresidues = 0;
for (size_t i = 1; i < 1000; i++) {
try {
acc = acc + curve_Fq2(
curve_Fq("5204065062716160319596273903996315000119019512886596366359652578430118331601") ^ i,
curve_Fq("348957923485290374852379485348957923485290374852379485348957923485290374852") ^ i
);
curve_Fq2 x = acc.sqrt();
ASSERT_TRUE((x*x) == acc);
quadratic_residues += 1;
} catch (std::runtime_error &e) {
quadratic_nonresidues += 1;
}
}
// Half of all nonzero elements in Fp^k are quadratic residues as long
// as p != 2
ASSERT_TRUE(quadratic_residues == 505);
ASSERT_TRUE(quadratic_nonresidues == 494);
for (size_t i = 0; i < 1000; i++) {
curve_Fq2 x = curve_Fq2::random_element();
curve_Fq2 x2 = x * x;
ASSERT_TRUE((x2.sqrt() == x) || (x2.sqrt() == -x));
}
// Test vectors
ASSERT_THROW(curve_Fq2(
curve_Fq("2"),
curve_Fq("1")
).sqrt(), std::runtime_error);
ASSERT_THROW(curve_Fq2(
curve_Fq("3345897230485723946872934576923485762803457692345760237495682347502347589473"),
curve_Fq("1234912378405347958234756902345768290345762348957605678245967234857634857676")
).sqrt(), std::runtime_error);
curve_Fq2 x = curve_Fq2(
curve_Fq("12844195307879678418043983815760255909500142247603239203345049921980497041944"),
curve_Fq("7476417578426924565731404322659619974551724117137577781074613937423560117731")
);
curve_Fq2 nx = -x;
curve_Fq2 x2 = curve_Fq2(
curve_Fq("3345897230485723946872934576923485762803457692345760237495682347502347589474"),
curve_Fq("1234912378405347958234756902345768290345762348957605678245967234857634857676")
);
ASSERT_TRUE(x == x2.sqrt());
ASSERT_TRUE(nx == -x2.sqrt());
ASSERT_TRUE(x*x == x2);
ASSERT_TRUE(nx*nx == x2);
}
TEST(proofs, size_is_expected)
{
ZCProof p;
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << p;
ASSERT_EQ(ss.size(), 296);
}
TEST(proofs, fq_serializes_properly)
{
for (size_t i = 0; i < 1000; i++) {
curve_Fq e = curve_Fq::random_element();
Fq e2(e);
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << e2;
Fq e3;
ss >> e3;
curve_Fq e4 = e3.to_libsnark_fq<curve_Fq>();
ASSERT_TRUE(e == e4);
}
}
TEST(proofs, fq2_serializes_properly)
{
for (size_t i = 0; i < 1000; i++) {
curve_Fq2 e = curve_Fq2::random_element();
Fq2 e2(e);
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << e2;
Fq2 e3;
ss >> e3;
curve_Fq2 e4 = e3.to_libsnark_fq2<curve_Fq2>();
ASSERT_TRUE(e == e4);
}
}
template<typename T>
T deserialize_tv(std::string s)
{
T e;
CDataStream ss(ParseHex(s), SER_NETWORK, PROTOCOL_VERSION);
ss >> e;
return e;
}
curve_Fq deserialize_fq(std::string s)
{
return deserialize_tv<Fq>(s).to_libsnark_fq<curve_Fq>();
}
curve_Fq2 deserialize_fq2(std::string s)
{
return deserialize_tv<Fq2>(s).to_libsnark_fq2<curve_Fq2>();
}
TEST(proofs, fq_valid)
{
curve_Fq e = deserialize_fq("30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd46");
ASSERT_TRUE(e == curve_Fq("21888242871839275222246405745257275088696311157297823662689037894645226208582"));
ASSERT_TRUE(e != curve_Fq("21888242871839275222246405745257275088696311157297823662689037894645226208581"));
curve_Fq e2 = deserialize_fq("30644e72e131a029b75045b68181585d97816a916871ca8d3c208c16d87cfd46");
ASSERT_TRUE(e2 == curve_Fq("21888242871839275222221885816603420866962577604863418715751138068690288573766"));
}
TEST(proofs, fq_invalid)
{
// Should not be able to deserialize the modulus
ASSERT_THROW(
deserialize_fq("30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47"),
std::logic_error
);
// Should not be able to deserialize the modulus plus one
ASSERT_THROW(
deserialize_fq("30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd48"),
std::logic_error
);
// Should not be able to deserialize a ridiculously out of bound int
ASSERT_THROW(
deserialize_fq("ff644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd46"),
std::logic_error
);
}
TEST(proofs, fq2_valid)
{
// (q - 1) * q + q
curve_Fq2 e = deserialize_fq2("0925c4b8763cbf9c599a6f7c0348d21cb00b85511637560626edfa5c34c6b38d04689e957a1242c84a50189c6d96cadca602072d09eac1013b5458a2275d69b0");
ASSERT_TRUE(e.c0 == curve_Fq("21888242871839275222246405745257275088696311157297823662689037894645226208582"));
ASSERT_TRUE(e.c1 == curve_Fq("21888242871839275222246405745257275088696311157297823662689037894645226208582"));
curve_Fq2 e2 = deserialize_fq2("000000000000000000000000000000000000000000000000010245be1c91e3186bbbe1c430a93fcfc5aada4ab10c3492f70eea97a91c7b29554db55acffa34d2");
ASSERT_TRUE(e2.c0 == curve_Fq("238769481237490823"));
ASSERT_TRUE(e2.c1 == curve_Fq("384579238459723485"));
curve_Fq2 e3 = deserialize_fq2("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000");
ASSERT_TRUE(e3.c0 == curve_Fq("0"));
ASSERT_TRUE(e3.c1 == curve_Fq("0"));
curve_Fq2 e4 = deserialize_fq2("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001");
ASSERT_TRUE(e4.c0 == curve_Fq("1"));
ASSERT_TRUE(e4.c1 == curve_Fq("0"));
}
TEST(proofs, fq2_invalid)
{
// (q - 1) * q + q is invalid
ASSERT_THROW(
deserialize_fq2("0925c4b8763cbf9c599a6f7c0348d21cb00b85511637560626edfa5c34c6b38d04689e957a1242c84a50189c6d96cadca602072d09eac1013b5458a2275d69b1"),
std::logic_error
);
// q * q + (q - 1) is invalid
ASSERT_THROW(
deserialize_fq2("0925c4b8763cbf9c599a6f7c0348d21cb00b85511637560626edfa5c34c6b38d34cced085b43e2f202a05e52ef18233a3d8371be725c8b8e7774e4b8ffda66f7"),
std::logic_error
);
// Ridiculously out of bounds
ASSERT_THROW(
deserialize_fq2("0fffc4b8763cbf9c599a6f7c0348d21cb00b85511637560626edfa5c34c6b38d04689e957a1242c84a50189c6d96cadca602072d09eac1013b5458a2275d69b0"),
std::logic_error
);
ASSERT_THROW(
deserialize_fq2("ffffffff763cbf9c599a6f7c0348d21cb00b85511637560626edfa5c34c6b38d04689e957a1242c84a50189c6d96cadca602072d09eac1013b5458a2275d69b0"),
std::logic_error
);
}
TEST(proofs, g1_serializes_properly)
{
// Cannot serialize zero
{
ASSERT_THROW({CompressedG1 g = CompressedG1(curve_G1::zero());}, std::domain_error);
}
for (size_t i = 0; i < 1000; i++) {
curve_G1 e = curve_G1::random_element();
CompressedG1 e2(e);
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << e2;
CompressedG1 e3;
ss >> e3;
ASSERT_TRUE(e2 == e3);
curve_G1 e4 = e3.to_libsnark_g1<curve_G1>();
ASSERT_TRUE(e == e4);
}
}
TEST(proofs, g2_serializes_properly)
{
// Cannot serialize zero
{
ASSERT_THROW({CompressedG2 g = CompressedG2(curve_G2::zero());}, std::domain_error);
}
for (size_t i = 0; i < 1000; i++) {
curve_G2 e = curve_G2::random_element();
CompressedG2 e2(e);
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << e2;
CompressedG2 e3;
ss >> e3;
ASSERT_TRUE(e2 == e3);
curve_G2 e4 = e3.to_libsnark_g2<curve_G2>();
ASSERT_TRUE(e == e4);
}
}
TEST(proofs, zksnark_serializes_properly)
{
auto example = libsnark::generate_r1cs_example_with_field_input<curve_Fr>(250, 4);
example.constraint_system.swap_AB_if_beneficial();
auto kp = libsnark::r1cs_ppzksnark_generator<curve_pp>(example.constraint_system);
for (size_t i = 0; i < 20; i++) {
auto proof = libsnark::r1cs_ppzksnark_prover<curve_pp>(
kp.pk,
example.primary_input,
example.auxiliary_input,
example.constraint_system
);
ASSERT_TRUE(libsnark::r1cs_ppzksnark_verifier_strong_IC<curve_pp>(
kp.vk,
example.primary_input,
proof
));
ZCProof compressed_proof_0(proof);
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << compressed_proof_0;
ZCProof compressed_proof_1;
ss >> compressed_proof_1;
ASSERT_TRUE(compressed_proof_0 == compressed_proof_1);
auto newproof = compressed_proof_1.to_libsnark_proof<libsnark::r1cs_ppzksnark_proof<curve_pp>>();
ASSERT_TRUE(proof == newproof);
ASSERT_TRUE(libsnark::r1cs_ppzksnark_verifier_strong_IC<curve_pp>(
kp.vk,
example.primary_input,
newproof
));
}
}
TEST(proofs, g1_deserialization)
{
CompressedG1 g;
curve_G1 expected;
// Valid G1 element.
{
CDataStream ss(ParseHex("0230644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd46"), SER_NETWORK, PROTOCOL_VERSION);
ss >> g;
expected.X = curve_Fq("21888242871839275222246405745257275088696311157297823662689037894645226208582");
expected.Y = curve_Fq("3969792565221544645472939191694882283483352126195956956354061729942568608776");
expected.Z = curve_Fq::one();
ASSERT_TRUE(g.to_libsnark_g1<curve_G1>() == expected);
}
// Its negation.
{
CDataStream ss(ParseHex("0330644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd46"), SER_NETWORK, PROTOCOL_VERSION);
ss >> g;
expected.X = curve_Fq("21888242871839275222246405745257275088696311157297823662689037894645226208582");
expected.Y = curve_Fq("3969792565221544645472939191694882283483352126195956956354061729942568608776");
expected.Z = curve_Fq::one();
ASSERT_TRUE(g.to_libsnark_g1<curve_G1>() == -expected);
}
// Invalid leading bytes
{
CDataStream ss(ParseHex("ff30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd46"), SER_NETWORK, PROTOCOL_VERSION);
ASSERT_THROW(ss >> g, std::ios_base::failure);
}
// Invalid point
{
CDataStream ss(ParseHex("0208c6d2adffacbc8438f09f321874ea66e2fcc29f8dcfec2caefa21ec8c96a77c"), SER_NETWORK, PROTOCOL_VERSION);
ss >> g;
ASSERT_THROW(g.to_libsnark_g1<curve_G1>(), std::runtime_error);
}
// Point with out of bounds Fq
{
CDataStream ss(ParseHex("02ffc6d2adffacbc8438f09f321874ea66e2fcc29f8dcfec2caefa21ec8c96a77c"), SER_NETWORK, PROTOCOL_VERSION);
ss >> g;
ASSERT_THROW(g.to_libsnark_g1<curve_G1>(), std::logic_error);
}
// Randomly produce valid G1 representations and fail/succeed to
// turn them into G1 points based on whether they are valid.
for (size_t i = 0; i < 5000; i++) {
curve_Fq e = curve_Fq::random_element();
CDataStream ss(ParseHex("02"), SER_NETWORK, PROTOCOL_VERSION);
ss << Fq(e);
CompressedG1 g;
ss >> g;
try {
curve_G1 g_real = g.to_libsnark_g1<curve_G1>();
} catch(...) {
}
}
}
TEST(proofs, g2_deserialization)
{
CompressedG2 g;
curve_G2 expected = curve_G2::random_element();
// Valid G2 point
{
CDataStream ss(ParseHex("0a023aed31b5a9e486366ea9988b05dba469c6206e58361d9c065bbea7d928204a761efc6e4fa08ed227650134b52c7f7dd0463963e8a4bf21f4899fe5da7f984a"), SER_NETWORK, PROTOCOL_VERSION);
ss >> g;
expected.X = curve_Fq2(
curve_Fq("5923585509243758863255447226263146374209884951848029582715967108651637186684"),
curve_Fq("5336385337059958111259504403491065820971993066694750945459110579338490853570")
);
expected.Y = curve_Fq2(
curve_Fq("10374495865873200088116930399159835104695426846400310764827677226300185211748"),
curve_Fq("5256529835065685814318509161957442385362539991735248614869838648137856366932")
);
expected.Z = curve_Fq2::one();
ASSERT_TRUE(g.to_libsnark_g2<curve_G2>() == expected);
}
// Its negation
{
CDataStream ss(ParseHex("0b023aed31b5a9e486366ea9988b05dba469c6206e58361d9c065bbea7d928204a761efc6e4fa08ed227650134b52c7f7dd0463963e8a4bf21f4899fe5da7f984a"), SER_NETWORK, PROTOCOL_VERSION);
ss >> g;
expected.X = curve_Fq2(
curve_Fq("5923585509243758863255447226263146374209884951848029582715967108651637186684"),
curve_Fq("5336385337059958111259504403491065820971993066694750945459110579338490853570")
);
expected.Y = curve_Fq2(
curve_Fq("10374495865873200088116930399159835104695426846400310764827677226300185211748"),
curve_Fq("5256529835065685814318509161957442385362539991735248614869838648137856366932")
);
expected.Z = curve_Fq2::one();
ASSERT_TRUE(g.to_libsnark_g2<curve_G2>() == -expected);
}
// Invalid leading bytes
{
CDataStream ss(ParseHex("ff023aed31b5a9e486366ea9988b05dba469c6206e58361d9c065bbea7d928204a761efc6e4fa08ed227650134b52c7f7dd0463963e8a4bf21f4899fe5da7f984a"), SER_NETWORK, PROTOCOL_VERSION);
ASSERT_THROW(ss >> g, std::ios_base::failure);
}
// Invalid point
{
CDataStream ss(ParseHex("0b023aed31b5a9e486366ea9988b05dba469c6206e58361d9c065bbea7d928204a761efc6e4fa08ed227650134b52c7f7dd0463963e8a4bf21f4899fe5da7f984b"), SER_NETWORK, PROTOCOL_VERSION);
ss >> g;
ASSERT_THROW(g.to_libsnark_g2<curve_G2>(), std::runtime_error);
}
// Point with out of bounds Fq2
{
CDataStream ss(ParseHex("0a0f3aed31b5a9e486366ea9988b05dba469c6206e58361d9c065bbea7d928204a761efc6e4fa08ed227650134b52c7f7dd0463963e8a4bf21f4899fe5da7f984a"), SER_NETWORK, PROTOCOL_VERSION);
ss >> g;
ASSERT_THROW(g.to_libsnark_g2<curve_G2>(), std::logic_error);
}
// Randomly produce valid G2 representations and fail/succeed to
// turn them into G2 points based on whether they are valid.
for (size_t i = 0; i < 5000; i++) {
curve_Fq2 e = curve_Fq2::random_element();
CDataStream ss(ParseHex("0a"), SER_NETWORK, PROTOCOL_VERSION);
ss << Fq2(e);
CompressedG2 g;
ss >> g;
try {
curve_G2 g_real = g.to_libsnark_g2<curve_G2>();
} catch(...) {
}
}
}
#include "json_test_vectors.h"
#include "test/data/g1_compressed.json.h"
TEST(proofs, g1_test_vectors)
{
Array v = read_json(std::string(json_tests::g1_compressed, json_tests::g1_compressed + sizeof(json_tests::g1_compressed)));
Array::iterator v_iterator = v.begin();
curve_G1 e = curve_Fr("34958239045823") * curve_G1::one();
for (size_t i = 0; i < 10000; i++) {
e = (curve_Fr("34958239045823") ^ i) * e;
auto expected = CompressedG1(e);
expect_test_vector(v_iterator, expected);
ASSERT_TRUE(expected.to_libsnark_g1<curve_G1>() == e);
}
}
#include "test/data/g2_compressed.json.h"
TEST(proofs, g2_test_vectors)
{
Array v = read_json(std::string(json_tests::g2_compressed, json_tests::g2_compressed + sizeof(json_tests::g2_compressed)));
Array::iterator v_iterator = v.begin();
curve_G2 e = curve_Fr("34958239045823") * curve_G2::one();
for (size_t i = 0; i < 10000; i++) {
e = (curve_Fr("34958239045823") ^ i) * e;
auto expected = CompressedG2(e);
expect_test_vector(v_iterator, expected);
ASSERT_TRUE(expected.to_libsnark_g2<curve_G2>() == e);
}
}

3
src/primitives/transaction.h

@ -16,6 +16,7 @@
#include "zcash/NoteEncryption.hpp"
#include "zcash/Zcash.h"
#include "zcash/JoinSplit.hpp"
#include "zcash/Proof.hpp"
class JSDescription
{
@ -63,7 +64,7 @@ public:
// JoinSplit proof
// This is a zk-SNARK which ensures that this JoinSplit is valid.
boost::array<unsigned char, ZKSNARK_PROOF_SIZE> proof;
libzcash::ZCProof proof;
JSDescription(): vpub_old(0), vpub_new(0) { }

10002
src/test/data/g1_compressed.json

File diff suppressed because it is too large

10002
src/test/data/g2_compressed.json

File diff suppressed because it is too large

1000
src/test/data/sighash.json

File diff suppressed because one or more lines are too long

2
src/test/sighash_tests.cpp

@ -136,7 +136,7 @@ void static RandomTransaction(CMutableTransaction &tx, bool fSingle) {
jsdesc.randomSeed = GetRandHash();
randombytes_buf(jsdesc.ciphertexts[0].begin(), jsdesc.ciphertexts[0].size());
randombytes_buf(jsdesc.ciphertexts[1].begin(), jsdesc.ciphertexts[1].size());
randombytes_buf(jsdesc.proof.begin(), jsdesc.proof.size());
jsdesc.proof = libzcash::ZCProof::random_invalid();
jsdesc.macs[0] = GetRandHash();
jsdesc.macs[1] = GetRandHash();

1
src/test/test_bitcoin.cpp

@ -34,6 +34,7 @@ BasicTestingSetup::BasicTestingSetup()
{
assert(init_and_check_sodium() != -1);
ECC_Start();
pzcashParams = ZCJoinSplit::Unopened();
SetupEnvironment();
fPrintToDebugLog = false; // don't want to write to debug.log file
fCheckBlockIndex = true;

25
src/zcash/JoinSplit.cpp

@ -125,7 +125,7 @@ public:
JoinSplitCircuit() {}
bool verify(
const boost::array<unsigned char, ZKSNARK_PROOF_SIZE>& proof,
const ZCProof& proof,
const uint256& pubKeyHash,
const uint256& randomSeed,
const boost::array<uint256, NumInputs>& macs,
@ -140,11 +140,7 @@ public:
}
try {
r1cs_ppzksnark_proof<ppzksnark_ppT> r1cs_proof;
std::stringstream ss;
std::string proof_str(proof.begin(), proof.end());
ss.str(proof_str);
ss >> r1cs_proof;
auto r1cs_proof = proof.to_libsnark_proof<r1cs_ppzksnark_proof<ppzksnark_ppT>>();
uint256 h_sig = this->h_sig(randomSeed, nullifiers, pubKeyHash);
@ -164,7 +160,7 @@ public:
}
}
boost::array<unsigned char, ZKSNARK_PROOF_SIZE> prove(
ZCProof prove(
const boost::array<JSInput, NumInputs>& inputs,
const boost::array<JSOutput, NumOutputs>& outputs,
boost::array<Note, NumOutputs>& out_notes,
@ -264,23 +260,12 @@ public:
// estimate that it doesn't matter if we check every time.
pb.constraint_system.swap_AB_if_beneficial();
auto proof = r1cs_ppzksnark_prover<ppzksnark_ppT>(
return ZCProof(r1cs_ppzksnark_prover<ppzksnark_ppT>(
*pk,
primary_input,
aux_input,
pb.constraint_system
);
std::stringstream ss;
ss << proof;
std::string serialized_proof = ss.str();
boost::array<unsigned char, ZKSNARK_PROOF_SIZE> result_proof;
//std::cout << "proof size in bytes when serialized: " << serialized_proof.size() << std::endl;
assert(serialized_proof.size() == ZKSNARK_PROOF_SIZE);
memcpy(&result_proof[0], &serialized_proof[0], ZKSNARK_PROOF_SIZE);
return result_proof;
));
}
};

7
src/zcash/JoinSplit.hpp

@ -2,6 +2,7 @@
#define _ZCJOINSPLIT_H_
#include "Zcash.h"
#include "Proof.hpp"
#include "Address.hpp"
#include "Note.hpp"
#include "IncrementalMerkleTree.hpp"
@ -59,7 +60,7 @@ public:
virtual void loadVerifyingKey(std::string path) = 0;
virtual void saveVerifyingKey(std::string path) = 0;
virtual boost::array<unsigned char, ZKSNARK_PROOF_SIZE> prove(
virtual ZCProof prove(
const boost::array<JSInput, NumInputs>& inputs,
const boost::array<JSOutput, NumOutputs>& outputs,
boost::array<Note, NumOutputs>& out_notes,
@ -76,7 +77,7 @@ public:
) = 0;
virtual bool verify(
const boost::array<unsigned char, ZKSNARK_PROOF_SIZE>& proof,
const ZCProof& proof,
const uint256& pubKeyHash,
const uint256& randomSeed,
const boost::array<uint256, NumInputs>& hmacs,
@ -96,4 +97,4 @@ protected:
typedef libzcash::JoinSplit<ZC_NUM_JS_INPUTS,
ZC_NUM_JS_OUTPUTS> ZCJoinSplit;
#endif // _ZCJOINSPLIT_H_
#endif // _ZCJOINSPLIT_H_

258
src/zcash/Proof.cpp

@ -0,0 +1,258 @@
#include "Proof.hpp"
#include <boost/static_assert.hpp>
#include "crypto/common.h"
#include "libsnark/common/default_types/r1cs_ppzksnark_pp.hpp"
#include "libsnark/zk_proof_systems/ppzksnark/r1cs_ppzksnark/r1cs_ppzksnark.hpp"
using namespace libsnark;
typedef alt_bn128_pp curve_pp;
typedef alt_bn128_pp::G1_type curve_G1;
typedef alt_bn128_pp::G2_type curve_G2;
typedef alt_bn128_pp::GT_type curve_GT;
typedef alt_bn128_pp::Fp_type curve_Fr;
typedef alt_bn128_pp::Fq_type curve_Fq;
typedef alt_bn128_pp::Fqe_type curve_Fq2;
BOOST_STATIC_ASSERT(sizeof(mp_limb_t) == 8);
namespace libzcash {
bigint<8> fq2_to_bigint(const curve_Fq2 &e)
{
auto modq = curve_Fq::field_char();
auto c0 = e.c0.as_bigint();
auto c1 = e.c1.as_bigint();
// TODO: It should be possible to use libsnark's bigint
// to do this stuff.
bigint<8> res;
// Multiply c1 by modq
mpn_mul(res.data, c1.data, 4, modq.data, 4);
// Add c0
mpn_add(res.data, res.data, 8, c0.data, 4);
return res;
}
// Compares two bigints, returning 0 if equal, 1 if a > b, and -1 if a < b
template<mp_size_t LIMBS>
int cmp_bigint(const bigint<LIMBS> &a, const bigint<LIMBS> &b)
{
for (ssize_t i = LIMBS-1; i >= 0; i--) {
if (a.data[i] < b.data[i]) {
return -1;
} else if (a.data[i] > b.data[i]) {
return 1;
}
}
return 0;
}
// Returns whether a > b
bool cmp_fq2(const curve_Fq2 &a, const curve_Fq2 &b)
{
return cmp_bigint(fq2_to_bigint(a), fq2_to_bigint(b)) > 0;
}
// Writes a bigint in big endian
template<mp_size_t LIMBS>
void write_bigint(base_blob<8 * LIMBS * sizeof(mp_limb_t)> &blob, const bigint<LIMBS> &val)
{
auto ptr = blob.begin();
for (ssize_t i = LIMBS-1; i >= 0; i--, ptr += 8) {
WriteBE64(ptr, val.data[i]);
}
}
// Reads a bigint from big endian
template<mp_size_t LIMBS>
bigint<LIMBS> read_bigint(const base_blob<8 * LIMBS * sizeof(mp_limb_t)> &blob)
{
bigint<LIMBS> ret;
auto ptr = blob.begin();
for (ssize_t i = LIMBS-1; i >= 0; i--, ptr += 8) {
ret.data[i] = ReadBE64(ptr);
}
return ret;
}
template<>
Fq::Fq(curve_Fq element) : data()
{
write_bigint(data, element.as_bigint());
}
template<>
curve_Fq Fq::to_libsnark_fq() const
{
auto element_bigint = read_bigint<4>(data);
// Check that the integer is smaller than the modulus
auto modq = curve_Fq::field_char();
if (cmp_bigint(element_bigint, modq) != -1) {
throw std::logic_error("element is not in Fq");
}
return curve_Fq(element_bigint);
}
template<>
Fq2::Fq2(curve_Fq2 element) : data()
{
write_bigint(data, fq2_to_bigint(element));
}
template<>
curve_Fq2 Fq2::to_libsnark_fq2() const
{
auto modq = curve_Fq::field_char();
auto combined = read_bigint<8>(data);
// TODO: It should be possible to use libsnark's bigint
// to do this stuff.
bigint<5> res;
bigint<4> c0;
mpn_tdiv_qr(res.data, c0.data, 0, combined.data, 8, modq.data, 4);
if (res.data[4] != 0) {
throw std::logic_error("element is not in Fq2");
}
bigint<4> c1;
memcpy(c1.data, res.data, 4 * sizeof(mp_limb_t));
if (cmp_bigint(c1, modq) != -1) {
throw std::logic_error("element is not in Fq2");
}
return curve_Fq2(curve_Fq(c0), curve_Fq(c1));
}
template<>
CompressedG1::CompressedG1(curve_G1 point)
{
if (point.is_zero()) {
throw std::domain_error("curve point is zero");
}
point.to_affine_coordinates();
x = Fq(point.X);
y_lsb = point.Y.as_bigint().data[0] & 1;
}
template<>
curve_G1 CompressedG1::to_libsnark_g1() const
{
curve_Fq x_coordinate = x.to_libsnark_fq<curve_Fq>();
// y = +/- sqrt(x^3 + b)
auto y_coordinate = ((x_coordinate.squared() * x_coordinate) + alt_bn128_coeff_b).sqrt();
if ((y_coordinate.as_bigint().data[0] & 1) != y_lsb) {
y_coordinate = -y_coordinate;
}
curve_G1 r = curve_G1::one();
r.X = x_coordinate;
r.Y = y_coordinate;
r.Z = curve_Fq::one();
assert(r.is_well_formed());
return r;
}
template<>
CompressedG2::CompressedG2(curve_G2 point)
{
if (point.is_zero()) {
throw std::domain_error("curve point is zero");
}
point.to_affine_coordinates();
x = Fq2(point.X);
y_gt = cmp_fq2(point.Y, -(point.Y));
}
template<>
curve_G2 CompressedG2::to_libsnark_g2() const
{
auto x_coordinate = x.to_libsnark_fq2<curve_Fq2>();
// y = +/- sqrt(x^3 + b)
auto y_coordinate = ((x_coordinate.squared() * x_coordinate) + alt_bn128_twist_coeff_b).sqrt();
auto y_coordinate_neg = -y_coordinate;
if (cmp_fq2(y_coordinate, y_coordinate_neg) != y_gt) {
y_coordinate = y_coordinate_neg;
}
curve_G2 r = curve_G2::one();
r.X = x_coordinate;
r.Y = y_coordinate;
r.Z = curve_Fq2::one();
assert(r.is_well_formed());
return r;
}
template<>
ZCProof::ZCProof(const r1cs_ppzksnark_proof<curve_pp> &proof)
{
g_A = CompressedG1(proof.g_A.g);
g_A_prime = CompressedG1(proof.g_A.h);
g_B = CompressedG2(proof.g_B.g);
g_B_prime = CompressedG1(proof.g_B.h);
g_C = CompressedG1(proof.g_C.g);
g_C_prime = CompressedG1(proof.g_C.h);
g_K = CompressedG1(proof.g_K);
g_H = CompressedG1(proof.g_H);
}
template<>
r1cs_ppzksnark_proof<curve_pp> ZCProof::to_libsnark_proof() const
{
r1cs_ppzksnark_proof<curve_pp> proof;
proof.g_A.g = g_A.to_libsnark_g1<curve_G1>();
proof.g_A.h = g_A_prime.to_libsnark_g1<curve_G1>();
proof.g_B.g = g_B.to_libsnark_g2<curve_G2>();
proof.g_B.h = g_B_prime.to_libsnark_g1<curve_G1>();
proof.g_C.g = g_C.to_libsnark_g1<curve_G1>();
proof.g_C.h = g_C_prime.to_libsnark_g1<curve_G1>();
proof.g_K = g_K.to_libsnark_g1<curve_G1>();
proof.g_H = g_H.to_libsnark_g1<curve_G1>();
return proof;
}
ZCProof ZCProof::random_invalid()
{
ZCProof p;
p.g_A = curve_G1::random_element();
p.g_A_prime = curve_G1::random_element();
p.g_B = curve_G2::random_element();
p.g_B_prime = curve_G1::random_element();
p.g_C = curve_G1::random_element();
p.g_C_prime = curve_G1::random_element();
p.g_K = curve_G1::random_element();
p.g_H = curve_G1::random_element();
return p;
}
}

241
src/zcash/Proof.hpp

@ -0,0 +1,241 @@
#ifndef _ZCPROOF_H_
#define _ZCPROOF_H_
#include "serialize.h"
#include "uint256.h"
namespace libzcash {
const unsigned char G1_PREFIX_MASK = 0x02;
const unsigned char G2_PREFIX_MASK = 0x0a;
// Element in the base field
class Fq {
private:
base_blob<256> data;
public:
Fq() : data() { }
template<typename libsnark_Fq>
Fq(libsnark_Fq element);
template<typename libsnark_Fq>
libsnark_Fq to_libsnark_fq() const;
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
READWRITE(data);
}
friend bool operator==(const Fq& a, const Fq& b)
{
return (
a.data == b.data
);
}
friend bool operator!=(const Fq& a, const Fq& b)
{
return !(a == b);
}
};
// Element in the extension field
class Fq2 {
private:
base_blob<512> data;
public:
Fq2() : data() { }
template<typename libsnark_Fq2>
Fq2(libsnark_Fq2 element);
template<typename libsnark_Fq2>
libsnark_Fq2 to_libsnark_fq2() const;
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
READWRITE(data);
}
friend bool operator==(const Fq2& a, const Fq2& b)
{
return (
a.data == b.data
);
}
friend bool operator!=(const Fq2& a, const Fq2& b)
{
return !(a == b);
}
};
// Compressed point in G1
class CompressedG1 {
private:
bool y_lsb;
Fq x;
public:
CompressedG1() : y_lsb(false), x() { }
template<typename libsnark_G1>
CompressedG1(libsnark_G1 point);
template<typename libsnark_G1>
libsnark_G1 to_libsnark_g1() const;
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
unsigned char leadingByte = G1_PREFIX_MASK;
if (y_lsb) {
leadingByte |= 1;
}
READWRITE(leadingByte);
if ((leadingByte & (~1)) != G1_PREFIX_MASK) {
throw std::ios_base::failure("lead byte of G1 point not recognized");
}
y_lsb = leadingByte & 1;
READWRITE(x);
}
friend bool operator==(const CompressedG1& a, const CompressedG1& b)
{
return (
a.y_lsb == b.y_lsb &&
a.x == b.x
);
}
friend bool operator!=(const CompressedG1& a, const CompressedG1& b)
{
return !(a == b);
}
};
// Compressed point in G2
class CompressedG2 {
private:
bool y_gt;
Fq2 x;
public:
CompressedG2() : y_gt(false), x() { }
template<typename libsnark_G2>
CompressedG2(libsnark_G2 point);
template<typename libsnark_G2>
libsnark_G2 to_libsnark_g2() const;
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
unsigned char leadingByte = G2_PREFIX_MASK;
if (y_gt) {
leadingByte |= 1;
}
READWRITE(leadingByte);
if ((leadingByte & (~1)) != G2_PREFIX_MASK) {
throw std::ios_base::failure("lead byte of G2 point not recognized");
}
y_gt = leadingByte & 1;
READWRITE(x);
}
friend bool operator==(const CompressedG2& a, const CompressedG2& b)
{
return (
a.y_gt == b.y_gt &&
a.x == b.x
);
}
friend bool operator!=(const CompressedG2& a, const CompressedG2& b)
{
return !(a == b);
}
};
// Compressed zkSNARK proof
class ZCProof {
private:
CompressedG1 g_A;
CompressedG1 g_A_prime;
CompressedG2 g_B;
CompressedG1 g_B_prime;
CompressedG1 g_C;
CompressedG1 g_C_prime;
CompressedG1 g_K;
CompressedG1 g_H;
public:
ZCProof() : g_A(), g_A_prime(), g_B(), g_B_prime(), g_C(), g_C_prime(), g_K(), g_H() { }
// Produces a compressed proof using a libsnark zkSNARK proof
template<typename libsnark_proof>
ZCProof(const libsnark_proof& proof);
// Produces a libsnark zkSNARK proof out of this proof,
// or throws an exception if it is invalid.
template<typename libsnark_proof>
libsnark_proof to_libsnark_proof() const;
static ZCProof random_invalid();
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
READWRITE(g_A);
READWRITE(g_A_prime);
READWRITE(g_B);
READWRITE(g_B_prime);
READWRITE(g_C);
READWRITE(g_C_prime);
READWRITE(g_K);
READWRITE(g_H);
}
friend bool operator==(const ZCProof& a, const ZCProof& b)
{
return (
a.g_A == b.g_A &&
a.g_A_prime == b.g_A_prime &&
a.g_B == b.g_B &&
a.g_B_prime == b.g_B_prime &&
a.g_C == b.g_C &&
a.g_C_prime == b.g_C_prime &&
a.g_K == b.g_K &&
a.g_H == b.g_H
);
}
friend bool operator!=(const ZCProof& a, const ZCProof& b)
{
return !(a == b);
}
};
}
#endif // _ZCPROOF_H_

2
src/zcash/Zcash.h

@ -14,6 +14,4 @@
#define ZC_NOTEPLAINTEXT_SIZE (ZC_NOTEPLAINTEXT_LEADING + ZC_V_SIZE + ZC_RHO_SIZE + ZC_R_SIZE + ZC_MEMO_SIZE)
#define ZKSNARK_PROOF_SIZE 584
#endif // _ZCCONSTANTS_H_

Loading…
Cancel
Save