Implement zkSNARK compression.

This commit is contained in:
Sean Bowe 2016-08-16 14:08:59 -06:00
parent 365845216b
commit f0dab51cf3
19 changed files with 21646 additions and 588 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 Normal file
View File

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

View File

@ -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) { }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

@ -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 Normal file
View File

@ -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 Normal file
View File

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

View File

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