Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "nova-snark"
version = "0.73.0"
version = "0.74.0"
authors = ["Srinath Setty <srinath@microsoft.com>"]
edition = "2021"
description = "High-speed recursive arguments from folding schemes"
Expand Down
29 changes: 25 additions & 4 deletions src/frontend/r1cs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,31 @@ use super::{shape_cs::ShapeCS, solver::SatisfyingAssignment, test_shape_cs::Test
use crate::{
errors::NovaError,
frontend::{Index, LinearCombination},
r1cs::{R1CSInstance, R1CSShape, R1CSWitness, SparseMatrix},
r1cs::{R1CSInstance, R1CSShape, R1CSWitness, R1CSWitnessBlind, SparseMatrix},
traits::Engine,
CommitmentKey,
};
use ff::PrimeField;

/// `NovaWitness` provide a method for acquiring an `R1CSInstance` and `R1CSWitness` from implementers.
pub trait NovaWitness<E: Engine> {
/// Return an instance and witness, given a shape and ck.
/// Returns an instance and witness using a fresh random witness blind.
/// The sampled blind is available through [`R1CSWitness::r_W`].
fn r1cs_instance_and_witness(
&self,
shape: &R1CSShape<E>,
ck: &CommitmentKey<E>,
) -> Result<(R1CSInstance<E>, R1CSWitness<E>), NovaError>;

/// Returns an instance and witness using an explicit typed witness blind.
///
/// This variant is intended for deterministic protocols and exact replay.
fn r1cs_instance_and_witness_with_blind(
&self,
shape: &R1CSShape<E>,
ck: &CommitmentKey<E>,
blind: R1CSWitnessBlind<E>,
) -> Result<(R1CSInstance<E>, R1CSWitness<E>), NovaError>;
}

/// `NovaShape` provides methods for acquiring `R1CSShape` from implementers.
Expand All @@ -33,11 +44,21 @@ impl<E: Engine> NovaWitness<E> for SatisfyingAssignment<E> {
) -> Result<(R1CSInstance<E>, R1CSWitness<E>), NovaError> {
let W = R1CSWitness::<E>::new(shape, self.aux_assignment())?;
let X = &self.input_assignment()[1..];

let comm_W = W.commit(ck);

let instance = R1CSInstance::<E>::new(shape, &comm_W, X)?;
Ok((instance, W))
}

fn r1cs_instance_and_witness_with_blind(
&self,
shape: &R1CSShape<E>,
ck: &CommitmentKey<E>,
blind: R1CSWitnessBlind<E>,
) -> Result<(R1CSInstance<E>, R1CSWitness<E>), NovaError> {
let W = R1CSWitness::<E>::new_with_blind(shape, self.aux_assignment(), blind)?;
let X = &self.input_assignment()[1..];
let comm_W = W.commit(ck);
let instance = R1CSInstance::<E>::new(shape, &comm_W, X)?;
Ok((instance, W))
}
}
Expand Down
72 changes: 70 additions & 2 deletions src/r1cs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,33 @@ pub struct R1CSWitness<E: Engine> {
pub(crate) r_W: E::Scalar,
}

/// A blinding factor for an R1CS witness commitment.
///
/// Use [`Self::random`] for ordinary proving. Protocols that support exact
/// replay may reconstruct a previously derived value with
/// [`Self::from_protocol_scalar`].
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct R1CSWitnessBlind<E: Engine>(E::Scalar);

impl<E: Engine> R1CSWitnessBlind<E> {
/// Samples a fresh witness commitment blinding factor.
pub fn random() -> Self {
Self(E::Scalar::random(&mut OsRng))
}

/// Wraps a protocol-derived blinding factor.
///
/// The caller must ensure the scalar was derived from secret randomness with
/// a unique domain and coordinates. Reuse it only to replay the same proof.
pub fn from_protocol_scalar(blind: E::Scalar) -> Self {
Comment thread
sai-deng marked this conversation as resolved.
Outdated
Self(blind)
}

fn into_scalar(self) -> E::Scalar {
self.0
}
}

/// A type that holds an R1CS instance
#[serde_as]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
Expand Down Expand Up @@ -805,14 +832,24 @@ impl<E: Engine> R1CSShape<E> {
}

impl<E: Engine> R1CSWitness<E> {
/// A method to create a witness object using a vector of scalars
/// Creates a witness with a fresh random commitment blinding factor.
/// The sampled blind is available through [`Self::r_W`].
pub fn new(S: &R1CSShape<E>, W: &[E::Scalar]) -> Result<R1CSWitness<E>, NovaError> {
Self::new_with_blind(S, W, R1CSWitnessBlind::random())
}

/// Creates a witness using an explicit typed commitment blinding factor.
pub fn new_with_blind(
S: &R1CSShape<E>,
W: &[E::Scalar],
blind: R1CSWitnessBlind<E>,
) -> Result<R1CSWitness<E>, NovaError> {
let mut W = W.to_vec();
W.resize(S.num_vars, E::Scalar::ZERO);

Ok(R1CSWitness {
W,
r_W: E::Scalar::random(&mut OsRng),
r_W: blind.into_scalar(),
})
}

Expand Down Expand Up @@ -1436,6 +1473,37 @@ mod tests {
test_random_sample_with::<Secp256k1Engine>();
}

#[test]
fn test_witness_with_blind_is_deterministic() {
let shape = tiny_r1cs::<Bn256EngineKZG>(4);
let ck = R1CSShape::commitment_key(&[&shape], &[&*default_ck_hint()]).unwrap();
let values = vec![<Bn256EngineKZG as Engine>::Scalar::ONE; 3];
let blind = <Bn256EngineKZG as Engine>::Scalar::from(42_u64);

let witness_1 = R1CSWitness::new_with_blind(
&shape,
&values,
R1CSWitnessBlind::from_protocol_scalar(blind),
)
.unwrap();
let witness_2 = R1CSWitness::new_with_blind(
&shape,
&values,
R1CSWitnessBlind::from_protocol_scalar(blind),
)
.unwrap();
let witness_3 = R1CSWitness::new_with_blind(
&shape,
&values,
R1CSWitnessBlind::from_protocol_scalar(<Bn256EngineKZG as Engine>::Scalar::from(43_u64)),
)
.unwrap();

assert_eq!(witness_1, witness_2);
assert_eq!(witness_1.commit(&ck), witness_2.commit(&ck));
assert_ne!(witness_1.commit(&ck), witness_3.commit(&ck));
}

fn test_multiply_vec_pair_with<E: Engine>() {
// tiny_r1cs(4) has num_cons=4, num_vars=4, num_io=2
// z has length num_vars + 1 + num_io = 7
Expand Down
Loading