]>
Commit | Line | Data |
---|---|---|
369df065 SB |
1 | #include "JoinSplit.hpp" |
2 | #include "prf.h" | |
3 | #include "sodium.h" | |
4 | ||
81469bbb | 5 | #include "zcash/util.h" |
074eb3a2 SB |
6 | |
7 | #include <memory> | |
8 | ||
5e61a78f | 9 | #include <boost/foreach.hpp> |
369df065 SB |
10 | #include <boost/format.hpp> |
11 | #include <boost/optional.hpp> | |
12 | #include <fstream> | |
fee88353 JG |
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> | |
4305a562 | 17 | #include "tinyformat.h" |
369df065 | 18 | #include "sync.h" |
c4643bd9 | 19 | #include "amount.h" |
369df065 SB |
20 | |
21 | using namespace libsnark; | |
22 | ||
23 | namespace libzcash { | |
24 | ||
25 | #include "zcash/circuit/gadget.tcc" | |
26 | ||
27 | CCriticalSection cs_ParamsIO; | |
78df9f02 | 28 | CCriticalSection cs_LoadKeys; |
369df065 SB |
29 | |
30 | template<typename T> | |
1a9543d0 | 31 | void saveToFile(const std::string path, T& obj) { |
369df065 SB |
32 | LOCK(cs_ParamsIO); |
33 | ||
34 | std::stringstream ss; | |
35 | ss << obj; | |
36 | std::ofstream fh; | |
37 | fh.open(path, std::ios::binary); | |
38 | ss.rdbuf()->pubseekpos(0, std::ios_base::out); | |
39 | fh << ss.rdbuf(); | |
40 | fh.flush(); | |
41 | fh.close(); | |
42 | } | |
43 | ||
44 | template<typename T> | |
1a9543d0 | 45 | void loadFromFile(const std::string path, T& objIn) { |
369df065 SB |
46 | LOCK(cs_ParamsIO); |
47 | ||
48 | std::stringstream ss; | |
49 | std::ifstream fh(path, std::ios::binary); | |
50 | ||
51 | if(!fh.is_open()) { | |
4305a562 | 52 | throw std::runtime_error(strprintf("could not load param file at %s", path)); |
369df065 SB |
53 | } |
54 | ||
55 | ss << fh.rdbuf(); | |
56 | fh.close(); | |
57 | ||
58 | ss.rdbuf()->pubseekpos(0, std::ios_base::in); | |
59 | ||
60 | T obj; | |
61 | ss >> obj; | |
62 | ||
63 | objIn = std::move(obj); | |
64 | } | |
65 | ||
66 | template<size_t NumInputs, size_t NumOutputs> | |
67 | class JoinSplitCircuit : public JoinSplit<NumInputs, NumOutputs> { | |
68 | public: | |
69 | typedef default_r1cs_ppzksnark_pp ppzksnark_ppT; | |
70 | typedef Fr<ppzksnark_ppT> FieldT; | |
71 | ||
1a9543d0 SB |
72 | r1cs_ppzksnark_verification_key<ppzksnark_ppT> vk; |
73 | r1cs_ppzksnark_processed_verification_key<ppzksnark_ppT> vk_precomp; | |
74 | std::string pkPath; | |
369df065 | 75 | |
1a9543d0 SB |
76 | JoinSplitCircuit(const std::string vkPath, const std::string pkPath) : pkPath(pkPath) { |
77 | loadFromFile(vkPath, vk); | |
78 | vk_precomp = r1cs_ppzksnark_verifier_process_vk(vk); | |
0a958ae7 | 79 | } |
1a9543d0 | 80 | ~JoinSplitCircuit() {} |
0a958ae7 | 81 | |
1a9543d0 SB |
82 | static void generate(const std::string r1csPath, |
83 | const std::string vkPath, | |
84 | const std::string pkPath) | |
85 | { | |
369df065 SB |
86 | protoboard<FieldT> pb; |
87 | ||
88 | joinsplit_gadget<FieldT, NumInputs, NumOutputs> g(pb); | |
89 | g.generate_r1cs_constraints(); | |
90 | ||
1a9543d0 | 91 | auto r1cs = pb.get_constraint_system(); |
0a958ae7 | 92 | |
1a9543d0 | 93 | saveToFile(r1csPath, r1cs); |
78df9f02 | 94 | |
1a9543d0 | 95 | r1cs_ppzksnark_keypair<ppzksnark_ppT> keypair = r1cs_ppzksnark_generator<ppzksnark_ppT>(r1cs); |
369df065 | 96 | |
1a9543d0 SB |
97 | saveToFile(vkPath, keypair.vk); |
98 | saveToFile(pkPath, keypair.pk); | |
369df065 SB |
99 | } |
100 | ||
369df065 | 101 | bool verify( |
f0dab51c | 102 | const ZCProof& proof, |
bc59f537 | 103 | ProofVerifier& verifier, |
369df065 SB |
104 | const uint256& pubKeyHash, |
105 | const uint256& randomSeed, | |
032164d5 | 106 | const boost::array<uint256, NumInputs>& macs, |
369df065 SB |
107 | const boost::array<uint256, NumInputs>& nullifiers, |
108 | const boost::array<uint256, NumOutputs>& commitments, | |
109 | uint64_t vpub_old, | |
110 | uint64_t vpub_new, | |
111 | const uint256& rt | |
112 | ) { | |
25d21970 | 113 | try { |
f0dab51c | 114 | auto r1cs_proof = proof.to_libsnark_proof<r1cs_ppzksnark_proof<ppzksnark_ppT>>(); |
d81c31f5 S |
115 | |
116 | uint256 h_sig = this->h_sig(randomSeed, nullifiers, pubKeyHash); | |
117 | ||
118 | auto witness = joinsplit_gadget<FieldT, NumInputs, NumOutputs>::witness_map( | |
119 | rt, | |
120 | h_sig, | |
121 | macs, | |
122 | nullifiers, | |
123 | commitments, | |
124 | vpub_old, | |
125 | vpub_new | |
126 | ); | |
127 | ||
bc59f537 | 128 | return verifier.check( |
1a9543d0 SB |
129 | vk, |
130 | vk_precomp, | |
bc59f537 SB |
131 | witness, |
132 | r1cs_proof | |
133 | ); | |
25d21970 SB |
134 | } catch (...) { |
135 | return false; | |
136 | } | |
369df065 SB |
137 | } |
138 | ||
f0dab51c | 139 | ZCProof prove( |
369df065 SB |
140 | const boost::array<JSInput, NumInputs>& inputs, |
141 | const boost::array<JSOutput, NumOutputs>& outputs, | |
142 | boost::array<Note, NumOutputs>& out_notes, | |
143 | boost::array<ZCNoteEncryption::Ciphertext, NumOutputs>& out_ciphertexts, | |
144 | uint256& out_ephemeralKey, | |
145 | const uint256& pubKeyHash, | |
146 | uint256& out_randomSeed, | |
147 | boost::array<uint256, NumInputs>& out_macs, | |
148 | boost::array<uint256, NumInputs>& out_nullifiers, | |
149 | boost::array<uint256, NumOutputs>& out_commitments, | |
150 | uint64_t vpub_old, | |
151 | uint64_t vpub_new, | |
5db5e42e | 152 | const uint256& rt, |
45232b19 S |
153 | bool computeProof, |
154 | uint256 *out_esk // Payment disclosure | |
369df065 | 155 | ) { |
c4643bd9 SB |
156 | if (vpub_old > MAX_MONEY) { |
157 | throw std::invalid_argument("nonsensical vpub_old value"); | |
158 | } | |
159 | ||
160 | if (vpub_new > MAX_MONEY) { | |
161 | throw std::invalid_argument("nonsensical vpub_new value"); | |
162 | } | |
163 | ||
164 | uint64_t lhs_value = vpub_old; | |
165 | uint64_t rhs_value = vpub_new; | |
166 | ||
369df065 | 167 | for (size_t i = 0; i < NumInputs; i++) { |
c4643bd9 SB |
168 | // Sanity checks of input |
169 | { | |
5f0a73ce SB |
170 | // If note has nonzero value |
171 | if (inputs[i].note.value != 0) { | |
172 | // The witness root must equal the input root. | |
173 | if (inputs[i].witness.root() != rt) { | |
174 | throw std::invalid_argument("joinsplit not anchored to the correct root"); | |
175 | } | |
176 | ||
177 | // The tree must witness the correct element | |
178 | if (inputs[i].note.cm() != inputs[i].witness.element()) { | |
179 | throw std::invalid_argument("witness of wrong element for joinsplit input"); | |
180 | } | |
c4643bd9 SB |
181 | } |
182 | ||
183 | // Ensure we have the key to this note. | |
184 | if (inputs[i].note.a_pk != inputs[i].key.address().a_pk) { | |
185 | throw std::invalid_argument("input note not authorized to spend with given key"); | |
186 | } | |
187 | ||
188 | // Balance must be sensical | |
189 | if (inputs[i].note.value > MAX_MONEY) { | |
190 | throw std::invalid_argument("nonsensical input note value"); | |
191 | } | |
192 | ||
193 | lhs_value += inputs[i].note.value; | |
194 | ||
195 | if (lhs_value > MAX_MONEY) { | |
196 | throw std::invalid_argument("nonsensical left hand size of joinsplit balance"); | |
197 | } | |
198 | } | |
199 | ||
200 | // Compute nullifier of input | |
369df065 SB |
201 | out_nullifiers[i] = inputs[i].nullifier(); |
202 | } | |
203 | ||
204 | // Sample randomSeed | |
205 | out_randomSeed = random_uint256(); | |
206 | ||
207 | // Compute h_sig | |
208 | uint256 h_sig = this->h_sig(out_randomSeed, out_nullifiers, pubKeyHash); | |
209 | ||
210 | // Sample phi | |
defe37a6 | 211 | uint252 phi = random_uint252(); |
369df065 SB |
212 | |
213 | // Compute notes for outputs | |
214 | for (size_t i = 0; i < NumOutputs; i++) { | |
c4643bd9 SB |
215 | // Sanity checks of output |
216 | { | |
217 | if (outputs[i].value > MAX_MONEY) { | |
218 | throw std::invalid_argument("nonsensical output value"); | |
219 | } | |
220 | ||
221 | rhs_value += outputs[i].value; | |
222 | ||
223 | if (rhs_value > MAX_MONEY) { | |
224 | throw std::invalid_argument("nonsensical right hand side of joinsplit balance"); | |
225 | } | |
226 | } | |
227 | ||
369df065 SB |
228 | // Sample r |
229 | uint256 r = random_uint256(); | |
230 | ||
231 | out_notes[i] = outputs[i].note(phi, r, i, h_sig); | |
232 | } | |
233 | ||
c4643bd9 SB |
234 | if (lhs_value != rhs_value) { |
235 | throw std::invalid_argument("invalid joinsplit balance"); | |
236 | } | |
237 | ||
369df065 SB |
238 | // Compute the output commitments |
239 | for (size_t i = 0; i < NumOutputs; i++) { | |
240 | out_commitments[i] = out_notes[i].cm(); | |
241 | } | |
242 | ||
243 | // Encrypt the ciphertexts containing the note | |
244 | // plaintexts to the recipients of the value. | |
245 | { | |
246 | ZCNoteEncryption encryptor(h_sig); | |
247 | ||
248 | for (size_t i = 0; i < NumOutputs; i++) { | |
4eb1a96f | 249 | NotePlaintext pt(out_notes[i], outputs[i].memo); |
369df065 SB |
250 | |
251 | out_ciphertexts[i] = pt.encrypt(encryptor, outputs[i].addr.pk_enc); | |
252 | } | |
253 | ||
254 | out_ephemeralKey = encryptor.get_epk(); | |
45232b19 S |
255 | |
256 | // !!! Payment disclosure START | |
257 | if (out_esk != nullptr) { | |
258 | *out_esk = encryptor.get_esk(); | |
259 | } | |
260 | // !!! Payment disclosure END | |
369df065 SB |
261 | } |
262 | ||
263 | // Authenticate h_sig with each of the input | |
264 | // spending keys, producing macs which protect | |
265 | // against malleability. | |
266 | for (size_t i = 0; i < NumInputs; i++) { | |
267 | out_macs[i] = PRF_pk(inputs[i].key, i, h_sig); | |
268 | } | |
269 | ||
5db5e42e JG |
270 | if (!computeProof) { |
271 | return ZCProof(); | |
272 | } | |
273 | ||
bf76024e | 274 | protoboard<FieldT> pb; |
369df065 | 275 | { |
bf76024e SB |
276 | joinsplit_gadget<FieldT, NumInputs, NumOutputs> g(pb); |
277 | g.generate_r1cs_constraints(); | |
278 | g.generate_r1cs_witness( | |
279 | phi, | |
280 | rt, | |
281 | h_sig, | |
282 | inputs, | |
283 | out_notes, | |
284 | vpub_old, | |
285 | vpub_new | |
286 | ); | |
287 | } | |
369df065 | 288 | |
c4643bd9 SB |
289 | // The constraint system must be satisfied or there is an unimplemented |
290 | // or incorrect sanity check above. Or the constraint system is broken! | |
291 | assert(pb.is_satisfied()); | |
369df065 | 292 | |
bf76024e SB |
293 | // TODO: These are copies, which is not strictly necessary. |
294 | std::vector<FieldT> primary_input = pb.primary_input(); | |
295 | std::vector<FieldT> aux_input = pb.auxiliary_input(); | |
296 | ||
297 | // Swap A and B if it's beneficial (less arithmetic in G2) | |
298 | // In our circuit, we already know that it's beneficial | |
299 | // to swap, but it takes so little time to perform this | |
300 | // estimate that it doesn't matter if we check every time. | |
301 | pb.constraint_system.swap_AB_if_beneficial(); | |
302 | ||
394f4185 | 303 | std::ifstream fh(pkPath, std::ios::binary); |
1a9543d0 | 304 | |
394f4185 | 305 | if(!fh.is_open()) { |
4305a562 | 306 | throw std::runtime_error(strprintf("could not load param file at %s", pkPath)); |
394f4185 SB |
307 | } |
308 | ||
309 | return ZCProof(r1cs_ppzksnark_prover_streaming<ppzksnark_ppT>( | |
310 | fh, | |
369df065 | 311 | primary_input, |
bf76024e SB |
312 | aux_input, |
313 | pb.constraint_system | |
f0dab51c | 314 | )); |
369df065 SB |
315 | } |
316 | }; | |
317 | ||
318 | template<size_t NumInputs, size_t NumOutputs> | |
1a9543d0 SB |
319 | void JoinSplit<NumInputs, NumOutputs>::Generate(const std::string r1csPath, |
320 | const std::string vkPath, | |
321 | const std::string pkPath) | |
369df065 | 322 | { |
bc59f537 | 323 | initialize_curve_params(); |
1a9543d0 | 324 | JoinSplitCircuit<NumInputs, NumOutputs>::generate(r1csPath, vkPath, pkPath); |
369df065 SB |
325 | } |
326 | ||
327 | template<size_t NumInputs, size_t NumOutputs> | |
1a9543d0 SB |
328 | JoinSplit<NumInputs, NumOutputs>* JoinSplit<NumInputs, NumOutputs>::Prepared(const std::string vkPath, |
329 | const std::string pkPath) | |
369df065 | 330 | { |
bc59f537 | 331 | initialize_curve_params(); |
1a9543d0 | 332 | return new JoinSplitCircuit<NumInputs, NumOutputs>(vkPath, pkPath); |
369df065 SB |
333 | } |
334 | ||
335 | template<size_t NumInputs, size_t NumOutputs> | |
336 | uint256 JoinSplit<NumInputs, NumOutputs>::h_sig( | |
337 | const uint256& randomSeed, | |
338 | const boost::array<uint256, NumInputs>& nullifiers, | |
339 | const uint256& pubKeyHash | |
340 | ) { | |
6aae9d1a | 341 | const unsigned char personalization[crypto_generichash_blake2b_PERSONALBYTES] |
369df065 SB |
342 | = {'Z','c','a','s','h','C','o','m','p','u','t','e','h','S','i','g'}; |
343 | ||
344 | std::vector<unsigned char> block(randomSeed.begin(), randomSeed.end()); | |
345 | ||
346 | for (size_t i = 0; i < NumInputs; i++) { | |
347 | block.insert(block.end(), nullifiers[i].begin(), nullifiers[i].end()); | |
348 | } | |
349 | ||
350 | block.insert(block.end(), pubKeyHash.begin(), pubKeyHash.end()); | |
351 | ||
352 | uint256 output; | |
353 | ||
354 | if (crypto_generichash_blake2b_salt_personal(output.begin(), 32, | |
355 | &block[0], block.size(), | |
356 | NULL, 0, // No key. | |
357 | NULL, // No salt. | |
358 | personalization | |
359 | ) != 0) | |
360 | { | |
361 | throw std::logic_error("hash function failure"); | |
362 | } | |
363 | ||
364 | return output; | |
365 | } | |
366 | ||
defe37a6 | 367 | Note JSOutput::note(const uint252& phi, const uint256& r, size_t i, const uint256& h_sig) const { |
369df065 SB |
368 | uint256 rho = PRF_rho(phi, i, h_sig); |
369 | ||
370 | return Note(addr.a_pk, value, rho, r); | |
371 | } | |
372 | ||
373 | JSOutput::JSOutput() : addr(uint256(), uint256()), value(0) { | |
defe37a6 | 374 | SpendingKey a_sk = SpendingKey::random(); |
369df065 SB |
375 | addr = a_sk.address(); |
376 | } | |
377 | ||
378 | JSInput::JSInput() : witness(ZCIncrementalMerkleTree().witness()), | |
defe37a6 | 379 | key(SpendingKey::random()) { |
369df065 SB |
380 | note = Note(key.address().a_pk, 0, random_uint256(), random_uint256()); |
381 | ZCIncrementalMerkleTree dummy_tree; | |
382 | dummy_tree.append(note.cm()); | |
383 | witness = dummy_tree.witness(); | |
384 | } | |
385 | ||
386 | template class JoinSplit<ZC_NUM_JS_INPUTS, | |
387 | ZC_NUM_JS_OUTPUTS>; | |
388 | ||
6aae9d1a | 389 | } |