Browse Source

Introduce new `libzcash` Zcash protocol API and crypto constructions surrounding the zkSNARK circuit.

pull/145/head
Sean Bowe 8 years ago
parent
commit
369df06583
  1. 3
      .gitignore
  2. 6
      src/Makefile.am
  3. 23
      src/zcash/Address.cpp
  4. 53
      src/zcash/Address.hpp
  5. 339
      src/zcash/JoinSplit.cpp
  6. 98
      src/zcash/JoinSplit.hpp
  7. 92
      src/zcash/Note.cpp
  8. 71
      src/zcash/Note.hpp
  9. 44
      src/zcash/circuit/gadget.tcc

3
.gitignore

@ -16,6 +16,9 @@ src/zerocash/tests/utilTest
src/zerocash/tests/zerocashTest
src/zerocash/tests/test_zerocash_pour_ppzksnark
*zcashTest.pk
*zcashTest.vk
# autoreconf
Makefile.in
aclocal.m4

6
src/Makefile.am

@ -86,6 +86,9 @@ LIBZEROCASH_H = \
zerocash/zerocash_pour_params.hpp \
zerocash/utils/util.h \
zcash/NoteEncryption.hpp \
zcash/Address.hpp \
zcash/JoinSplit.hpp \
zcash/Note.hpp \
zcash/prf.h
.PHONY: FORCE
@ -427,6 +430,9 @@ libzerocash_a_SOURCES = \
zerocash/ZerocashParams.cpp \
zerocash/utils/util.cpp \
zcash/NoteEncryption.cpp \
zcash/Address.cpp \
zcash/JoinSplit.cpp \
zcash/Note.cpp \
zcash/prf.cpp
libzerocash_a_CPPFLAGS = -fPIC -DCURVE_ALT_BN128 -DBOOST_SPIRIT_THREADSAFE -DHAVE_BUILD_INFO -D__STDC_FORMAT_MACROS -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 -std=c++11 -pipe -O2 -O0 -g -Wstack-protector -fstack-protector-all -fPIE -fvisibility=hidden -DSTATIC $(BITCOIN_INCLUDES)

23
src/zcash/Address.cpp

@ -0,0 +1,23 @@
#include "Address.hpp"
#include "NoteEncryption.hpp"
#include "prf.h"
namespace libzcash {
uint256 ViewingKey::pk_enc() {
return ZCNoteEncryption::generate_pubkey(*this);
}
ViewingKey SpendingKey::viewing_key() {
return ViewingKey(ZCNoteEncryption::generate_privkey(*this));
}
SpendingKey SpendingKey::random() {
return SpendingKey(random_uint256());
}
PaymentAddress SpendingKey::address() {
return PaymentAddress(PRF_addr_a_pk(*this), viewing_key().pk_enc());
}
}

53
src/zcash/Address.hpp

@ -0,0 +1,53 @@
#ifndef _ZCADDRESS_H_
#define _ZCADDRESS_H_
#include "uint256.h"
#include "serialize.h"
namespace libzcash {
class PaymentAddress {
public:
uint256 a_pk;
uint256 pk_enc;
PaymentAddress() : a_pk(), pk_enc() { }
PaymentAddress(uint256 a_pk, uint256 pk_enc) : a_pk(a_pk), pk_enc(pk_enc) { }
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
unsigned char leadingByte = 0x92;
READWRITE(leadingByte);
if (leadingByte != 0x92) {
throw std::ios_base::failure("unrecognized payment address lead byte");
}
READWRITE(a_pk);
READWRITE(pk_enc);
}
};
class ViewingKey : public uint256 {
public:
ViewingKey(uint256 sk_enc) : uint256(sk_enc) { }
uint256 pk_enc();
};
class SpendingKey : public uint256 {
public:
SpendingKey() : uint256() { }
SpendingKey(uint256 a_sk) : uint256(a_sk) { }
static SpendingKey random();
ViewingKey viewing_key();
PaymentAddress address();
};
}
#endif // _ZCADDRESS_H_

339
src/zcash/JoinSplit.cpp

@ -0,0 +1,339 @@
#include "JoinSplit.hpp"
#include "prf.h"
#include "sodium.h"
#include <boost/format.hpp>
#include <boost/optional.hpp>
#include <fstream>
#include "libsnark/common/default_types/r1cs_ppzksnark_pp.hpp"
#include "libsnark/zk_proof_systems/ppzksnark/r1cs_ppzksnark/r1cs_ppzksnark.hpp"
#include "libsnark/gadgetlib1/gadgets/hashes/sha256/sha256_gadget.hpp"
#include "libsnark/gadgetlib1/gadgets/merkle_tree/merkle_tree_check_read_gadget.hpp"
#include "sync.h"
using namespace libsnark;
namespace libzcash {
#include "zcash/circuit/gadget.tcc"
CCriticalSection cs_ParamsIO;
CCriticalSection cs_InitializeParams;
template<typename T>
void saveToFile(std::string path, T& obj) {
LOCK(cs_ParamsIO);
std::stringstream ss;
ss << obj;
std::ofstream fh;
fh.open(path, std::ios::binary);
ss.rdbuf()->pubseekpos(0, std::ios_base::out);
fh << ss.rdbuf();
fh.flush();
fh.close();
}
template<typename T>
void loadFromFile(std::string path, boost::optional<T>& objIn) {
LOCK(cs_ParamsIO);
std::stringstream ss;
std::ifstream fh(path, std::ios::binary);
if(!fh.is_open()) {
throw std::runtime_error((boost::format("could not load param file at %s") % path).str());
}
ss << fh.rdbuf();
fh.close();
ss.rdbuf()->pubseekpos(0, std::ios_base::in);
T obj;
ss >> obj;
objIn = std::move(obj);
}
template<size_t NumInputs, size_t NumOutputs>
class JoinSplitCircuit : public JoinSplit<NumInputs, NumOutputs> {
public:
typedef default_r1cs_ppzksnark_pp ppzksnark_ppT;
typedef Fr<ppzksnark_ppT> FieldT;
boost::optional<r1cs_ppzksnark_proving_key<ppzksnark_ppT>> pk;
boost::optional<r1cs_ppzksnark_verification_key<ppzksnark_ppT>> vk;
boost::optional<std::string> pkPath;
static void initialize() {
LOCK(cs_InitializeParams);
ppzksnark_ppT::init_public_params();
}
void setProvingKeyPath(std::string path) {
pkPath = path;
}
void loadProvingKey() {
if (!pk) {
if (!pkPath) {
throw std::runtime_error("proving key path unknown");
}
loadFromFile(*pkPath, pk);
}
}
void saveProvingKey(std::string path) {
if (pk) {
saveToFile(path, *pk);
} else {
throw std::runtime_error("cannot save proving key; key doesn't exist");
}
}
void loadVerifyingKey(std::string path) {
loadFromFile(path, vk);
}
void saveVerifyingKey(std::string path) {
if (vk) {
saveToFile(path, *vk);
} else {
throw std::runtime_error("cannot save verifying key; key doesn't exist");
}
}
void generate() {
protoboard<FieldT> pb;
joinsplit_gadget<FieldT, NumInputs, NumOutputs> g(pb);
g.generate_r1cs_constraints();
const r1cs_constraint_system<FieldT> constraint_system = pb.get_constraint_system();
r1cs_ppzksnark_keypair<ppzksnark_ppT> keypair = r1cs_ppzksnark_generator<ppzksnark_ppT>(constraint_system);
pk = keypair.pk;
vk = keypair.vk;
}
JoinSplitCircuit() {}
bool verify(
const std::string& proof,
const uint256& pubKeyHash,
const uint256& randomSeed,
const boost::array<uint256, NumInputs>& hmacs,
const boost::array<uint256, NumInputs>& nullifiers,
const boost::array<uint256, NumOutputs>& commitments,
uint64_t vpub_old,
uint64_t vpub_new,
const uint256& rt
) {
if (!vk) {
throw std::runtime_error("JoinSplit verifying key not loaded");
}
r1cs_ppzksnark_proof<ppzksnark_ppT> r1cs_proof;
std::stringstream ss;
ss.str(proof);
ss >> r1cs_proof;
uint256 h_sig = this->h_sig(randomSeed, nullifiers, pubKeyHash);
auto witness = joinsplit_gadget<FieldT, NumInputs, NumOutputs>::witness_map(
rt,
h_sig,
hmacs,
nullifiers,
commitments,
vpub_old,
vpub_new
);
return r1cs_ppzksnark_verifier_strong_IC<ppzksnark_ppT>(*vk, witness, r1cs_proof);
}
std::string prove(
const boost::array<JSInput, NumInputs>& inputs,
const boost::array<JSOutput, NumOutputs>& outputs,
boost::array<Note, NumOutputs>& out_notes,
boost::array<ZCNoteEncryption::Ciphertext, NumOutputs>& out_ciphertexts,
uint256& out_ephemeralKey,
const uint256& pubKeyHash,
uint256& out_randomSeed,
boost::array<uint256, NumInputs>& out_macs,
boost::array<uint256, NumInputs>& out_nullifiers,
boost::array<uint256, NumOutputs>& out_commitments,
uint64_t vpub_old,
uint64_t vpub_new,
const uint256& rt
) {
if (!pk) {
throw std::runtime_error("JoinSplit proving key not loaded");
}
// Compute nullifiers of inputs
for (size_t i = 0; i < NumInputs; i++) {
out_nullifiers[i] = inputs[i].nullifier();
}
// Sample randomSeed
out_randomSeed = random_uint256();
// Compute h_sig
uint256 h_sig = this->h_sig(out_randomSeed, out_nullifiers, pubKeyHash);
// Sample phi
uint256 phi = random_uint256();
// Compute notes for outputs
for (size_t i = 0; i < NumOutputs; i++) {
// Sample r
uint256 r = random_uint256();
out_notes[i] = outputs[i].note(phi, r, i, h_sig);
}
// Compute the output commitments
for (size_t i = 0; i < NumOutputs; i++) {
out_commitments[i] = out_notes[i].cm();
}
// Encrypt the ciphertexts containing the note
// plaintexts to the recipients of the value.
{
ZCNoteEncryption encryptor(h_sig);
for (size_t i = 0; i < NumOutputs; i++) {
// TODO: expose memo in the public interface
// 0xF6 is invalid UTF8 as per spec
boost::array<unsigned char, ZC_MEMO_SIZE> memo = {{0xF6}};
NotePlaintext pt(out_notes[i], memo);
out_ciphertexts[i] = pt.encrypt(encryptor, outputs[i].addr.pk_enc);
}
out_ephemeralKey = encryptor.get_epk();
}
// Authenticate h_sig with each of the input
// spending keys, producing macs which protect
// against malleability.
for (size_t i = 0; i < NumInputs; i++) {
out_macs[i] = PRF_pk(inputs[i].key, i, h_sig);
}
std::vector<FieldT> primary_input;
std::vector<FieldT> aux_input;
{
protoboard<FieldT> pb;
{
joinsplit_gadget<FieldT, NumInputs, NumOutputs> g(pb);
g.generate_r1cs_constraints();
g.generate_r1cs_witness(
phi,
rt,
h_sig,
inputs,
out_notes,
vpub_old,
vpub_new
);
}
if (!pb.is_satisfied()) {
throw std::invalid_argument("Constraint system not satisfied by inputs");
}
primary_input = pb.primary_input();
aux_input = pb.auxiliary_input();
}
auto proof = r1cs_ppzksnark_prover<ppzksnark_ppT>(
*pk,
primary_input,
aux_input
);
std::stringstream ss;
ss << proof;
return ss.str();
}
};
template<size_t NumInputs, size_t NumOutputs>
JoinSplit<NumInputs, NumOutputs>* JoinSplit<NumInputs, NumOutputs>::Generate()
{
JoinSplitCircuit<NumInputs, NumOutputs>::initialize();
auto js = new JoinSplitCircuit<NumInputs, NumOutputs>();
js->generate();
return js;
}
template<size_t NumInputs, size_t NumOutputs>
JoinSplit<NumInputs, NumOutputs>* JoinSplit<NumInputs, NumOutputs>::Unopened()
{
JoinSplitCircuit<NumInputs, NumOutputs>::initialize();
return new JoinSplitCircuit<NumInputs, NumOutputs>();
}
template<size_t NumInputs, size_t NumOutputs>
uint256 JoinSplit<NumInputs, NumOutputs>::h_sig(
const uint256& randomSeed,
const boost::array<uint256, NumInputs>& nullifiers,
const uint256& pubKeyHash
) {
unsigned char personalization[crypto_generichash_blake2b_PERSONALBYTES]
= {'Z','c','a','s','h','C','o','m','p','u','t','e','h','S','i','g'};
std::vector<unsigned char> block(randomSeed.begin(), randomSeed.end());
for (size_t i = 0; i < NumInputs; i++) {
block.insert(block.end(), nullifiers[i].begin(), nullifiers[i].end());
}
block.insert(block.end(), pubKeyHash.begin(), pubKeyHash.end());
uint256 output;
if (crypto_generichash_blake2b_salt_personal(output.begin(), 32,
&block[0], block.size(),
NULL, 0, // No key.
NULL, // No salt.
personalization
) != 0)
{
throw std::logic_error("hash function failure");
}
return output;
}
Note JSOutput::note(const uint256& phi, const uint256& r, size_t i, const uint256& h_sig) const {
uint256 rho = PRF_rho(phi, i, h_sig);
return Note(addr.a_pk, value, rho, r);
}
JSOutput::JSOutput() : addr(uint256(), uint256()), value(0) {
SpendingKey a_sk(random_uint256());
addr = a_sk.address();
}
JSInput::JSInput() : witness(ZCIncrementalMerkleTree().witness()),
key(random_uint256()) {
note = Note(key.address().a_pk, 0, random_uint256(), random_uint256());
ZCIncrementalMerkleTree dummy_tree;
dummy_tree.append(note.cm());
witness = dummy_tree.witness();
}
template class JoinSplit<ZC_NUM_JS_INPUTS,
ZC_NUM_JS_OUTPUTS>;
}

98
src/zcash/JoinSplit.hpp

@ -0,0 +1,98 @@
#ifndef _ZCJOINSPLIT_H_
#define _ZCJOINSPLIT_H_
#include "Zcash.h"
#include "Address.hpp"
#include "Note.hpp"
#include "IncrementalMerkleTree.hpp"
#include "NoteEncryption.hpp"
#include "uint256.h"
#include <boost/array.hpp>
namespace libzcash {
class JSInput {
public:
ZCIncrementalWitness witness;
Note note;
SpendingKey key;
JSInput();
JSInput(ZCIncrementalWitness witness,
Note note,
SpendingKey key) : witness(witness), note(note), key(key) { }
uint256 nullifier() const {
return note.nullifier(key);
}
};
class JSOutput {
public:
PaymentAddress addr;
uint64_t value;
JSOutput();
JSOutput(PaymentAddress addr, uint64_t value) : addr(addr), value(value) { }
Note note(const uint256& phi, const uint256& r, size_t i, const uint256& h_sig) const;
};
template<size_t NumInputs, size_t NumOutputs>
class JoinSplit {
public:
static JoinSplit<NumInputs, NumOutputs>* Generate();
static JoinSplit<NumInputs, NumOutputs>* Unopened();
static uint256 h_sig(const uint256& randomSeed,
const boost::array<uint256, NumInputs>& nullifiers,
const uint256& pubKeyHash
);
// TODO: #789
virtual void setProvingKeyPath(std::string) = 0;
virtual void loadProvingKey() = 0;
virtual void saveProvingKey(std::string path) = 0;
virtual void loadVerifyingKey(std::string path) = 0;
virtual void saveVerifyingKey(std::string path) = 0;
virtual std::string prove(
const boost::array<JSInput, NumInputs>& inputs,
const boost::array<JSOutput, NumOutputs>& outputs,
boost::array<Note, NumOutputs>& out_notes,
boost::array<ZCNoteEncryption::Ciphertext, NumOutputs>& out_ciphertexts,
uint256& out_ephemeralKey,
const uint256& pubKeyHash,
uint256& out_randomSeed,
boost::array<uint256, NumInputs>& out_hmacs,
boost::array<uint256, NumInputs>& out_nullifiers,
boost::array<uint256, NumOutputs>& out_commitments,
uint64_t vpub_old,
uint64_t vpub_new,
const uint256& rt
) = 0;
virtual bool verify(
const std::string& proof,
const uint256& pubKeyHash,
const uint256& randomSeed,
const boost::array<uint256, NumInputs>& hmacs,
const boost::array<uint256, NumInputs>& nullifiers,
const boost::array<uint256, NumOutputs>& commitments,
uint64_t vpub_old,
uint64_t vpub_new,
const uint256& rt
) = 0;
protected:
JoinSplit() {}
};
}
typedef libzcash::JoinSplit<ZC_NUM_JS_INPUTS,
ZC_NUM_JS_OUTPUTS> ZCJoinSplit;
#endif // _ZCJOINSPLIT_H_

92
src/zcash/Note.cpp

@ -0,0 +1,92 @@
#include "Note.hpp"
#include "prf.h"
#include "crypto/sha256.h"
#include "zerocash/utils/util.h"
#include "version.h"
#include "streams.h"
namespace libzcash {
Note::Note() {
a_pk = random_uint256();
rho = random_uint256();
r = random_uint256();
value = 0;
}
uint256 Note::cm() const {
unsigned char discriminant = 0xb0;
CSHA256 hasher;
hasher.Write(&discriminant, 1);
hasher.Write(a_pk.begin(), 32);
std::vector<unsigned char> value_vec(sizeof(value), 0);
libzerocash::convertIntToBytesVector(value, value_vec);
hasher.Write(&value_vec[0], value_vec.size());
hasher.Write(rho.begin(), 32);
hasher.Write(r.begin(), 32);
uint256 result;
hasher.Finalize(result.begin());
return result;
}
uint256 Note::nullifier(const SpendingKey& a_sk) const {
return PRF_nf(a_sk, rho);
}
NotePlaintext::NotePlaintext(
const Note& note,
boost::array<unsigned char, ZCASH_MEMO_SIZE> memo) : memo(memo)
{
value = note.value;
rho = note.rho;
r = note.r;
}
Note NotePlaintext::note(const PaymentAddress& addr) const
{
return Note(addr.a_pk, value, rho, r);
}
NotePlaintext NotePlaintext::decrypt(const ZCNoteDecryption& decryptor,
const ZCNoteDecryption::Ciphertext& ciphertext,
const uint256& ephemeralKey,
const uint256& h_sig,
unsigned char nonce
)
{
auto plaintext = decryptor.decrypt(ciphertext, ephemeralKey, h_sig, nonce);
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << plaintext;
NotePlaintext ret;
ss >> ret;
assert(ss.size() == 0);
return ret;
}
ZCNoteEncryption::Ciphertext NotePlaintext::encrypt(ZCNoteEncryption& encryptor,
const uint256& pk_enc
) const
{
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << (*this);
ZCNoteEncryption::Plaintext pt;
assert(pt.size() == ss.size());
memcpy(&pt[0], &ss[0], pt.size());
return encryptor.encrypt(pk_enc, pt);
}
}

71
src/zcash/Note.hpp

@ -0,0 +1,71 @@
#ifndef _ZCNOTE_H_
#define _ZCNOTE_H_
#include "uint256.h"
#include "Zcash.h"
#include "Address.hpp"
#include "NoteEncryption.hpp"
namespace libzcash {
class Note {
public:
uint256 a_pk;
uint64_t value;
uint256 rho;
uint256 r;
Note(uint256 a_pk, uint64_t value, uint256 rho, uint256 r)
: a_pk(a_pk), value(value), rho(rho), r(r) {}
Note();
uint256 cm() const;
uint256 nullifier(const SpendingKey& a_sk) const;
};
class NotePlaintext {
public:
uint64_t value;
uint256 rho;
uint256 r;
boost::array<unsigned char, ZC_MEMO_SIZE> memo;
NotePlaintext() {}
NotePlaintext(const Note& note, boost::array<unsigned char, ZC_MEMO_SIZE> memo);
Note note(const PaymentAddress& addr) const;
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
unsigned char leadingByte = 0x00;
READWRITE(leadingByte);
if (leadingByte != 0x00) {
throw std::ios_base::failure("lead byte of NotePlaintext is not recognized");
}
READWRITE(value);
READWRITE(rho);
READWRITE(r);
READWRITE(memo);
}
static NotePlaintext decrypt(const ZCNoteDecryption& decryptor,
const ZCNoteDecryption::Ciphertext& ciphertext,
const uint256& ephemeralKey,
const uint256& h_sig,
unsigned char nonce
);
ZCNoteEncryption::Ciphertext encrypt(ZCNoteEncryption& encryptor,
const uint256& pk_enc
) const;
};
}
#endif // _ZCNOTE_H_

44
src/zcash/circuit/gadget.tcc

@ -0,0 +1,44 @@
template<typename FieldT, size_t NumInputs, size_t NumOutputs>
class joinsplit_gadget : gadget<FieldT> {
public:
joinsplit_gadget(protoboard<FieldT> &pb) : gadget<FieldT>(pb) {
pb_variable_array<FieldT> test;
test.allocate(pb, 1);
pb.set_input_sizes(1);
// TODO!
}
void generate_r1cs_constraints() {
// TODO!
}
void generate_r1cs_witness(
const uint256& phi,
const uint256& rt,
const uint256& h_sig,
const boost::array<JSInput, NumInputs>& inputs,
const boost::array<Note, NumOutputs>& outputs,
uint64_t vpub_old,
uint64_t vpub_new
) {
// TODO!
}
static r1cs_primary_input<FieldT> witness_map(
const uint256& rt,
const uint256& h_sig,
const boost::array<uint256, NumInputs>& hmacs,
const boost::array<uint256, NumInputs>& nullifiers,
const boost::array<uint256, NumOutputs>& commitments,
uint64_t vpub_old,
uint64_t vpub_new
) {
// todo
std::vector<FieldT> input_as_field_elements;
input_as_field_elements.push_back(FieldT::zero());
return input_as_field_elements;
}
};
Loading…
Cancel
Save