arnaucube
arnaucube/testudoPublic Self-hosted repository
testudo/src/r1csproof.rs
src/r1csproof.rsRaw
  1#![allow(clippy::too_many_arguments)]
  2use super::commitments::{Commitments, MultiCommitGens};
  3use super::dense_mlpoly::{
  4  DensePolynomial, EqPolynomial, PolyCommitment, PolyCommitmentGens, PolyEvalProof,
  5};
  6use super::errors::ProofVerifyError;
  7use super::group::{CompressedGroup, GroupElement, VartimeMultiscalarMul};
  8use super::nizk::{EqualityProof, KnowledgeProof, ProductProof};
  9use super::r1csinstance::R1CSInstance;
 10use super::random::RandomTape;
 11use super::scalar::Scalar;
 12use super::sparse_mlpoly::{SparsePolyEntry, SparsePolynomial};
 13use super::sumcheck::ZKSumcheckInstanceProof;
 14use super::timer::Timer;
 15use super::transcript::{AppendToTranscript, ProofTranscript};
 16use core::iter;
 17use merlin::Transcript;
 18use serde::{Deserialize, Serialize};
 19
 20#[derive(Serialize, Deserialize, Debug)]
 21pub struct R1CSProof {
 22  comm_vars: PolyCommitment,
 23  sc_proof_phase1: ZKSumcheckInstanceProof,
 24  claims_phase2: (
 25    CompressedGroup,
 26    CompressedGroup,
 27    CompressedGroup,
 28    CompressedGroup,
 29  ),
 30  pok_claims_phase2: (KnowledgeProof, ProductProof),
 31  proof_eq_sc_phase1: EqualityProof,
 32  sc_proof_phase2: ZKSumcheckInstanceProof,
 33  comm_vars_at_ry: CompressedGroup,
 34  proof_eval_vars_at_ry: PolyEvalProof,
 35  proof_eq_sc_phase2: EqualityProof,
 36}
 37
 38pub struct R1CSSumcheckGens {
 39  gens_1: MultiCommitGens,
 40  gens_3: MultiCommitGens,
 41  gens_4: MultiCommitGens,
 42}
 43
 44// TODO: fix passing gens_1_ref
 45impl R1CSSumcheckGens {
 46  pub fn new(label: &'static [u8], gens_1_ref: &MultiCommitGens) -> Self {
 47    let gens_1 = gens_1_ref.clone();
 48    let gens_3 = MultiCommitGens::new(3, label);
 49    let gens_4 = MultiCommitGens::new(4, label);
 50
 51    R1CSSumcheckGens {
 52      gens_1,
 53      gens_3,
 54      gens_4,
 55    }
 56  }
 57}
 58
 59pub struct R1CSGens {
 60  gens_sc: R1CSSumcheckGens,
 61  gens_pc: PolyCommitmentGens,
 62}
 63
 64impl R1CSGens {
 65  pub fn new(label: &'static [u8], _num_cons: usize, num_vars: usize) -> Self {
 66    let num_poly_vars = num_vars.log2() as usize;
 67    let gens_pc = PolyCommitmentGens::new(num_poly_vars, label);
 68    let gens_sc = R1CSSumcheckGens::new(label, &gens_pc.gens.gens_1);
 69    R1CSGens { gens_sc, gens_pc }
 70  }
 71}
 72
 73impl R1CSProof {
 74  fn prove_phase_one(
 75    num_rounds: usize,
 76    evals_tau: &mut DensePolynomial,
 77    evals_Az: &mut DensePolynomial,
 78    evals_Bz: &mut DensePolynomial,
 79    evals_Cz: &mut DensePolynomial,
 80    gens: &R1CSSumcheckGens,
 81    transcript: &mut Transcript,
 82    random_tape: &mut RandomTape,
 83  ) -> (ZKSumcheckInstanceProof, Vec<Scalar>, Vec<Scalar>, Scalar) {
 84    let comb_func = |poly_A_comp: &Scalar,
 85                     poly_B_comp: &Scalar,
 86                     poly_C_comp: &Scalar,
 87                     poly_D_comp: &Scalar|
 88     -> Scalar { poly_A_comp * (poly_B_comp * poly_C_comp - poly_D_comp) };
 89
 90    let (sc_proof_phase_one, r, claims, blind_claim_postsc) =
 91      ZKSumcheckInstanceProof::prove_cubic_with_additive_term(
 92        &Scalar::zero(), // claim is zero
 93        &Scalar::zero(), // blind for claim is also zero
 94        num_rounds,
 95        evals_tau,
 96        evals_Az,
 97        evals_Bz,
 98        evals_Cz,
 99        comb_func,
100        &gens.gens_1,
101        &gens.gens_4,
102        transcript,
103        random_tape,
104      );
105
106    (sc_proof_phase_one, r, claims, blind_claim_postsc)
107  }
108
109  fn prove_phase_two(
110    num_rounds: usize,
111    claim: &Scalar,
112    blind_claim: &Scalar,
113    evals_z: &mut DensePolynomial,
114    evals_ABC: &mut DensePolynomial,
115    gens: &R1CSSumcheckGens,
116    transcript: &mut Transcript,
117    random_tape: &mut RandomTape,
118  ) -> (ZKSumcheckInstanceProof, Vec<Scalar>, Vec<Scalar>, Scalar) {
119    let comb_func =
120      |poly_A_comp: &Scalar, poly_B_comp: &Scalar| -> Scalar { poly_A_comp * poly_B_comp };
121    let (sc_proof_phase_two, r, claims, blind_claim_postsc) = ZKSumcheckInstanceProof::prove_quad(
122      claim,
123      blind_claim,
124      num_rounds,
125      evals_z,
126      evals_ABC,
127      comb_func,
128      &gens.gens_1,
129      &gens.gens_3,
130      transcript,
131      random_tape,
132    );
133
134    (sc_proof_phase_two, r, claims, blind_claim_postsc)
135  }
136
137  fn protocol_name() -> &'static [u8] {
138    b"R1CS proof"
139  }
140
141  pub fn prove(
142    inst: &R1CSInstance,
143    vars: Vec<Scalar>,
144    input: &[Scalar],
145    gens: &R1CSGens,
146    transcript: &mut Transcript,
147    random_tape: &mut RandomTape,
148  ) -> (R1CSProof, Vec<Scalar>, Vec<Scalar>) {
149    let timer_prove = Timer::new("R1CSProof::prove");
150    transcript.append_protocol_name(R1CSProof::protocol_name());
151
152    // we currently require the number of |inputs| + 1 to be at most number of vars
153    assert!(input.len() < vars.len());
154
155    input.append_to_transcript(b"input", transcript);
156
157    let timer_commit = Timer::new("polycommit");
158    let (poly_vars, comm_vars, blinds_vars) = {
159      // create a multilinear polynomial using the supplied assignment for variables
160      let poly_vars = DensePolynomial::new(vars.clone());
161
162      // produce a commitment to the satisfying assignment
163      let (comm_vars, blinds_vars) = poly_vars.commit(&gens.gens_pc, Some(random_tape));
164
165      // add the commitment to the prover's transcript
166      comm_vars.append_to_transcript(b"poly_commitment", transcript);
167      (poly_vars, comm_vars, blinds_vars)
168    };
169    timer_commit.stop();
170
171    let timer_sc_proof_phase1 = Timer::new("prove_sc_phase_one");
172
173    // append input to variables to create a single vector z
174    let z = {
175      let num_inputs = input.len();
176      let num_vars = vars.len();
177      let mut z = vars;
178      z.extend(&vec![Scalar::one()]); // add constant term in z
179      z.extend(input);
180      z.extend(&vec![Scalar::zero(); num_vars - num_inputs - 1]); // we will pad with zeros
181      z
182    };
183
184    // derive the verifier's challenge tau
185    let (num_rounds_x, num_rounds_y) =
186      (inst.get_num_cons().log2() as usize, z.len().log2() as usize);
187    let tau = transcript.challenge_vector(b"challenge_tau", num_rounds_x);
188    // compute the initial evaluation table for R(\tau, x)
189    let mut poly_tau = DensePolynomial::new(EqPolynomial::new(tau).evals());
190    let (mut poly_Az, mut poly_Bz, mut poly_Cz) =
191      inst.multiply_vec(inst.get_num_cons(), z.len(), &z);
192
193    let (sc_proof_phase1, rx, _claims_phase1, blind_claim_postsc1) = R1CSProof::prove_phase_one(
194      num_rounds_x,
195      &mut poly_tau,
196      &mut poly_Az,
197      &mut poly_Bz,
198      &mut poly_Cz,
199      &gens.gens_sc,
200      transcript,
201      random_tape,
202    );
203    assert_eq!(poly_tau.len(), 1);
204    assert_eq!(poly_Az.len(), 1);
205    assert_eq!(poly_Bz.len(), 1);
206    assert_eq!(poly_Cz.len(), 1);
207    timer_sc_proof_phase1.stop();
208
209    let (tau_claim, Az_claim, Bz_claim, Cz_claim) =
210      (&poly_tau[0], &poly_Az[0], &poly_Bz[0], &poly_Cz[0]);
211    let (Az_blind, Bz_blind, Cz_blind, prod_Az_Bz_blind) = (
212      random_tape.random_scalar(b"Az_blind"),
213      random_tape.random_scalar(b"Bz_blind"),
214      random_tape.random_scalar(b"Cz_blind"),
215      random_tape.random_scalar(b"prod_Az_Bz_blind"),
216    );
217
218    let (pok_Cz_claim, comm_Cz_claim) = {
219      KnowledgeProof::prove(
220        &gens.gens_sc.gens_1,
221        transcript,
222        random_tape,
223        Cz_claim,
224        &Cz_blind,
225      )
226    };
227
228    let (proof_prod, comm_Az_claim, comm_Bz_claim, comm_prod_Az_Bz_claims) = {
229      let prod = Az_claim * Bz_claim;
230      ProductProof::prove(
231        &gens.gens_sc.gens_1,
232        transcript,
233        random_tape,
234        Az_claim,
235        &Az_blind,
236        Bz_claim,
237        &Bz_blind,
238        &prod,
239        &prod_Az_Bz_blind,
240      )
241    };
242
243    comm_Az_claim.append_to_transcript(b"comm_Az_claim", transcript);
244    comm_Bz_claim.append_to_transcript(b"comm_Bz_claim", transcript);
245    comm_Cz_claim.append_to_transcript(b"comm_Cz_claim", transcript);
246    comm_prod_Az_Bz_claims.append_to_transcript(b"comm_prod_Az_Bz_claims", transcript);
247
248    // prove the final step of sum-check #1
249    let taus_bound_rx = tau_claim;
250    let blind_expected_claim_postsc1 = taus_bound_rx * (prod_Az_Bz_blind - Cz_blind);
251    let claim_post_phase1 = (Az_claim * Bz_claim - Cz_claim) * taus_bound_rx;
252    let (proof_eq_sc_phase1, _C1, _C2) = EqualityProof::prove(
253      &gens.gens_sc.gens_1,
254      transcript,
255      random_tape,
256      &claim_post_phase1,
257      &blind_expected_claim_postsc1,
258      &claim_post_phase1,
259      &blind_claim_postsc1,
260    );
261
262    let timer_sc_proof_phase2 = Timer::new("prove_sc_phase_two");
263    // combine the three claims into a single claim
264    let r_A = transcript.challenge_scalar(b"challenege_Az");
265    let r_B = transcript.challenge_scalar(b"challenege_Bz");
266    let r_C = transcript.challenge_scalar(b"challenege_Cz");
267    let claim_phase2 = r_A * Az_claim + r_B * Bz_claim + r_C * Cz_claim;
268    let blind_claim_phase2 = r_A * Az_blind + r_B * Bz_blind + r_C * Cz_blind;
269
270    let evals_ABC = {
271      // compute the initial evaluation table for R(\tau, x)
272      let evals_rx = EqPolynomial::new(rx.clone()).evals();
273      let (evals_A, evals_B, evals_C) =
274        inst.compute_eval_table_sparse(inst.get_num_cons(), z.len(), &evals_rx);
275
276      assert_eq!(evals_A.len(), evals_B.len());
277      assert_eq!(evals_A.len(), evals_C.len());
278      (0..evals_A.len())
279        .map(|i| r_A * evals_A[i] + r_B * evals_B[i] + r_C * evals_C[i])
280        .collect::<Vec<Scalar>>()
281    };
282
283    // another instance of the sum-check protocol
284    let (sc_proof_phase2, ry, claims_phase2, blind_claim_postsc2) = R1CSProof::prove_phase_two(
285      num_rounds_y,
286      &claim_phase2,
287      &blind_claim_phase2,
288      &mut DensePolynomial::new(z),
289      &mut DensePolynomial::new(evals_ABC),
290      &gens.gens_sc,
291      transcript,
292      random_tape,
293    );
294    timer_sc_proof_phase2.stop();
295
296    let timer_polyeval = Timer::new("polyeval");
297    let eval_vars_at_ry = poly_vars.evaluate(&ry[1..].to_vec());
298    let blind_eval = random_tape.random_scalar(b"blind_eval");
299    let (proof_eval_vars_at_ry, comm_vars_at_ry) = PolyEvalProof::prove(
300      &poly_vars,
301      Some(&blinds_vars),
302      &ry[1..].to_vec(),
303      &eval_vars_at_ry,
304      Some(&blind_eval),
305      &gens.gens_pc,
306      transcript,
307      random_tape,
308    );
309    timer_polyeval.stop();
310
311    // prove the final step of sum-check #2
312    let blind_eval_Z_at_ry = (Scalar::one() - ry[0]) * blind_eval;
313    let blind_expected_claim_postsc2 = claims_phase2[1] * blind_eval_Z_at_ry;
314    let claim_post_phase2 = claims_phase2[0] * claims_phase2[1];
315    let (proof_eq_sc_phase2, _C1, _C2) = EqualityProof::prove(
316      &gens.gens_pc.gens.gens_1,
317      transcript,
318      random_tape,
319      &claim_post_phase2,
320      &blind_expected_claim_postsc2,
321      &claim_post_phase2,
322      &blind_claim_postsc2,
323    );
324
325    timer_prove.stop();
326
327    (
328      R1CSProof {
329        comm_vars,
330        sc_proof_phase1,
331        claims_phase2: (
332          comm_Az_claim,
333          comm_Bz_claim,
334          comm_Cz_claim,
335          comm_prod_Az_Bz_claims,
336        ),
337        pok_claims_phase2: (pok_Cz_claim, proof_prod),
338        proof_eq_sc_phase1,
339        sc_proof_phase2,
340        comm_vars_at_ry,
341        proof_eval_vars_at_ry,
342        proof_eq_sc_phase2,
343      },
344      rx,
345      ry,
346    )
347  }
348
349  pub fn verify(
350    &self,
351    num_vars: usize,
352    num_cons: usize,
353    input: &[Scalar],
354    evals: &(Scalar, Scalar, Scalar),
355    transcript: &mut Transcript,
356    gens: &R1CSGens,
357  ) -> Result<(Vec<Scalar>, Vec<Scalar>), ProofVerifyError> {
358    transcript.append_protocol_name(R1CSProof::protocol_name());
359
360    input.append_to_transcript(b"input", transcript);
361
362    let n = num_vars;
363    // add the commitment to the verifier's transcript
364    self
365      .comm_vars
366      .append_to_transcript(b"poly_commitment", transcript);
367
368    let (num_rounds_x, num_rounds_y) = (num_cons.log2() as usize, (2 * num_vars).log2() as usize);
369
370    // derive the verifier's challenge tau
371    let tau = transcript.challenge_vector(b"challenge_tau", num_rounds_x);
372
373    // verify the first sum-check instance
374    let claim_phase1 = Scalar::zero()
375      .commit(&Scalar::zero(), &gens.gens_sc.gens_1)
376      .compress();
377    let (comm_claim_post_phase1, rx) = self.sc_proof_phase1.verify(
378      &claim_phase1,
379      num_rounds_x,
380      3,
381      &gens.gens_sc.gens_1,
382      &gens.gens_sc.gens_4,
383      transcript,
384    )?;
385    // perform the intermediate sum-check test with claimed Az, Bz, and Cz
386    let (comm_Az_claim, comm_Bz_claim, comm_Cz_claim, comm_prod_Az_Bz_claims) = &self.claims_phase2;
387    let (pok_Cz_claim, proof_prod) = &self.pok_claims_phase2;
388
389    pok_Cz_claim.verify(&gens.gens_sc.gens_1, transcript, comm_Cz_claim)?;
390    proof_prod.verify(
391      &gens.gens_sc.gens_1,
392      transcript,
393      comm_Az_claim,
394      comm_Bz_claim,
395      comm_prod_Az_Bz_claims,
396    )?;
397
398    comm_Az_claim.append_to_transcript(b"comm_Az_claim", transcript);
399    comm_Bz_claim.append_to_transcript(b"comm_Bz_claim", transcript);
400    comm_Cz_claim.append_to_transcript(b"comm_Cz_claim", transcript);
401    comm_prod_Az_Bz_claims.append_to_transcript(b"comm_prod_Az_Bz_claims", transcript);
402
403    let taus_bound_rx: Scalar = (0..rx.len())
404      .map(|i| rx[i] * tau[i] + (Scalar::one() - rx[i]) * (Scalar::one() - tau[i]))
405      .product();
406    let expected_claim_post_phase1 = (taus_bound_rx
407      * (comm_prod_Az_Bz_claims.decompress().unwrap() - comm_Cz_claim.decompress().unwrap()))
408    .compress();
409
410    // verify proof that expected_claim_post_phase1 == claim_post_phase1
411    self.proof_eq_sc_phase1.verify(
412      &gens.gens_sc.gens_1,
413      transcript,
414      &expected_claim_post_phase1,
415      &comm_claim_post_phase1,
416    )?;
417
418    // derive three public challenges and then derive a joint claim
419    let r_A = transcript.challenge_scalar(b"challenege_Az");
420    let r_B = transcript.challenge_scalar(b"challenege_Bz");
421    let r_C = transcript.challenge_scalar(b"challenege_Cz");
422
423    // r_A * comm_Az_claim + r_B * comm_Bz_claim + r_C * comm_Cz_claim;
424    let comm_claim_phase2 = GroupElement::vartime_multiscalar_mul(
425      iter::once(&r_A)
426        .chain(iter::once(&r_B))
427        .chain(iter::once(&r_C)),
428      iter::once(&comm_Az_claim)
429        .chain(iter::once(&comm_Bz_claim))
430        .chain(iter::once(&comm_Cz_claim))
431        .map(|pt| pt.decompress().unwrap())
432        .collect::<Vec<GroupElement>>(),
433    )
434    .compress();
435
436    // verify the joint claim with a sum-check protocol
437    let (comm_claim_post_phase2, ry) = self.sc_proof_phase2.verify(
438      &comm_claim_phase2,
439      num_rounds_y,
440      2,
441      &gens.gens_sc.gens_1,
442      &gens.gens_sc.gens_3,
443      transcript,
444    )?;
445
446    // verify Z(ry) proof against the initial commitment
447    self.proof_eval_vars_at_ry.verify(
448      &gens.gens_pc,
449      transcript,
450      &ry[1..].to_vec(),
451      &self.comm_vars_at_ry,
452      &self.comm_vars,
453    )?;
454
455    let poly_input_eval = {
456      // constant term
457      let mut input_as_sparse_poly_entries = vec![SparsePolyEntry::new(0, Scalar::one())];
458      //remaining inputs
459      input_as_sparse_poly_entries.extend(
460        (0..input.len())
461          .map(|i| SparsePolyEntry::new(i + 1, input[i]))
462          .collect::<Vec<SparsePolyEntry>>(),
463      );
464      SparsePolynomial::new(n.log2() as usize, input_as_sparse_poly_entries)
465        .evaluate(&ry[1..].to_vec())
466    };
467
468    // compute commitment to eval_Z_at_ry = (Scalar::one() - ry[0]) * self.eval_vars_at_ry + ry[0] * poly_input_eval
469    let comm_eval_Z_at_ry = GroupElement::vartime_multiscalar_mul(
470      iter::once(Scalar::one() - ry[0]).chain(iter::once(ry[0])),
471      iter::once(&self.comm_vars_at_ry.decompress().unwrap()).chain(iter::once(
472        &poly_input_eval.commit(&Scalar::zero(), &gens.gens_pc.gens.gens_1),
473      )),
474    );
475
476    // perform the final check in the second sum-check protocol
477    let (eval_A_r, eval_B_r, eval_C_r) = evals;
478    let expected_claim_post_phase2 =
479      ((r_A * eval_A_r + r_B * eval_B_r + r_C * eval_C_r) * comm_eval_Z_at_ry).compress();
480    // verify proof that expected_claim_post_phase1 == claim_post_phase1
481    self.proof_eq_sc_phase2.verify(
482      &gens.gens_sc.gens_1,
483      transcript,
484      &expected_claim_post_phase2,
485      &comm_claim_post_phase2,
486    )?;
487
488    Ok((rx, ry))
489  }
490}
491
492#[cfg(test)]
493mod tests {
494  use super::*;
495  use rand::rngs::OsRng;
496
497  fn produce_tiny_r1cs() -> (R1CSInstance, Vec<Scalar>, Vec<Scalar>) {
498    // three constraints over five variables Z1, Z2, Z3, Z4, and Z5
499    // rounded to the nearest power of two
500    let num_cons = 128;
501    let num_vars = 256;
502    let num_inputs = 2;
503
504    // encode the above constraints into three matrices
505    let mut A: Vec<(usize, usize, Scalar)> = Vec::new();
506    let mut B: Vec<(usize, usize, Scalar)> = Vec::new();
507    let mut C: Vec<(usize, usize, Scalar)> = Vec::new();
508
509    let one = Scalar::one();
510    // constraint 0 entries
511    // (Z1 + Z2) * I0 - Z3 = 0;
512    A.push((0, 0, one));
513    A.push((0, 1, one));
514    B.push((0, num_vars + 1, one));
515    C.push((0, 2, one));
516
517    // constraint 1 entries
518    // (Z1 + I1) * (Z3) - Z4 = 0
519    A.push((1, 0, one));
520    A.push((1, num_vars + 2, one));
521    B.push((1, 2, one));
522    C.push((1, 3, one));
523    // constraint 3 entries
524    // Z5 * 1 - 0 = 0
525    A.push((2, 4, one));
526    B.push((2, num_vars, one));
527
528    let inst = R1CSInstance::new(num_cons, num_vars, num_inputs, &A, &B, &C);
529
530    // compute a satisfying assignment
531    let mut csprng: OsRng = OsRng;
532    let i0 = Scalar::random(&mut csprng);
533    let i1 = Scalar::random(&mut csprng);
534    let z1 = Scalar::random(&mut csprng);
535    let z2 = Scalar::random(&mut csprng);
536    let z3 = (z1 + z2) * i0; // constraint 1: (Z1 + Z2) * I0 - Z3 = 0;
537    let z4 = (z1 + i1) * z3; // constraint 2: (Z1 + I1) * (Z3) - Z4 = 0
538    let z5 = Scalar::zero(); //constraint 3
539
540    let mut vars = vec![Scalar::zero(); num_vars];
541    vars[0] = z1;
542    vars[1] = z2;
543    vars[2] = z3;
544    vars[3] = z4;
545    vars[4] = z5;
546
547    let mut input = vec![Scalar::zero(); num_inputs];
548    input[0] = i0;
549    input[1] = i1;
550
551    (inst, vars, input)
552  }
553
554  #[test]
555  fn test_tiny_r1cs() {
556    let (inst, vars, input) = tests::produce_tiny_r1cs();
557    let is_sat = inst.is_sat(&vars, &input);
558    assert!(is_sat);
559  }
560
561  #[test]
562  fn test_synthetic_r1cs() {
563    let (inst, vars, input) = R1CSInstance::produce_synthetic_r1cs(1024, 1024, 10);
564    let is_sat = inst.is_sat(&vars, &input);
565    assert!(is_sat);
566  }
567
568  #[test]
569  pub fn check_r1cs_proof() {
570    let num_vars = 1024;
571    let num_cons = num_vars;
572    let num_inputs = 10;
573    let (inst, vars, input) = R1CSInstance::produce_synthetic_r1cs(num_cons, num_vars, num_inputs);
574
575    let gens = R1CSGens::new(b"test-m", num_cons, num_vars);
576
577    let mut random_tape = RandomTape::new(b"proof");
578    let mut prover_transcript = Transcript::new(b"example");
579    let (proof, rx, ry) = R1CSProof::prove(
580      &inst,
581      vars,
582      &input,
583      &gens,
584      &mut prover_transcript,
585      &mut random_tape,
586    );
587
588    let inst_evals = inst.evaluate(&rx, &ry);
589
590    let mut verifier_transcript = Transcript::new(b"example");
591    assert!(proof
592      .verify(
593        inst.get_num_vars(),
594        inst.get_num_cons(),
595        &input,
596        &inst_evals,
597        &mut verifier_transcript,
598        &gens,
599      )
600      .is_ok());
601  }
602}