#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; }