1 #include "JoinSplit.hpp"
5 #include "zcash/util.h"
9 #include <boost/foreach.hpp>
10 #include <boost/format.hpp>
11 #include <boost/optional.hpp>
13 #include <libsnark/common/default_types/r1cs_ppzksnark_pp.hpp>
14 #include <libsnark/zk_proof_systems/ppzksnark/r1cs_ppzksnark/r1cs_ppzksnark.hpp>
15 #include <libsnark/gadgetlib1/gadgets/hashes/sha256/sha256_gadget.hpp>
16 #include <libsnark/gadgetlib1/gadgets/merkle_tree/merkle_tree_check_read_gadget.hpp>
17 #include "tinyformat.h"
20 extern int64_t MAX_MONEY;
22 #include "librustzcash.h"
26 using namespace libsnark;
30 #include "zcash/circuit/gadget.tcc"
32 static CCriticalSection cs_ParamsIO;
35 void saveToFile(const std::string path, T& obj) {
41 fh.open(path, std::ios::binary);
42 ss.rdbuf()->pubseekpos(0, std::ios_base::out);
49 void loadFromFile(const std::string path, T& objIn) {
53 std::ifstream fh(path, std::ios::binary);
56 throw std::runtime_error(strprintf("could not load param file at %s", path));
62 ss.rdbuf()->pubseekpos(0, std::ios_base::in);
67 objIn = std::move(obj);
70 template<size_t NumInputs, size_t NumOutputs>
71 class JoinSplitCircuit : public JoinSplit<NumInputs, NumOutputs> {
73 typedef default_r1cs_ppzksnark_pp ppzksnark_ppT;
74 typedef Fr<ppzksnark_ppT> FieldT;
76 r1cs_ppzksnark_verification_key<ppzksnark_ppT> vk;
77 r1cs_ppzksnark_processed_verification_key<ppzksnark_ppT> vk_precomp;
80 JoinSplitCircuit(const std::string vkPath, const std::string pkPath) : pkPath(pkPath) {
81 loadFromFile(vkPath, vk);
82 vk_precomp = r1cs_ppzksnark_verifier_process_vk(vk);
84 ~JoinSplitCircuit() {}
86 static void generate(const std::string r1csPath,
87 const std::string vkPath,
88 const std::string pkPath)
90 protoboard<FieldT> pb;
92 joinsplit_gadget<FieldT, NumInputs, NumOutputs> g(pb);
93 g.generate_r1cs_constraints();
95 auto r1cs = pb.get_constraint_system();
97 saveToFile(r1csPath, r1cs);
99 r1cs_ppzksnark_keypair<ppzksnark_ppT> keypair = r1cs_ppzksnark_generator<ppzksnark_ppT>(r1cs);
101 saveToFile(vkPath, keypair.vk);
102 saveToFile(pkPath, keypair.pk);
105 void saveR1CS(std::string path)
107 protoboard<FieldT> pb;
108 joinsplit_gadget<FieldT, NumInputs, NumOutputs> g(pb);
109 g.generate_r1cs_constraints();
110 r1cs_constraint_system<FieldT> r1cs = pb.get_constraint_system();
112 saveToFile(path, r1cs);
116 const PHGRProof& proof,
117 ProofVerifier& verifier,
118 const uint256& joinSplitPubKey,
119 const uint256& randomSeed,
120 const std::array<uint256, NumInputs>& macs,
121 const std::array<uint256, NumInputs>& nullifiers,
122 const std::array<uint256, NumOutputs>& commitments,
128 auto r1cs_proof = proof.to_libsnark_proof<r1cs_ppzksnark_proof<ppzksnark_ppT>>();
130 uint256 h_sig = this->h_sig(randomSeed, nullifiers, joinSplitPubKey);
132 auto witness = joinsplit_gadget<FieldT, NumInputs, NumOutputs>::witness_map(
142 return verifier.check(
155 const std::array<JSInput, NumInputs>& inputs,
156 const std::array<JSOutput, NumOutputs>& outputs,
157 std::array<SproutNote, NumOutputs>& out_notes,
158 std::array<ZCNoteEncryption::Ciphertext, NumOutputs>& out_ciphertexts,
159 uint256& out_ephemeralKey,
160 const uint256& joinSplitPubKey,
161 uint256& out_randomSeed,
162 std::array<uint256, NumInputs>& out_macs,
163 std::array<uint256, NumInputs>& out_nullifiers,
164 std::array<uint256, NumOutputs>& out_commitments,
169 uint256 *out_esk // Payment disclosure
171 if (vpub_old > MAX_MONEY) {
172 throw std::invalid_argument("nonsensical vpub_old value");
175 if (vpub_new > MAX_MONEY) {
176 throw std::invalid_argument("nonsensical vpub_new value");
179 uint64_t lhs_value = vpub_old;
180 uint64_t rhs_value = vpub_new;
182 for (size_t i = 0; i < NumInputs; i++) {
183 // Sanity checks of input
185 // If note has nonzero value
186 if (inputs[i].note.value() != 0) {
187 // The witness root must equal the input root.
188 if (inputs[i].witness.root() != rt) {
189 throw std::invalid_argument("joinsplit not anchored to the correct root");
192 // The tree must witness the correct element
193 if (inputs[i].note.cm() != inputs[i].witness.element()) {
194 throw std::invalid_argument("witness of wrong element for joinsplit input");
198 // Ensure we have the key to this note.
199 if (inputs[i].note.a_pk != inputs[i].key.address().a_pk) {
200 throw std::invalid_argument("input note not authorized to spend with given key");
203 // Balance must be sensical
204 if (inputs[i].note.value() > MAX_MONEY) {
205 throw std::invalid_argument("nonsensical input note value");
208 lhs_value += inputs[i].note.value();
210 if (lhs_value > MAX_MONEY) {
211 throw std::invalid_argument("nonsensical left hand size of joinsplit balance");
215 // Compute nullifier of input
216 out_nullifiers[i] = inputs[i].nullifier();
220 out_randomSeed = random_uint256();
223 uint256 h_sig = this->h_sig(out_randomSeed, out_nullifiers, joinSplitPubKey);
226 uint252 phi = random_uint252();
228 // Compute notes for outputs
229 for (size_t i = 0; i < NumOutputs; i++) {
230 // Sanity checks of output
232 if (outputs[i].value > MAX_MONEY) {
233 throw std::invalid_argument("nonsensical output value");
236 rhs_value += outputs[i].value;
238 if (rhs_value > MAX_MONEY) {
239 throw std::invalid_argument("nonsensical right hand side of joinsplit balance");
244 uint256 r = random_uint256();
246 out_notes[i] = outputs[i].note(phi, r, i, h_sig);
249 if (lhs_value != rhs_value) {
250 throw std::invalid_argument("invalid joinsplit balance");
253 // Compute the output commitments
254 for (size_t i = 0; i < NumOutputs; i++) {
255 out_commitments[i] = out_notes[i].cm();
258 // Encrypt the ciphertexts containing the note
259 // plaintexts to the recipients of the value.
261 ZCNoteEncryption encryptor(h_sig);
263 for (size_t i = 0; i < NumOutputs; i++) {
264 SproutNotePlaintext pt(out_notes[i], outputs[i].memo);
266 out_ciphertexts[i] = pt.encrypt(encryptor, outputs[i].addr.pk_enc);
269 out_ephemeralKey = encryptor.get_epk();
271 // !!! Payment disclosure START
272 if (out_esk != nullptr) {
273 *out_esk = encryptor.get_esk();
275 // !!! Payment disclosure END
278 // Authenticate h_sig with each of the input
279 // spending keys, producing macs which protect
280 // against malleability.
281 for (size_t i = 0; i < NumInputs; i++) {
282 out_macs[i] = PRF_pk(inputs[i].key, i, h_sig);
285 if (makeGrothProof) {
292 CDataStream ss1(SER_NETWORK, PROTOCOL_VERSION);
293 ss1 << inputs[0].witness.path();
294 std::vector<unsigned char> auth1(ss1.begin(), ss1.end());
296 CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION);
297 ss2 << inputs[1].witness.path();
298 std::vector<unsigned char> auth2(ss2.begin(), ss2.end());
300 librustzcash_sprout_prove(
307 inputs[0].key.begin(),
308 inputs[0].note.value(),
309 inputs[0].note.rho.begin(),
310 inputs[0].note.r.begin(),
313 inputs[1].key.begin(),
314 inputs[1].note.value(),
315 inputs[1].note.rho.begin(),
316 inputs[1].note.r.begin(),
319 out_notes[0].a_pk.begin(),
320 out_notes[0].value(),
321 out_notes[0].r.begin(),
323 out_notes[1].a_pk.begin(),
324 out_notes[1].value(),
325 out_notes[1].r.begin(),
338 protoboard<FieldT> pb;
340 joinsplit_gadget<FieldT, NumInputs, NumOutputs> g(pb);
341 g.generate_r1cs_constraints();
342 g.generate_r1cs_witness(
353 // The constraint system must be satisfied or there is an unimplemented
354 // or incorrect sanity check above. Or the constraint system is broken!
355 assert(pb.is_satisfied());
357 // TODO: These are copies, which is not strictly necessary.
358 std::vector<FieldT> primary_input = pb.primary_input();
359 std::vector<FieldT> aux_input = pb.auxiliary_input();
361 // Swap A and B if it's beneficial (less arithmetic in G2)
362 // In our circuit, we already know that it's beneficial
363 // to swap, but it takes so little time to perform this
364 // estimate that it doesn't matter if we check every time.
365 pb.constraint_system.swap_AB_if_beneficial();
367 std::ifstream fh(pkPath, std::ios::binary);
370 throw std::runtime_error(strprintf("could not load param file at %s", pkPath));
373 return PHGRProof(r1cs_ppzksnark_prover_streaming<ppzksnark_ppT>(
382 template<size_t NumInputs, size_t NumOutputs>
383 void JoinSplit<NumInputs, NumOutputs>::Generate(const std::string r1csPath,
384 const std::string vkPath,
385 const std::string pkPath)
387 initialize_curve_params();
388 JoinSplitCircuit<NumInputs, NumOutputs>::generate(r1csPath, vkPath, pkPath);
391 template<size_t NumInputs, size_t NumOutputs>
392 JoinSplit<NumInputs, NumOutputs>* JoinSplit<NumInputs, NumOutputs>::Prepared(const std::string vkPath,
393 const std::string pkPath)
395 initialize_curve_params();
396 return new JoinSplitCircuit<NumInputs, NumOutputs>(vkPath, pkPath);
399 template<size_t NumInputs, size_t NumOutputs>
400 uint256 JoinSplit<NumInputs, NumOutputs>::h_sig(
401 const uint256& randomSeed,
402 const std::array<uint256, NumInputs>& nullifiers,
403 const uint256& joinSplitPubKey
405 const unsigned char personalization[crypto_generichash_blake2b_PERSONALBYTES]
406 = {'Z','c','a','s','h','C','o','m','p','u','t','e','h','S','i','g'};
408 std::vector<unsigned char> block(randomSeed.begin(), randomSeed.end());
410 for (size_t i = 0; i < NumInputs; i++) {
411 block.insert(block.end(), nullifiers[i].begin(), nullifiers[i].end());
414 block.insert(block.end(), joinSplitPubKey.begin(), joinSplitPubKey.end());
418 if (crypto_generichash_blake2b_salt_personal(output.begin(), 32,
419 &block[0], block.size(),
425 throw std::logic_error("hash function failure");
431 SproutNote JSOutput::note(const uint252& phi, const uint256& r, size_t i, const uint256& h_sig) const {
432 uint256 rho = PRF_rho(phi, i, h_sig);
434 return SproutNote(addr.a_pk, value, rho, r);
437 JSOutput::JSOutput() : addr(uint256(), uint256()), value(0) {
438 SproutSpendingKey a_sk = SproutSpendingKey::random();
439 addr = a_sk.address();
442 JSInput::JSInput() : witness(SproutMerkleTree().witness()),
443 key(SproutSpendingKey::random()) {
444 note = SproutNote(key.address().a_pk, 0, random_uint256(), random_uint256());
445 SproutMerkleTree dummy_tree;
446 dummy_tree.append(note.cm());
447 witness = dummy_tree.witness();
450 template class JoinSplit<ZC_NUM_JS_INPUTS,