diff --git a/halo2-ecc/configs/secp256k1/bench_schnorr.config b/halo2-ecc/configs/secp256k1/bench_schnorr.config new file mode 100644 index 00000000..07591eae --- /dev/null +++ b/halo2-ecc/configs/secp256k1/bench_schnorr.config @@ -0,0 +1,9 @@ +{"strategy":"Simple","degree":19,"num_advice":1,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":18,"limb_bits":88,"num_limbs":3} +{"strategy":"Simple","degree":18,"num_advice":2,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":17,"limb_bits":88,"num_limbs":3} +{"strategy":"Simple","degree":17,"num_advice":4,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":16,"limb_bits":88,"num_limbs":3} +{"strategy":"Simple","degree":16,"num_advice":8,"num_lookup_advice":2,"num_fixed":1,"lookup_bits":15,"limb_bits":90,"num_limbs":3} +{"strategy":"Simple","degree":15,"num_advice":17,"num_lookup_advice":3,"num_fixed":1,"lookup_bits":14,"limb_bits":90,"num_limbs":3} +{"strategy":"Simple","degree":14,"num_advice":34,"num_lookup_advice":6,"num_fixed":1,"lookup_bits":13,"limb_bits":91,"num_limbs":3} +{"strategy":"Simple","degree":13,"num_advice":68,"num_lookup_advice":12,"num_fixed":1,"lookup_bits":12,"limb_bits":88,"num_limbs":3} +{"strategy":"Simple","degree":12,"num_advice":139,"num_lookup_advice":24,"num_fixed":2,"lookup_bits":11,"limb_bits":88,"num_limbs":3} +{"strategy":"Simple","degree":11,"num_advice":291,"num_lookup_advice":53,"num_fixed":4,"lookup_bits":10,"limb_bits":88,"num_limbs":3} \ No newline at end of file diff --git a/halo2-ecc/configs/secp256k1/schnorr_circuit.config b/halo2-ecc/configs/secp256k1/schnorr_circuit.config new file mode 100644 index 00000000..a13e7075 --- /dev/null +++ b/halo2-ecc/configs/secp256k1/schnorr_circuit.config @@ -0,0 +1 @@ +{"strategy":"Simple","degree":18,"num_advice":2,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":17,"limb_bits":88,"num_limbs":3} \ No newline at end of file diff --git a/halo2-ecc/src/bigint/big_is_even.rs b/halo2-ecc/src/bigint/big_is_even.rs new file mode 100644 index 00000000..13f9e873 --- /dev/null +++ b/halo2-ecc/src/bigint/big_is_even.rs @@ -0,0 +1,20 @@ +use super::OverflowInteger; +use halo2_base::gates::GateInstructions; +use halo2_base::gates::RangeChip; +use halo2_base::{safe_types::RangeInstructions, utils::ScalarField, AssignedValue, Context}; + +/// # Assumptions +/// * `a` has nonzero number of limbs +pub fn positive( + range: &RangeChip, + ctx: &mut Context, + a: OverflowInteger, + limb_bits: usize, +) -> AssignedValue { + let k = a.limbs.len(); + assert_ne!(k, 0); + let first_cell: AssignedValue = a.limbs[0]; + + let last_bit = range.get_last_bit(ctx, first_cell, limb_bits); + range.gate.not(ctx, last_bit) +} diff --git a/halo2-ecc/src/bigint/mod.rs b/halo2-ecc/src/bigint/mod.rs index ea14b127..37c32ecf 100644 --- a/halo2-ecc/src/bigint/mod.rs +++ b/halo2-ecc/src/bigint/mod.rs @@ -9,6 +9,7 @@ use num_traits::Zero; pub mod add_no_carry; pub mod big_is_equal; +pub mod big_is_even; pub mod big_is_zero; pub mod big_less_than; pub mod carry_mod; diff --git a/halo2-ecc/src/ecc/mod.rs b/halo2-ecc/src/ecc/mod.rs index a196e039..0b3103d2 100644 --- a/halo2-ecc/src/ecc/mod.rs +++ b/halo2-ecc/src/ecc/mod.rs @@ -16,6 +16,7 @@ use std::marker::PhantomData; pub mod ecdsa; pub mod fixed_base; +pub mod schnorr_signature; // pub mod fixed_base_pippenger; pub mod pippenger; diff --git a/halo2-ecc/src/ecc/schnorr_signature.rs b/halo2-ecc/src/ecc/schnorr_signature.rs new file mode 100644 index 00000000..aebdfeca --- /dev/null +++ b/halo2-ecc/src/ecc/schnorr_signature.rs @@ -0,0 +1,80 @@ +use crate::bigint::{big_is_equal, ProperCrtUint}; +use crate::fields::{fp::FpChip, FieldChip, PrimeField}; +use halo2_base::{gates::GateInstructions, utils::CurveAffineExt, AssignedValue, Context}; + +use super::{fixed_base, scalar_multiply, EcPoint, EccChip}; + +// CF is the coordinate field of GA +// SF is the scalar field of GA +// p = base field modulus +// n = scalar field modulus +// checks r < p, 0 < s < n, 0 < msgHash < n +// this circuit applies over constraints that s > 0, msgHash > 0 cause scalar_multiply can't handle zero scalar +/// `pubkey` should not be the identity point +/// follow spec in https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki +pub fn schnorr_verify_no_pubkey_check( + chip: &EccChip>, + ctx: &mut Context, + pubkey: EcPoint as FieldChip>::FieldPoint>, + r: ProperCrtUint, // int(sig[0:32]); fail if r ≥ p. + s: ProperCrtUint, // int(sig[32:64]); fail if s ≥ n + msgHash: ProperCrtUint, // int(hashBIP0340/challenge(bytes(r) || bytes(P) || m)) mod n + var_window_bits: usize, + fixed_window_bits: usize, +) -> AssignedValue +where + GA: CurveAffineExt, +{ + let base_chip = chip.field_chip; + let scalar_chip = + FpChip::::new(base_chip.range, base_chip.limb_bits, base_chip.num_limbs); + + // check r < p + let r_valid = base_chip.is_less_than_p(ctx, &r); + // check 0 < s < n + let s_valid = scalar_chip.is_soft_nonzero(ctx, &s); + // check 0 < e < n + let e_valid = scalar_chip.is_soft_nonzero(ctx, &msgHash); + + // compute s * G and msgHash * pubkey + let s_G = fixed_base::scalar_multiply( + base_chip, + ctx, + &GA::generator(), + s.limbs().to_vec(), + base_chip.limb_bits, + fixed_window_bits, + ); + let e_P = scalar_multiply::<_, _, GA>( + base_chip, + ctx, + pubkey, + msgHash.limbs().to_vec(), + base_chip.limb_bits, + var_window_bits, + ); + + // check s_G.x != e_P.x, which is a requirement for sub_unequal + let x_eq = base_chip.is_equal(ctx, &s_G.x, &e_P.x); + let x_neq = base_chip.gate().not(ctx, x_eq); + + // R = s⋅G - e⋅P + // R is not infinity point implicitly constrainted by is_strict = true + let R = chip.sub_unequal(ctx, s_G, e_P, true); + + // check R.y is even + let R_y = R.y; + let R_y_is_even: AssignedValue = base_chip.is_even(ctx, &R_y); + + // check R.x == r + let R_x = scalar_chip.enforce_less_than(ctx, R.x); + let equal_check = big_is_equal::assign(base_chip.gate(), ctx, R_x.0, r); + + let res1 = base_chip.gate().and(ctx, r_valid, s_valid); + let res2: AssignedValue = base_chip.gate().and(ctx, res1, e_valid); + let res3 = base_chip.gate().and(ctx, res2, x_neq); + let res4: AssignedValue = base_chip.gate().and(ctx, res3, R_y_is_even); + let res5 = base_chip.gate().and(ctx, res4, equal_check); + + res5 +} diff --git a/halo2-ecc/src/fields/fp.rs b/halo2-ecc/src/fields/fp.rs index 54cffa1d..6ec428ef 100644 --- a/halo2-ecc/src/fields/fp.rs +++ b/halo2-ecc/src/fields/fp.rs @@ -1,8 +1,8 @@ use super::{FieldChip, PrimeField, PrimeFieldChip, Selectable}; use crate::bigint::{ - add_no_carry, big_is_equal, big_is_zero, carry_mod, check_carry_mod_to_zero, mul_no_carry, - scalar_mul_and_add_no_carry, scalar_mul_no_carry, select, select_by_indicator, sub, - sub_no_carry, CRTInteger, FixedCRTInteger, OverflowInteger, ProperCrtUint, ProperUint, + add_no_carry, big_is_equal, big_is_even, big_is_zero, carry_mod, check_carry_mod_to_zero, + mul_no_carry, scalar_mul_and_add_no_carry, scalar_mul_no_carry, select, select_by_indicator, + sub, sub_no_carry, CRTInteger, FixedCRTInteger, OverflowInteger, ProperCrtUint, ProperUint, }; use crate::halo2_proofs::halo2curves::CurveAffine; use halo2_base::gates::RangeChip; @@ -124,6 +124,26 @@ impl<'range, F: PrimeField, Fp: PrimeField> FpChip<'range, F, Fp> { self.gate().assert_is_const(ctx, &borrow.unwrap(), &F::one()); } + /// Given proper CRT integer `a`, returns 1 iff `a < modulus::()` + /// # Assumptions + /// * `a` is proper representation of BigUint + pub fn is_less_than_p( + &self, + ctx: &mut Context, + a: impl Into>, + ) -> AssignedValue { + let a = a.into(); + + // underflow != 0 iff carry < p + let p = self.load_constant_uint(ctx, self.p.to_biguint().unwrap()); + let (_, underflow) = + sub::crt::(self.range(), ctx, a, p, self.limb_bits, self.limb_bases[1]); + let is_underflow_zero = self.gate().is_zero(ctx, underflow); + let no_underflow = self.gate().not(ctx, is_underflow_zero); + + no_underflow + } + pub fn load_constant_uint(&self, ctx: &mut Context, a: BigUint) -> ProperCrtUint { FixedCRTInteger::from_native(a, self.num_limbs, self.limb_bits).assign( ctx, @@ -131,6 +151,19 @@ impl<'range, F: PrimeField, Fp: PrimeField> FpChip<'range, F, Fp> { self.native_modulus(), ) } + + // assuming `a` has been range checked to be a proper BigInt + // constrain the witness `a` to be `< p` + // then check if `a[0]` is even + pub fn is_even( + &self, + ctx: &mut Context, + a: impl Into>, + ) -> AssignedValue { + let a = a.into(); + self.enforce_less_than_p(ctx, a.clone()); + big_is_even::positive(self.range(), ctx, a.0.truncation, self.limb_bits) + } } impl<'range, F: PrimeField, Fp: PrimeField> PrimeFieldChip for FpChip<'range, F, Fp> { diff --git a/halo2-ecc/src/secp256k1/tests/mod.rs b/halo2-ecc/src/secp256k1/tests/mod.rs index 87ba4ecb..5938fe3b 100644 --- a/halo2-ecc/src/secp256k1/tests/mod.rs +++ b/halo2-ecc/src/secp256k1/tests/mod.rs @@ -34,6 +34,8 @@ use crate::{ pub mod ecdsa; pub mod ecdsa_tests; +pub mod schnorr_signature_tests; + #[derive(Clone, Copy, Debug, Serialize, Deserialize)] struct CircuitParams { strategy: FpStrategy, diff --git a/halo2-ecc/src/secp256k1/tests/schnorr_signature_tests.rs b/halo2-ecc/src/secp256k1/tests/schnorr_signature_tests.rs new file mode 100644 index 00000000..efcbeeea --- /dev/null +++ b/halo2-ecc/src/secp256k1/tests/schnorr_signature_tests.rs @@ -0,0 +1,351 @@ +#![allow(non_snake_case)] +use crate::halo2_proofs::{ + arithmetic::CurveAffine, + dev::MockProver, + halo2curves::bn256::{Bn256, Fr, G1Affine}, + halo2curves::secp256k1::{Fp, Fq, Secp256k1Affine}, + plonk::*, + poly::commitment::ParamsProver, + transcript::{Blake2bRead, Blake2bWrite, Challenge255}, +}; +use crate::halo2_proofs::{ + poly::kzg::{ + commitment::KZGCommitmentScheme, + multiopen::{ProverSHPLONK, VerifierSHPLONK}, + strategy::SingleStrategy, + }, + transcript::{TranscriptReadBuffer, TranscriptWriterBuffer}, +}; +use crate::secp256k1::{FpChip, FqChip}; +use crate::{ + ecc::{schnorr_signature::schnorr_verify_no_pubkey_check, EccChip}, + fields::{FieldChip, PrimeField}, +}; +use ark_std::{end_timer, start_timer}; +use halo2_base::utils::fs::gen_srs; +use halo2_base::{ + gates::builder::{ + CircuitBuilderStage, GateThreadBuilder, MultiPhaseThreadBreakPoints, RangeCircuitBuilder, + }, + utils::fe_to_biguint, +}; +use num_bigint::BigUint; + +use halo2_base::gates::RangeChip; +use halo2_base::Context; +use num_integer::Integer; +use rand::random; +use rand::rngs::StdRng; +use rand_core::SeedableRng; +use std::fs::File; +use std::io::BufReader; +use std::io::Write; +use std::{fs, io::BufRead}; +use test_case::test_case; + +use super::CircuitParams; + +fn schnorr_signature_test( + ctx: &mut Context, + params: CircuitParams, + r: Fp, + s: Fq, + msghash: Fq, + pk: Secp256k1Affine, +) { + std::env::set_var("LOOKUP_BITS", params.lookup_bits.to_string()); + let range = RangeChip::::default(params.lookup_bits); + let fp_chip = FpChip::::new(&range, params.limb_bits, params.num_limbs); + let fq_chip = FqChip::::new(&range, params.limb_bits, params.num_limbs); + + let [m, s] = [msghash, s].map(|x| fq_chip.load_private(ctx, x)); + let r = fp_chip.load_private(ctx, r); + + let ecc_chip = EccChip::>::new(&fp_chip); + let pk = ecc_chip.assign_point(ctx, pk); + // test schnorr signature + let res = schnorr_verify_no_pubkey_check::( + &ecc_chip, ctx, pk, r, s, m, 4, 4, + ); + assert_eq!(res.value(), &F::one()); +} + +fn random_parameters_schnorr_signature() -> (Fp, Fq, Fq, Secp256k1Affine) { + let sk = ::ScalarExt::random(StdRng::from_seed([0u8; 32])); + let pubkey = Secp256k1Affine::from(Secp256k1Affine::generator() * sk); + let msg_hash = + ::ScalarExt::random(StdRng::from_seed([0u8; 32])); + + let mut k = ::ScalarExt::random(StdRng::from_seed([0u8; 32])); + + let mut r_point = + Secp256k1Affine::from(Secp256k1Affine::generator() * k).coordinates().unwrap(); + let mut x: &Fp = r_point.x(); + let mut y: &Fp = r_point.y(); + // make sure R.y is even + while fe_to_biguint(y).mod_floor(&BigUint::from(2u64)) != BigUint::from(0u64) { + k = ::ScalarExt::random(StdRng::from_seed([0u8; 32])); + r_point = Secp256k1Affine::from(Secp256k1Affine::generator() * k).coordinates().unwrap(); + x = r_point.x(); + y = r_point.y(); + } + + let r = *x; + let s = k + sk * msg_hash; + + (r, s, msg_hash, pubkey) +} + +fn custom_parameters_schnorr_signature( + sk: u64, + msg_hash: u64, + k: u64, +) -> (Fp, Fq, Fq, Secp256k1Affine) { + let sk = ::ScalarExt::from(sk); + let pubkey = Secp256k1Affine::from(Secp256k1Affine::generator() * sk); + let msg_hash = ::ScalarExt::from(msg_hash); + + let k = ::ScalarExt::from(k); + + let r_point = Secp256k1Affine::from(Secp256k1Affine::generator() * k).coordinates().unwrap(); + let x = r_point.x(); + + let r = *x; + let s = k + sk * msg_hash; + + (r, s, msg_hash, pubkey) +} + +fn schnorr_signature_circuit( + r: Fp, + s: Fq, + msg_hash: Fq, + pubkey: Secp256k1Affine, + params: CircuitParams, + stage: CircuitBuilderStage, + break_points: Option, +) -> RangeCircuitBuilder { + let mut builder = match stage { + CircuitBuilderStage::Mock => GateThreadBuilder::mock(), + CircuitBuilderStage::Prover => GateThreadBuilder::prover(), + CircuitBuilderStage::Keygen => GateThreadBuilder::keygen(), + }; + let start0 = start_timer!(|| format!("Witness generation for circuit in {stage:?} stage")); + schnorr_signature_test(builder.main(0), params, r, s, msg_hash, pubkey); + + let circuit = match stage { + CircuitBuilderStage::Mock => { + builder.config(params.degree as usize, Some(20)); + RangeCircuitBuilder::mock(builder) + } + CircuitBuilderStage::Keygen => { + builder.config(params.degree as usize, Some(20)); + RangeCircuitBuilder::keygen(builder) + } + CircuitBuilderStage::Prover => RangeCircuitBuilder::prover(builder, break_points.unwrap()), + }; + end_timer!(start0); + circuit +} + +#[test] +#[should_panic(expected = "assertion failed: `(left == right)`")] +fn test_schnorr_signature_msg_hash_zero() { + let path = "configs/secp256k1/schnorr_circuit.config"; + let params: CircuitParams = serde_json::from_reader( + File::open(path).unwrap_or_else(|e| panic!("{path} does not exist: {e:?}")), + ) + .unwrap(); + + let (r, s, msg_hash, pubkey) = + custom_parameters_schnorr_signature(random::(), 0, random::()); + + let circuit = + schnorr_signature_circuit(r, s, msg_hash, pubkey, params, CircuitBuilderStage::Mock, None); + MockProver::run(params.degree, &circuit, vec![]).unwrap().assert_satisfied(); +} + +#[test] +#[should_panic(expected = "assertion failed: `(left == right)`")] +fn test_schnorr_signature_private_key_zero() { + let path = "configs/secp256k1/schnorr_circuit.config"; + let params: CircuitParams = serde_json::from_reader( + File::open(path).unwrap_or_else(|e| panic!("{path} does not exist: {e:?}")), + ) + .unwrap(); + + let (r, s, msg_hash, pubkey) = + custom_parameters_schnorr_signature(0, random::(), random::()); + + let circuit = + schnorr_signature_circuit(r, s, msg_hash, pubkey, params, CircuitBuilderStage::Mock, None); + MockProver::run(params.degree, &circuit, vec![]).unwrap().assert_satisfied(); +} + +#[test_case(1, 1, 0; "")] +#[should_panic(expected = "assertion failed: `(left == right)`")] +fn test_schnorr_signature_k_zero(sk: u64, msg_hash: u64, k: u64) { + let path = "configs/secp256k1/schnorr_circuit.config"; + let params: CircuitParams = serde_json::from_reader( + File::open(path).unwrap_or_else(|e| panic!("{path} does not exist: {e:?}")), + ) + .unwrap(); + + let (r, s, msg_hash, pubkey) = custom_parameters_schnorr_signature(sk, msg_hash, k); + + let circuit = + schnorr_signature_circuit(r, s, msg_hash, pubkey, params, CircuitBuilderStage::Mock, None); + MockProver::run(params.degree, &circuit, vec![]).unwrap().assert_satisfied(); +} + +#[test] +fn test_schnorr_signature_random_valid_inputs() { + let path = "configs/secp256k1/schnorr_circuit.config"; + let params: CircuitParams = serde_json::from_reader( + File::open(path).unwrap_or_else(|e| panic!("{path} does not exist: {e:?}")), + ) + .unwrap(); + for _ in 0..10 { + let (r, s, msg_hash, pubkey) = random_parameters_schnorr_signature(); + + let circuit = schnorr_signature_circuit( + r, + s, + msg_hash, + pubkey, + params, + CircuitBuilderStage::Mock, + None, + ); + MockProver::run(params.degree, &circuit, vec![]).unwrap().assert_satisfied(); + } +} + +#[test_case(1, 1, 1; "")] +fn test_schnorr_signature_custom_valid_inputs(sk: u64, msg_hash: u64, k: u64) { + let path = "configs/secp256k1/schnorr_circuit.config"; + let params: CircuitParams = serde_json::from_reader( + File::open(path).unwrap_or_else(|e| panic!("{path} does not exist: {e:?}")), + ) + .unwrap(); + + let (r, s, msg_hash, pubkey) = custom_parameters_schnorr_signature(sk, msg_hash, k); + + let circuit = + schnorr_signature_circuit(r, s, msg_hash, pubkey, params, CircuitBuilderStage::Mock, None); + MockProver::run(params.degree, &circuit, vec![]).unwrap().assert_satisfied(); +} + +#[test] +fn bench_secp256k1_schnorr() -> Result<(), Box> { + let mut rng = StdRng::from_seed([0u8; 32]); + let config_path = "configs/secp256k1/bench_schnorr.config"; + let bench_params_file = + File::open(config_path).unwrap_or_else(|e| panic!("{config_path} does not exist: {e:?}")); + fs::create_dir_all("results/secp256k1").unwrap(); + fs::create_dir_all("data").unwrap(); + let results_path = "results/secp256k1/schnorr_bench.csv"; + let mut fs_results = File::create(results_path).unwrap(); + writeln!(fs_results, "degree,num_advice,num_lookup,num_fixed,lookup_bits,limb_bits,num_limbs,proof_time,proof_size,verify_time")?; + + let bench_params_reader = BufReader::new(bench_params_file); + for line in bench_params_reader.lines() { + let bench_params: CircuitParams = serde_json::from_str(line.unwrap().as_str()).unwrap(); + let k = bench_params.degree; + let (r, s, msg_hash, pubkey) = random_parameters_schnorr_signature(); + println!("---------------------- degree = {k} ------------------------------",); + + let params = gen_srs(k); + println!("{bench_params:?}"); + let circuit = schnorr_signature_circuit( + r, + s, + msg_hash, + pubkey, + bench_params, + CircuitBuilderStage::Keygen, + None, + ); + let vk_time = start_timer!(|| "Generating vkey"); + let vk = keygen_vk(¶ms, &circuit)?; + end_timer!(vk_time); + + let pk_time = start_timer!(|| "Generating pkey"); + let pk = keygen_pk(¶ms, vk, &circuit)?; + end_timer!(pk_time); + + let break_points = circuit.0.break_points.take(); + drop(circuit); + // create a proof + let proof_time = start_timer!(|| "Proving time"); + let circuit = schnorr_signature_circuit( + r, + s, + msg_hash, + pubkey, + bench_params, + CircuitBuilderStage::Prover, + Some(break_points), + ); + + let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); + create_proof::< + KZGCommitmentScheme, + ProverSHPLONK<'_, Bn256>, + Challenge255, + _, + Blake2bWrite, G1Affine, Challenge255>, + _, + >(¶ms, &pk, &[circuit], &[&[]], &mut rng, &mut transcript)?; + let proof = transcript.finalize(); + end_timer!(proof_time); + + let proof_size = { + let path = format!( + "data/schnorr_circuit_proof_{}_{}_{}_{}_{}_{}_{}.data", + bench_params.degree, + bench_params.num_advice, + bench_params.num_lookup_advice, + bench_params.num_fixed, + bench_params.lookup_bits, + bench_params.limb_bits, + bench_params.num_limbs + ); + let mut fd = File::create(&path)?; + fd.write_all(&proof)?; + let size = fd.metadata().unwrap().len(); + fs::remove_file(path)?; + size + }; + + let verify_time = start_timer!(|| "Verify time"); + let verifier_params = params.verifier_params(); + let strategy = SingleStrategy::new(¶ms); + let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); + verify_proof::< + KZGCommitmentScheme, + VerifierSHPLONK<'_, Bn256>, + Challenge255, + Blake2bRead<&[u8], G1Affine, Challenge255>, + SingleStrategy<'_, Bn256>, + >(verifier_params, pk.get_vk(), strategy, &[&[]], &mut transcript) + .unwrap(); + end_timer!(verify_time); + + writeln!( + fs_results, + "{},{},{},{},{},{},{},{:?},{},{:?}", + bench_params.degree, + bench_params.num_advice, + bench_params.num_lookup_advice, + bench_params.num_fixed, + bench_params.lookup_bits, + bench_params.limb_bits, + bench_params.num_limbs, + proof_time.time.elapsed(), + proof_size, + verify_time.time.elapsed() + )?; + } + Ok(()) +}