mirror of
https://github.com/arnaucube/sonobe.git
synced 2026-01-09 07:21:28 +01:00
Add Decider impl for Nova onchain (#66)
* Add Decider impl for Nova onchain Add Decider impl for Nova onchain. Update also the Decider trait. Nova onchain decider: (compressed SNARK / final proof), in order to later verify the Nova+CycleFold proofs onchain (in Ethereum’s EVM). * PR review updates and few other changes
This commit is contained in:
@@ -12,6 +12,7 @@ ark-crypto-primitives = { version = "^0.4.0", default-features = false, features
|
||||
ark-poly-commit = "^0.4.0"
|
||||
ark-relations = { version = "^0.4.0", default-features = false }
|
||||
ark-r1cs-std = { default-features = false } # use latest version from the patch
|
||||
ark-snark = { version = "^0.4.0"}
|
||||
ark-serialize = "^0.4.0"
|
||||
ark-circom = { git = "https://github.com/gakonst/ark-circom.git" }
|
||||
thiserror = "1.0"
|
||||
@@ -26,6 +27,10 @@ espresso_subroutines = {git="https://github.com/EspressoSystems/hyperplonk", pac
|
||||
ark-pallas = {version="0.4.0", features=["r1cs"]}
|
||||
ark-vesta = {version="0.4.0", features=["r1cs"]}
|
||||
ark-bn254 = "0.4.0"
|
||||
ark-mnt4-298 = {version="0.4.0", features=["r1cs"]}
|
||||
ark-mnt6-298 = {version="0.4.0", features=["r1cs"]}
|
||||
ark-groth16 = { version = "^0.4.0" }
|
||||
rand = "0.8.5"
|
||||
tracing = { version = "0.1", default-features = false, features = [ "attributes" ] }
|
||||
tracing-subscriber = { version = "0.2" }
|
||||
|
||||
@@ -35,7 +40,10 @@ default = ["parallel"]
|
||||
parallel = [
|
||||
"ark-std/parallel",
|
||||
"ark-ff/parallel",
|
||||
"ark-ec/parallel",
|
||||
"ark-poly/parallel",
|
||||
"ark-crypto-primitives/parallel",
|
||||
"ark-r1cs-std/parallel",
|
||||
]
|
||||
|
||||
# The following patch is to use a version of ark-r1cs-std compatible with
|
||||
|
||||
@@ -54,12 +54,26 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// point_to_nonnative_limbs is used to compute (outside the circuit) the limbs representation of a
|
||||
/// point that matches the one used in-circuit.
|
||||
/// Wrapper on top of [`point_to_nonnative_limbs_custom_opt`] which always uses
|
||||
/// [`OptimizationType::Weight`].
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn point_to_nonnative_limbs<C: CurveGroup>(
|
||||
p: C,
|
||||
) -> Result<(Vec<C::ScalarField>, Vec<C::ScalarField>), SynthesisError>
|
||||
where
|
||||
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
|
||||
{
|
||||
point_to_nonnative_limbs_custom_opt(p, OptimizationType::Weight)
|
||||
}
|
||||
|
||||
/// Used to compute (outside the circuit) the limbs representation of a point that matches the one
|
||||
/// used in-circuit, and in particular this method allows to specify which [`OptimizationType`] to
|
||||
/// use.
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn point_to_nonnative_limbs_custom_opt<C: CurveGroup>(
|
||||
p: C,
|
||||
optimization_type: OptimizationType,
|
||||
) -> Result<(Vec<C::ScalarField>, Vec<C::ScalarField>), SynthesisError>
|
||||
where
|
||||
<C as ark_ec::CurveGroup>::BaseField: ark_ff::PrimeField,
|
||||
{
|
||||
@@ -68,12 +82,12 @@ where
|
||||
let x =
|
||||
AllocatedNonNativeFieldVar::<C::BaseField, C::ScalarField>::get_limbs_representations(
|
||||
&C::BaseField::zero(),
|
||||
OptimizationType::Weight,
|
||||
optimization_type,
|
||||
)?;
|
||||
let y =
|
||||
AllocatedNonNativeFieldVar::<C::BaseField, C::ScalarField>::get_limbs_representations(
|
||||
&C::BaseField::one(),
|
||||
OptimizationType::Weight,
|
||||
optimization_type,
|
||||
)?;
|
||||
return Ok((x, y));
|
||||
}
|
||||
@@ -81,11 +95,11 @@ where
|
||||
let (x, y) = affine.xy().unwrap();
|
||||
let x = AllocatedNonNativeFieldVar::<C::BaseField, C::ScalarField>::get_limbs_representations(
|
||||
x,
|
||||
OptimizationType::Weight,
|
||||
optimization_type,
|
||||
)?;
|
||||
let y = AllocatedNonNativeFieldVar::<C::BaseField, C::ScalarField>::get_limbs_representations(
|
||||
y,
|
||||
OptimizationType::Weight,
|
||||
optimization_type,
|
||||
)?;
|
||||
Ok((x, y))
|
||||
}
|
||||
@@ -94,16 +108,24 @@ where
|
||||
mod tests {
|
||||
use super::*;
|
||||
use ark_pallas::{Fr, Projective};
|
||||
use ark_r1cs_std::alloc::AllocVar;
|
||||
use ark_r1cs_std::{alloc::AllocVar, R1CSVar};
|
||||
use ark_relations::r1cs::ConstraintSystem;
|
||||
use ark_std::Zero;
|
||||
use ark_std::{UniformRand, Zero};
|
||||
|
||||
#[test]
|
||||
fn test_alloc_nonnativeaffinevar_zero() {
|
||||
fn test_alloc_nonnativeaffinevar() {
|
||||
let cs = ConstraintSystem::<Fr>::new_ref();
|
||||
|
||||
// dealing with the 'zero' point should not panic when doing the unwrap
|
||||
let p = Projective::zero();
|
||||
NonNativeAffineVar::<Fr>::new_witness(cs.clone(), || Ok(p)).unwrap();
|
||||
|
||||
// check that point_to_nonnative_limbs returns the expected values
|
||||
let mut rng = ark_std::test_rng();
|
||||
let p = Projective::rand(&mut rng);
|
||||
let pVar = NonNativeAffineVar::<Fr>::new_witness(cs.clone(), || Ok(p)).unwrap();
|
||||
let (x, y) = point_to_nonnative_limbs(p).unwrap();
|
||||
assert_eq!(pVar.x.value().unwrap(), x);
|
||||
assert_eq!(pVar.y.value().unwrap(), y);
|
||||
}
|
||||
}
|
||||
|
||||
205
src/folding/nova/decider_eth.rs
Normal file
205
src/folding/nova/decider_eth.rs
Normal file
@@ -0,0 +1,205 @@
|
||||
/// This file implements the onchain (Ethereum's EVM) decider.
|
||||
use ark_crypto_primitives::sponge::Absorb;
|
||||
use ark_ec::{CurveGroup, Group};
|
||||
use ark_ff::PrimeField;
|
||||
use ark_r1cs_std::{groups::GroupOpsBounds, prelude::CurveVar};
|
||||
use ark_snark::SNARK;
|
||||
use ark_std::rand::CryptoRng;
|
||||
use ark_std::rand::RngCore;
|
||||
use core::marker::PhantomData;
|
||||
|
||||
pub use super::decider_eth_circuit::DeciderEthCircuit;
|
||||
use crate::commitment::{pedersen::Params as PedersenParams, CommitmentProver};
|
||||
use crate::folding::circuits::nonnative::point_to_nonnative_limbs_custom_opt;
|
||||
use crate::folding::nova::{circuits::CF2, CommittedInstance, Nova};
|
||||
use crate::frontend::FCircuit;
|
||||
use crate::Error;
|
||||
use crate::{Decider as DeciderTrait, FoldingScheme};
|
||||
use ark_r1cs_std::fields::nonnative::params::OptimizationType;
|
||||
|
||||
/// Onchain Decider, for ethereum use cases
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Decider<C1, GC1, C2, GC2, FC, CP1, CP2, S, FS> {
|
||||
_c1: PhantomData<C1>,
|
||||
_gc1: PhantomData<GC1>,
|
||||
_c2: PhantomData<C2>,
|
||||
_gc2: PhantomData<GC2>,
|
||||
_fc: PhantomData<FC>,
|
||||
_cp1: PhantomData<CP1>,
|
||||
_cp2: PhantomData<CP2>,
|
||||
_s: PhantomData<S>,
|
||||
_fs: PhantomData<FS>,
|
||||
}
|
||||
|
||||
impl<C1, GC1, C2, GC2, FC, CP1, CP2, S, FS> DeciderTrait<C1, C2, FC, FS>
|
||||
for Decider<C1, GC1, C2, GC2, FC, CP1, CP2, S, FS>
|
||||
where
|
||||
C1: CurveGroup,
|
||||
C2: CurveGroup,
|
||||
GC1: CurveVar<C1, CF2<C1>>,
|
||||
GC2: CurveVar<C2, CF2<C2>>,
|
||||
FC: FCircuit<C1::ScalarField>,
|
||||
CP1: CommitmentProver<C1>,
|
||||
// enforce that the CP2 is Pedersen commitment, since we're at Ethereum's EVM decider
|
||||
CP2: CommitmentProver<C2, Params = PedersenParams<C2>>,
|
||||
S: SNARK<C1::ScalarField>,
|
||||
FS: FoldingScheme<C1, C2, FC>,
|
||||
<C1 as CurveGroup>::BaseField: PrimeField,
|
||||
<C2 as CurveGroup>::BaseField: PrimeField,
|
||||
<C1 as Group>::ScalarField: Absorb,
|
||||
<C2 as Group>::ScalarField: Absorb,
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
for<'b> &'b GC2: GroupOpsBounds<'b, C2, GC2>,
|
||||
// constrain FS into Nova, since this is a Decider specificly for Nova
|
||||
Nova<C1, GC1, C2, GC2, FC, CP1, CP2>: From<FS>,
|
||||
{
|
||||
type ProverParam = S::ProvingKey;
|
||||
type Proof = S::Proof;
|
||||
type VerifierParam = S::VerifyingKey;
|
||||
type PublicInput = Vec<C1::ScalarField>;
|
||||
type CommittedInstanceWithWitness = ();
|
||||
type CommittedInstance = CommittedInstance<C1>;
|
||||
|
||||
fn prove(
|
||||
pp: &Self::ProverParam,
|
||||
mut rng: impl RngCore + CryptoRng,
|
||||
folding_scheme: FS,
|
||||
) -> Result<Self::Proof, Error> {
|
||||
let circuit =
|
||||
DeciderEthCircuit::<C1, GC1, C2, GC2, CP1, CP2>::from_nova::<FC>(folding_scheme.into());
|
||||
|
||||
let proof = S::prove(pp, circuit.clone(), &mut rng).unwrap();
|
||||
|
||||
Ok(proof)
|
||||
}
|
||||
|
||||
fn verify(
|
||||
vp: &Self::VerifierParam,
|
||||
i: C1::ScalarField,
|
||||
z_0: Vec<C1::ScalarField>,
|
||||
z_i: Vec<C1::ScalarField>,
|
||||
running_instance: &Self::CommittedInstance,
|
||||
proof: Self::Proof,
|
||||
) -> Result<bool, Error> {
|
||||
let (cmE_x, cmE_y) = point_to_nonnative_limbs_custom_opt::<C1>(
|
||||
running_instance.cmE,
|
||||
OptimizationType::Constraints,
|
||||
)?;
|
||||
let (cmW_x, cmW_y) = point_to_nonnative_limbs_custom_opt::<C1>(
|
||||
running_instance.cmW,
|
||||
OptimizationType::Constraints,
|
||||
)?;
|
||||
let public_input: Vec<C1::ScalarField> = vec![
|
||||
vec![i],
|
||||
z_0,
|
||||
z_i,
|
||||
vec![running_instance.u],
|
||||
running_instance.x.clone(),
|
||||
cmE_x,
|
||||
cmE_y,
|
||||
cmW_x,
|
||||
cmW_y,
|
||||
]
|
||||
.concat();
|
||||
S::verify(vp, &public_input, &proof).map_err(|e| Error::Other(e.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use ark_groth16::Groth16;
|
||||
use ark_mnt4_298::{constraints::G1Var as GVar, Fr, G1Projective as Projective, MNT4_298};
|
||||
use ark_mnt6_298::{constraints::G1Var as GVar2, G1Projective as Projective2};
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::commitment::pedersen::Pedersen;
|
||||
use crate::folding::nova::{get_pedersen_params_len, ProverParams};
|
||||
use crate::frontend::tests::CubicFCircuit;
|
||||
use crate::transcript::poseidon::poseidon_test_config;
|
||||
|
||||
// Note: since we're testing a big circuit, this test takes a bit more of computation and time,
|
||||
// do not run in the normal CI.
|
||||
// To run the test use `--ignored` flag, eg. `cargo test -- --ignored`
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_decider() {
|
||||
type NOVA = Nova<
|
||||
Projective,
|
||||
GVar,
|
||||
Projective2,
|
||||
GVar2,
|
||||
CubicFCircuit<Fr>,
|
||||
Pedersen<Projective>,
|
||||
Pedersen<Projective2>,
|
||||
>;
|
||||
type DECIDER = Decider<
|
||||
Projective,
|
||||
GVar,
|
||||
Projective2,
|
||||
GVar2,
|
||||
CubicFCircuit<Fr>,
|
||||
Pedersen<Projective>,
|
||||
Pedersen<Projective2>,
|
||||
Groth16<MNT4_298>, // here we define the Snark to use in the decider
|
||||
NOVA, // here we define the FoldingScheme to use
|
||||
>;
|
||||
|
||||
let mut rng = ark_std::test_rng();
|
||||
let poseidon_config = poseidon_test_config::<Fr>();
|
||||
|
||||
let F_circuit = CubicFCircuit::<Fr>::new(());
|
||||
let z_0 = vec![Fr::from(3_u32)];
|
||||
|
||||
let (cm_len, cf_cm_len) =
|
||||
get_pedersen_params_len::<Projective, GVar, Projective2, GVar2, CubicFCircuit<Fr>>(
|
||||
&poseidon_config,
|
||||
F_circuit,
|
||||
)
|
||||
.unwrap();
|
||||
let pedersen_params = Pedersen::<Projective>::new_params(&mut rng, cm_len);
|
||||
let cf_pedersen_params = Pedersen::<Projective2>::new_params(&mut rng, cf_cm_len);
|
||||
|
||||
let start = Instant::now();
|
||||
let prover_params =
|
||||
ProverParams::<Projective, Projective2, Pedersen<Projective>, Pedersen<Projective2>> {
|
||||
poseidon_config: poseidon_config.clone(),
|
||||
cm_params: pedersen_params,
|
||||
cf_cm_params: cf_pedersen_params,
|
||||
};
|
||||
println!("generating pedersen params, {:?}", start.elapsed());
|
||||
|
||||
// use Nova as FoldingScheme
|
||||
let start = Instant::now();
|
||||
let mut nova = NOVA::init(&prover_params, F_circuit, z_0.clone()).unwrap();
|
||||
println!("Nova initialized, {:?}", start.elapsed());
|
||||
let start = Instant::now();
|
||||
nova.prove_step().unwrap();
|
||||
println!("prove_step, {:?}", start.elapsed());
|
||||
|
||||
// generate Groth16 setup
|
||||
let circuit = DeciderEthCircuit::<
|
||||
Projective,
|
||||
GVar,
|
||||
Projective2,
|
||||
GVar2,
|
||||
Pedersen<Projective>,
|
||||
Pedersen<Projective2>,
|
||||
>::from_nova::<CubicFCircuit<Fr>>(nova.clone());
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
|
||||
let start = Instant::now();
|
||||
let (pk, vk) =
|
||||
Groth16::<MNT4_298>::circuit_specific_setup(circuit.clone(), &mut rng).unwrap();
|
||||
println!("Groth16 setup, {:?}", start.elapsed());
|
||||
|
||||
// decider proof generation
|
||||
let start = Instant::now();
|
||||
let proof = DECIDER::prove(&pk, rng, nova.clone()).unwrap();
|
||||
println!("Decider Groth16 prove, {:?}", start.elapsed());
|
||||
|
||||
// decider proof verification
|
||||
let verified = DECIDER::verify(&vk, nova.i, nova.z_0, nova.z_i, &nova.U_i, proof).unwrap();
|
||||
assert!(verified);
|
||||
}
|
||||
}
|
||||
@@ -178,6 +178,7 @@ where
|
||||
|
||||
/// Circuit that implements the in-circuit checks needed for the onchain (Ethereum's EVM)
|
||||
/// verification.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DeciderEthCircuit<C1, GC1, C2, GC2, CP1, CP2>
|
||||
where
|
||||
C1: CurveGroup,
|
||||
@@ -281,13 +282,12 @@ where
|
||||
Ok(self.r1cs.clone())
|
||||
})?;
|
||||
|
||||
let i = FpVar::<CF1<C1>>::new_witness(cs.clone(), || {
|
||||
Ok(self.i.unwrap_or_else(CF1::<C1>::zero))
|
||||
})?;
|
||||
let z_0 = Vec::<FpVar<CF1<C1>>>::new_witness(cs.clone(), || {
|
||||
let i =
|
||||
FpVar::<CF1<C1>>::new_input(cs.clone(), || Ok(self.i.unwrap_or_else(CF1::<C1>::zero)))?;
|
||||
let z_0 = Vec::<FpVar<CF1<C1>>>::new_input(cs.clone(), || {
|
||||
Ok(self.z_0.unwrap_or(vec![CF1::<C1>::zero()]))
|
||||
})?;
|
||||
let z_i = Vec::<FpVar<CF1<C1>>>::new_witness(cs.clone(), || {
|
||||
let z_i = Vec::<FpVar<CF1<C1>>>::new_input(cs.clone(), || {
|
||||
Ok(self.z_i.unwrap_or(vec![CF1::<C1>::zero()]))
|
||||
})?;
|
||||
|
||||
@@ -303,7 +303,7 @@ where
|
||||
let w_i = WitnessVar::<C1>::new_witness(cs.clone(), || {
|
||||
Ok(self.w_i.unwrap_or(w_dummy_native.clone()))
|
||||
})?;
|
||||
let U_i = CommittedInstanceVar::<C1>::new_witness(cs.clone(), || {
|
||||
let U_i = CommittedInstanceVar::<C1>::new_input(cs.clone(), || {
|
||||
Ok(self.U_i.unwrap_or(u_dummy_native.clone()))
|
||||
})?;
|
||||
let W_i = WitnessVar::<C1>::new_witness(cs.clone(), || {
|
||||
@@ -367,7 +367,7 @@ where
|
||||
#[cfg(not(test))]
|
||||
{
|
||||
// imports here instead of at the top of the file, so we avoid having multiple
|
||||
// `#[cfg(not(test))]
|
||||
// `#[cfg(not(test))]`
|
||||
use crate::commitment::pedersen::PedersenGadget;
|
||||
use crate::folding::nova::cyclefold::{CycleFoldCommittedInstanceVar, CF_IO_LEN};
|
||||
use ark_r1cs_std::ToBitsGadget;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/// Implements the scheme described in [Nova](https://eprint.iacr.org/2021/370.pdf)
|
||||
/// Implements the scheme described in [Nova](https://eprint.iacr.org/2021/370.pdf) and
|
||||
/// [CycleFold](https://eprint.iacr.org/2023/1192.pdf).
|
||||
use ark_crypto_primitives::{
|
||||
crh::{poseidon::CRH, CRHScheme},
|
||||
sponge::{poseidon::PoseidonConfig, Absorb},
|
||||
@@ -22,6 +23,7 @@ use crate::FoldingScheme;
|
||||
|
||||
pub mod circuits;
|
||||
pub mod cyclefold;
|
||||
pub mod decider_eth;
|
||||
pub mod decider_eth_circuit;
|
||||
pub mod nifs;
|
||||
pub mod traits;
|
||||
@@ -216,10 +218,8 @@ where
|
||||
type PreprocessorParam = (Self::ProverParam, FC);
|
||||
type ProverParam = ProverParams<C1, C2, CP1, CP2>;
|
||||
type VerifierParam = VerifierParams<C1, C2>;
|
||||
type Witness = Witness<C1>;
|
||||
type CommittedInstanceWithWitness = (CommittedInstance<C1>, Witness<C1>);
|
||||
type CFCommittedInstanceWithWitness = (CommittedInstance<C2>, Witness<C2>);
|
||||
type CommittedInstance = CommittedInstance<C1>;
|
||||
|
||||
fn preprocess(
|
||||
prep_param: &Self::PreprocessorParam,
|
||||
|
||||
40
src/lib.rs
40
src/lib.rs
@@ -5,18 +5,18 @@
|
||||
|
||||
use ark_ec::CurveGroup;
|
||||
use ark_ff::PrimeField;
|
||||
use ark_std::rand::CryptoRng;
|
||||
use ark_std::{fmt::Debug, rand::RngCore};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::frontend::FCircuit;
|
||||
|
||||
pub mod transcript;
|
||||
use transcript::Transcript;
|
||||
pub mod ccs;
|
||||
pub mod commitment;
|
||||
pub mod constants;
|
||||
pub mod folding;
|
||||
pub mod frontend;
|
||||
pub mod transcript;
|
||||
pub mod utils;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
@@ -82,10 +82,8 @@ where
|
||||
type PreprocessorParam: Debug;
|
||||
type ProverParam: Debug;
|
||||
type VerifierParam: Debug;
|
||||
type Witness: Debug;
|
||||
type CommittedInstanceWithWitness: Debug;
|
||||
type CFCommittedInstanceWithWitness: Debug; // CycleFold CommittedInstance & Witness
|
||||
type CommittedInstance: Clone + Debug;
|
||||
|
||||
fn preprocess(
|
||||
prep_param: &Self::PreprocessorParam,
|
||||
@@ -123,26 +121,36 @@ where
|
||||
) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
pub trait Decider<C: CurveGroup>: Clone + Debug {
|
||||
type PreprocessorParam: Debug;
|
||||
type ProverParam: Debug;
|
||||
type VerifierParam: Debug;
|
||||
type FreshInstance: Debug;
|
||||
pub trait Decider<
|
||||
C1: CurveGroup,
|
||||
C2: CurveGroup,
|
||||
FC: FCircuit<C1::ScalarField>,
|
||||
FS: FoldingScheme<C1, C2, FC>,
|
||||
> where
|
||||
C1: CurveGroup<BaseField = C2::ScalarField, ScalarField = C2::BaseField>,
|
||||
C2::BaseField: PrimeField,
|
||||
{
|
||||
type ProverParam: Clone;
|
||||
type Proof: Clone;
|
||||
type VerifierParam;
|
||||
type PublicInput: Debug;
|
||||
type CommittedInstanceWithWitness: Debug;
|
||||
type CommittedInstance: Clone + Debug;
|
||||
|
||||
fn prove(
|
||||
pp: &Self::ProverParam,
|
||||
running_instance: &Self::CommittedInstanceWithWitness,
|
||||
transcript: &mut impl Transcript<C>,
|
||||
rng: impl RngCore,
|
||||
) -> Result<(), Error>;
|
||||
rng: impl RngCore + CryptoRng,
|
||||
folding_scheme: FS,
|
||||
) -> Result<Self::Proof, Error>;
|
||||
|
||||
fn verify(
|
||||
vp: &Self::VerifierParam,
|
||||
i: C1::ScalarField,
|
||||
z_0: Vec<C1::ScalarField>,
|
||||
z_i: Vec<C1::ScalarField>,
|
||||
running_instance: &Self::CommittedInstance,
|
||||
transcript: &mut impl Transcript<C>,
|
||||
rng: impl RngCore,
|
||||
) -> Result<(), Error>;
|
||||
proof: Self::Proof,
|
||||
// returns `Result<bool, Error>` to differentiate between an error occurred while performing
|
||||
// the verification steps, and the verification logic of the scheme not passing.
|
||||
) -> Result<bool, Error>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user