#include "JoinSplit.hpp" #include "prf.h" #include "sodium.h" #include "zcash/util.h" #include #include #include #include #include #include #include #include #include #include "tinyformat.h" #include "sync.h" #include "amount.h" using namespace libsnark; namespace libzcash { #include "zcash/circuit/gadget.tcc" CCriticalSection cs_ParamsIO; CCriticalSection cs_LoadKeys; template void saveToFile(const 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(const std::string path, T& objIn) { LOCK(cs_ParamsIO); std::stringstream ss; std::ifstream fh(path, std::ios::binary); if(!fh.is_open()) { throw std::runtime_error(strprintf("could not load param file at %s", path)); } 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; r1cs_ppzksnark_verification_key vk; r1cs_ppzksnark_processed_verification_key vk_precomp; std::string pkPath; JoinSplitCircuit(const std::string vkPath, const std::string pkPath) : pkPath(pkPath) { loadFromFile(vkPath, vk); vk_precomp = r1cs_ppzksnark_verifier_process_vk(vk); } ~JoinSplitCircuit() {} static void generate(const std::string r1csPath, const std::string vkPath, const std::string pkPath) { protoboard pb; joinsplit_gadget g(pb); g.generate_r1cs_constraints(); auto r1cs = pb.get_constraint_system(); saveToFile(r1csPath, r1cs); r1cs_ppzksnark_keypair keypair = r1cs_ppzksnark_generator(r1cs); saveToFile(vkPath, keypair.vk); saveToFile(pkPath, keypair.pk); } bool verify( const ZCProof& proof, ProofVerifier& verifier, const uint256& pubKeyHash, const uint256& randomSeed, const boost::array& macs, const boost::array& nullifiers, const boost::array& commitments, uint64_t vpub_old, uint64_t vpub_new, const uint256& rt ) { try { auto r1cs_proof = proof.to_libsnark_proof>(); uint256 h_sig = this->h_sig(randomSeed, nullifiers, pubKeyHash); auto witness = joinsplit_gadget::witness_map( rt, h_sig, macs, nullifiers, commitments, vpub_old, vpub_new ); return verifier.check( vk, vk_precomp, witness, r1cs_proof ); } catch (...) { return false; } } ZCProof 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, bool computeProof, uint256 *out_esk // Payment disclosure ) { if (vpub_old > MAX_MONEY) { throw std::invalid_argument("nonsensical vpub_old value"); } if (vpub_new > MAX_MONEY) { throw std::invalid_argument("nonsensical vpub_new value"); } uint64_t lhs_value = vpub_old; uint64_t rhs_value = vpub_new; for (size_t i = 0; i < NumInputs; i++) { // Sanity checks of input { // If note has nonzero value if (inputs[i].note.value != 0) { // The witness root must equal the input root. if (inputs[i].witness.root() != rt) { throw std::invalid_argument("joinsplit not anchored to the correct root"); } // The tree must witness the correct element if (inputs[i].note.cm() != inputs[i].witness.element()) { throw std::invalid_argument("witness of wrong element for joinsplit input"); } } // Ensure we have the key to this note. if (inputs[i].note.a_pk != inputs[i].key.address().a_pk) { throw std::invalid_argument("input note not authorized to spend with given key"); } // Balance must be sensical if (inputs[i].note.value > MAX_MONEY) { throw std::invalid_argument("nonsensical input note value"); } lhs_value += inputs[i].note.value; if (lhs_value > MAX_MONEY) { throw std::invalid_argument("nonsensical left hand size of joinsplit balance"); } } // Compute nullifier of input 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 uint252 phi = random_uint252(); // Compute notes for outputs for (size_t i = 0; i < NumOutputs; i++) { // Sanity checks of output { if (outputs[i].value > MAX_MONEY) { throw std::invalid_argument("nonsensical output value"); } rhs_value += outputs[i].value; if (rhs_value > MAX_MONEY) { throw std::invalid_argument("nonsensical right hand side of joinsplit balance"); } } // Sample r uint256 r = random_uint256(); out_notes[i] = outputs[i].note(phi, r, i, h_sig); } if (lhs_value != rhs_value) { throw std::invalid_argument("invalid joinsplit balance"); } // 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++) { NotePlaintext pt(out_notes[i], outputs[i].memo); out_ciphertexts[i] = pt.encrypt(encryptor, outputs[i].addr.pk_enc); } out_ephemeralKey = encryptor.get_epk(); // !!! Payment disclosure START if (out_esk != nullptr) { *out_esk = encryptor.get_esk(); } // !!! Payment disclosure END } // 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); } if (!computeProof) { return ZCProof(); } 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 ); } // The constraint system must be satisfied or there is an unimplemented // or incorrect sanity check above. Or the constraint system is broken! assert(pb.is_satisfied()); // TODO: These are copies, which is not strictly necessary. std::vector primary_input = pb.primary_input(); std::vector aux_input = pb.auxiliary_input(); // Swap A and B if it's beneficial (less arithmetic in G2) // In our circuit, we already know that it's beneficial // to swap, but it takes so little time to perform this // estimate that it doesn't matter if we check every time. pb.constraint_system.swap_AB_if_beneficial(); std::ifstream fh(pkPath, std::ios::binary); if(!fh.is_open()) { throw std::runtime_error(strprintf("could not load param file at %s", pkPath)); } return ZCProof(r1cs_ppzksnark_prover_streaming( fh, primary_input, aux_input, pb.constraint_system )); } }; template void JoinSplit::Generate(const std::string r1csPath, const std::string vkPath, const std::string pkPath) { initialize_curve_params(); JoinSplitCircuit::generate(r1csPath, vkPath, pkPath); } template JoinSplit* JoinSplit::Prepared(const std::string vkPath, const std::string pkPath) { initialize_curve_params(); return new JoinSplitCircuit(vkPath, pkPath); } template uint256 JoinSplit::h_sig( const uint256& randomSeed, const boost::array& nullifiers, const uint256& pubKeyHash ) { const 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 uint252& 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 = SpendingKey::random(); addr = a_sk.address(); } JSInput::JSInput() : witness(ZCIncrementalMerkleTree().witness()), key(SpendingKey::random()) { 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; }