diff --git a/.gitignore b/.gitignore index b021e303f..5f91b9d02 100644 --- a/.gitignore +++ b/.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 diff --git a/src/Makefile.am b/src/Makefile.am index 5028c8360..bed78ed31 100644 --- a/src/Makefile.am +++ b/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) diff --git a/src/zcash/Address.cpp b/src/zcash/Address.cpp new file mode 100644 index 000000000..d7438ee2b --- /dev/null +++ b/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()); +} + +} \ No newline at end of file diff --git a/src/zcash/Address.hpp b/src/zcash/Address.hpp new file mode 100644 index 000000000..a240ace7f --- /dev/null +++ b/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 + 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_ \ No newline at end of file diff --git a/src/zcash/JoinSplit.cpp b/src/zcash/JoinSplit.cpp new file mode 100644 index 000000000..6f92652d3 --- /dev/null +++ b/src/zcash/JoinSplit.cpp @@ -0,0 +1,339 @@ +#include "JoinSplit.hpp" +#include "prf.h" +#include "sodium.h" + +#include +#include +#include +#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 +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 +void loadFromFile(std::string path, boost::optional& 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 +class JoinSplitCircuit : public JoinSplit { +public: + typedef default_r1cs_ppzksnark_pp ppzksnark_ppT; + typedef Fr FieldT; + + boost::optional> pk; + boost::optional> vk; + boost::optional 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 pb; + + joinsplit_gadget g(pb); + g.generate_r1cs_constraints(); + + const r1cs_constraint_system constraint_system = pb.get_constraint_system(); + r1cs_ppzksnark_keypair keypair = r1cs_ppzksnark_generator(constraint_system); + + pk = keypair.pk; + vk = keypair.vk; + } + + JoinSplitCircuit() {} + + bool verify( + const std::string& proof, + const uint256& pubKeyHash, + const uint256& randomSeed, + const boost::array& hmacs, + const boost::array& nullifiers, + const boost::array& 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 r1cs_proof; + std::stringstream ss; + ss.str(proof); + ss >> r1cs_proof; + + uint256 h_sig = this->h_sig(randomSeed, nullifiers, pubKeyHash); + + auto witness = joinsplit_gadget::witness_map( + rt, + h_sig, + hmacs, + nullifiers, + commitments, + vpub_old, + vpub_new + ); + + return r1cs_ppzksnark_verifier_strong_IC(*vk, witness, r1cs_proof); + } + + std::string prove( + const boost::array& inputs, + const boost::array& outputs, + boost::array& out_notes, + boost::array& out_ciphertexts, + uint256& out_ephemeralKey, + const uint256& pubKeyHash, + uint256& out_randomSeed, + boost::array& out_macs, + boost::array& out_nullifiers, + boost::array& 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 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 primary_input; + std::vector aux_input; + + { + protoboard pb; + { + joinsplit_gadget 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( + *pk, + primary_input, + aux_input + ); + + std::stringstream ss; + ss << proof; + + return ss.str(); + } +}; + +template +JoinSplit* JoinSplit::Generate() +{ + JoinSplitCircuit::initialize(); + auto js = new JoinSplitCircuit(); + js->generate(); + + return js; +} + +template +JoinSplit* JoinSplit::Unopened() +{ + JoinSplitCircuit::initialize(); + return new JoinSplitCircuit(); +} + +template +uint256 JoinSplit::h_sig( + const uint256& randomSeed, + const boost::array& 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 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; + +} \ No newline at end of file diff --git a/src/zcash/JoinSplit.hpp b/src/zcash/JoinSplit.hpp new file mode 100644 index 000000000..bd06c7ecb --- /dev/null +++ b/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 + +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 +class JoinSplit { +public: + static JoinSplit* Generate(); + static JoinSplit* Unopened(); + static uint256 h_sig(const uint256& randomSeed, + const boost::array& 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& inputs, + const boost::array& outputs, + boost::array& out_notes, + boost::array& out_ciphertexts, + uint256& out_ephemeralKey, + const uint256& pubKeyHash, + uint256& out_randomSeed, + boost::array& out_hmacs, + boost::array& out_nullifiers, + boost::array& 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& hmacs, + const boost::array& nullifiers, + const boost::array& commitments, + uint64_t vpub_old, + uint64_t vpub_new, + const uint256& rt + ) = 0; + +protected: + JoinSplit() {} +}; + +} + +typedef libzcash::JoinSplit ZCJoinSplit; + +#endif // _ZCJOINSPLIT_H_ \ No newline at end of file diff --git a/src/zcash/Note.cpp b/src/zcash/Note.cpp new file mode 100644 index 000000000..f768ca633 --- /dev/null +++ b/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 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 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); +} + +} \ No newline at end of file diff --git a/src/zcash/Note.hpp b/src/zcash/Note.hpp new file mode 100644 index 000000000..8cf53e5b4 --- /dev/null +++ b/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 memo; + + NotePlaintext() {} + + NotePlaintext(const Note& note, boost::array memo); + + Note note(const PaymentAddress& addr) const; + + ADD_SERIALIZE_METHODS; + + template + 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_ \ No newline at end of file diff --git a/src/zcash/circuit/gadget.tcc b/src/zcash/circuit/gadget.tcc new file mode 100644 index 000000000..6877620d0 --- /dev/null +++ b/src/zcash/circuit/gadget.tcc @@ -0,0 +1,44 @@ +template +class joinsplit_gadget : gadget { +public: + joinsplit_gadget(protoboard &pb) : gadget(pb) { + pb_variable_array 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& inputs, + const boost::array& outputs, + uint64_t vpub_old, + uint64_t vpub_new + ) { + // TODO! + } + + static r1cs_primary_input witness_map( + const uint256& rt, + const uint256& h_sig, + const boost::array& hmacs, + const boost::array& nullifiers, + const boost::array& commitments, + uint64_t vpub_old, + uint64_t vpub_new + ) { + // todo + + std::vector input_as_field_elements; + input_as_field_elements.push_back(FieldT::zero()); + + return input_as_field_elements; + } +}; \ No newline at end of file