sonobe-primitives 0.1.0-alpha.1

Cryptographic primitives, utilities, and abstractions for the Sonobe library
Documentation
//! This module implements in-circuit R1CS variables and relation check gadgets.

use ark_ff::{PrimeField, Zero};
use ark_r1cs_std::alloc::{AllocVar, AllocationMode};
use ark_relations::gr1cs::{Namespace, SynthesisError};
use ark_std::{One, borrow::Borrow, ops::Mul};

use super::R1CS;
use crate::{
    algebra::ops::{
        eq::EquivalenceGadget,
        matrix::{MatrixGadget, SparseMatrixVar},
        vector::VectorGadget,
    },
    arithmetizations::ArithRelationGadget,
    circuits::Assignments,
};

/// [`R1CSMatricesVar`] is the in-circuit variable of a given R1CS structure.
///
/// Only the matrices are represented, while the remaining R1CS parameters are
/// constants to the circuit.
///
/// The naming is chosen to distinguish from arkworks' `(G)R1CSVar`.
#[allow(non_snake_case)]
#[derive(Debug, Clone)]
pub struct R1CSMatricesVar<FVar> {
    A: SparseMatrixVar<FVar>,
    B: SparseMatrixVar<FVar>,
    C: SparseMatrixVar<FVar>,
}

impl<F: PrimeField, ConstraintF: PrimeField, FVar: AllocVar<F, ConstraintF>>
    AllocVar<R1CS<F>, ConstraintF> for R1CSMatricesVar<FVar>
{
    fn new_variable<T: Borrow<R1CS<F>>>(
        cs: impl Into<Namespace<ConstraintF>>,
        f: impl FnOnce() -> Result<T, SynthesisError>,
        mode: AllocationMode,
    ) -> Result<Self, SynthesisError> {
        f().and_then(|val| {
            let cs = cs.into();

            let val = val.borrow();

            Ok(Self {
                A: SparseMatrixVar::<FVar>::new_variable(
                    cs.clone(),
                    || Ok(&val.matrices[0]),
                    mode,
                )?,
                B: SparseMatrixVar::<FVar>::new_variable(
                    cs.clone(),
                    || Ok(&val.matrices[1]),
                    mode,
                )?,
                C: SparseMatrixVar::<FVar>::new_variable(
                    cs.clone(),
                    || Ok(&val.matrices[2]),
                    mode,
                )?,
            })
        })
    }
}

impl<FVar> R1CSMatricesVar<FVar>
where
    SparseMatrixVar<FVar>: MatrixGadget<FVar>,
    [FVar]: VectorGadget<FVar>,
    for<'a> &'a FVar: Mul<&'a FVar, Output = FVar>,
{
    /// [`R1CSMatricesVar::evaluate_r1cs`] is the in-circuit version of
    /// [`R1CS::evaluate_r1cs`] that evaluates the R1CS variable at a given
    /// vector of assignments `z`.
    #[allow(non_snake_case)]
    pub fn evaluate_r1cs(
        &self,
        z: Assignments<FVar, impl AsRef<[FVar]>>,
    ) -> Result<Vec<FVar>, SynthesisError> {
        // Multiply Cz by z[0] (u) here, allowing this method to be reused for
        // both relaxed and plain R1CS.
        let Az = self.A.mul_vector(&z)?;
        let Bz = self.B.mul_vector(&z)?;
        let Cz = self.C.mul_vector(&z)?;
        let uCz = Cz.scale(&z[0])?;
        let AzBz = Az.hadamard(&Bz)?;
        AzBz.sub(&uCz)
    }
}

impl<FVar, WVar: AsRef<[FVar]>, UVar: AsRef<[FVar]>> ArithRelationGadget<WVar, UVar>
    for R1CSMatricesVar<FVar>
where
    SparseMatrixVar<FVar>: MatrixGadget<FVar>,
    [FVar]: VectorGadget<FVar> + EquivalenceGadget<[FVar]>,
    // TODO (@winderica): this will not work for our incoming decider
    FVar: Clone + Zero + One,
    for<'a> &'a FVar: Mul<&'a FVar, Output = FVar>,
{
    type Evaluation = Vec<FVar>;

    fn eval_relation(&self, w: &WVar, u: &UVar) -> Result<Self::Evaluation, SynthesisError> {
        self.evaluate_r1cs((FVar::one(), u.as_ref(), w.as_ref()).into())
    }

    fn check_evaluation(_w: &WVar, _u: &UVar, e: Self::Evaluation) -> Result<(), SynthesisError> {
        e.enforce_equivalent(&vec![FVar::zero(); e.len()])
    }
}

#[cfg(test)]
mod tests {
    use ark_bn254::Fr;
    use ark_ff::{One, UniformRand, Zero};
    use ark_std::{error::Error, rand::thread_rng};

    use super::*;
    use crate::{
        circuits::utils::{constraints_for_test, satisfying_assignments_for_test},
        relations::Relation,
    };

    #[test]
    fn test_eval() -> Result<(), Box<dyn Error>> {
        let mut rng = thread_rng();
        let r1cs = constraints_for_test::<Fr>();

        assert!(
            r1cs.evaluate_r1cs(satisfying_assignments_for_test(Fr::rand(&mut rng)))?
                .into_iter()
                .all(|e| e.is_zero())
        );
        assert!(
            !r1cs
                .evaluate_r1cs(Assignments::from((
                    Fr::one(),
                    vec![Fr::rand(&mut rng)],
                    vec![
                        Fr::rand(&mut rng),
                        Fr::rand(&mut rng),
                        Fr::rand(&mut rng),
                        Fr::rand(&mut rng),
                    ],
                )))?
                .into_iter()
                .all(|e| e.is_zero())
        );

        Ok(())
    }

    #[test]
    fn test_check() -> Result<(), Box<dyn Error>> {
        let mut rng = thread_rng();
        let r1cs = constraints_for_test::<Fr>();

        let assignments = satisfying_assignments_for_test(Fr::rand(&mut rng));

        assert!(
            r1cs.check_relation(&assignments.private, &assignments.public)
                .is_ok()
        );
        assert!(
            r1cs.check_relation(
                &[
                    Fr::rand(&mut rng),
                    Fr::rand(&mut rng),
                    Fr::rand(&mut rng),
                    Fr::rand(&mut rng),
                ],
                &[Fr::rand(&mut rng)]
            )
            .is_err()
        );

        Ok(())
    }
}