Skip to content

Commit 0d0d00a

Browse files
authored
Add explicit R1CS witness blinding API (#506)
* Expose explicit R1CS witness blinding * Require explicit R1CS witness blinds * Fix explicit blind test import * Bump nova-snark to 0.73.1 * Clarify witness blind secrecy requirements * Bump nova-snark to 0.74.0 * Address witness blinding review nits * Add safe witness blinding API * Align witness blind conversion naming
1 parent 9092303 commit 0d0d00a

3 files changed

Lines changed: 88 additions & 7 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "nova-snark"
3-
version = "0.73.0"
3+
version = "0.74.0"
44
authors = ["Srinath Setty <srinath@microsoft.com>"]
55
edition = "2021"
66
description = "High-speed recursive arguments from folding schemes"

src/frontend/r1cs.rs

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,31 @@ use super::{shape_cs::ShapeCS, solver::SatisfyingAssignment, test_shape_cs::Test
33
use crate::{
44
errors::NovaError,
55
frontend::{Index, LinearCombination},
6-
r1cs::{R1CSInstance, R1CSShape, R1CSWitness, SparseMatrix},
6+
r1cs::{R1CSInstance, R1CSShape, R1CSWitness, R1CSWitnessBlind, SparseMatrix},
77
traits::Engine,
88
CommitmentKey,
99
};
1010
use ff::PrimeField;
1111

1212
/// `NovaWitness` provide a method for acquiring an `R1CSInstance` and `R1CSWitness` from implementers.
1313
pub trait NovaWitness<E: Engine> {
14-
/// Return an instance and witness, given a shape and ck.
14+
/// Returns an instance and witness using a fresh random witness blind.
15+
/// The sampled blind is available through [`R1CSWitness::r_W`].
1516
fn r1cs_instance_and_witness(
1617
&self,
1718
shape: &R1CSShape<E>,
1819
ck: &CommitmentKey<E>,
1920
) -> Result<(R1CSInstance<E>, R1CSWitness<E>), NovaError>;
21+
22+
/// Returns an instance and witness using an explicit typed witness blind.
23+
///
24+
/// This variant is intended for deterministic protocols and exact replay.
25+
fn r1cs_instance_and_witness_with_blind(
26+
&self,
27+
shape: &R1CSShape<E>,
28+
ck: &CommitmentKey<E>,
29+
blind: R1CSWitnessBlind<E>,
30+
) -> Result<(R1CSInstance<E>, R1CSWitness<E>), NovaError>;
2031
}
2132

2233
/// `NovaShape` provides methods for acquiring `R1CSShape` from implementers.
@@ -33,11 +44,21 @@ impl<E: Engine> NovaWitness<E> for SatisfyingAssignment<E> {
3344
) -> Result<(R1CSInstance<E>, R1CSWitness<E>), NovaError> {
3445
let W = R1CSWitness::<E>::new(shape, self.aux_assignment())?;
3546
let X = &self.input_assignment()[1..];
36-
3747
let comm_W = W.commit(ck);
38-
3948
let instance = R1CSInstance::<E>::new(shape, &comm_W, X)?;
49+
Ok((instance, W))
50+
}
4051

52+
fn r1cs_instance_and_witness_with_blind(
53+
&self,
54+
shape: &R1CSShape<E>,
55+
ck: &CommitmentKey<E>,
56+
blind: R1CSWitnessBlind<E>,
57+
) -> Result<(R1CSInstance<E>, R1CSWitness<E>), NovaError> {
58+
let W = R1CSWitness::<E>::new_with_blind(shape, self.aux_assignment(), blind)?;
59+
let X = &self.input_assignment()[1..];
60+
let comm_W = W.commit(ck);
61+
let instance = R1CSInstance::<E>::new(shape, &comm_W, X)?;
4162
Ok((instance, W))
4263
}
4364
}

src/r1cs/mod.rs

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,33 @@ pub struct R1CSWitness<E: Engine> {
6868
pub(crate) r_W: E::Scalar,
6969
}
7070

71+
/// A blinding factor for an R1CS witness commitment.
72+
///
73+
/// Use [`Self::random`] for ordinary proving. Protocols that support exact
74+
/// replay may reconstruct a previously derived value with
75+
/// [`Self::from_scalar`].
76+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
77+
pub struct R1CSWitnessBlind<E: Engine>(E::Scalar);
78+
79+
impl<E: Engine> R1CSWitnessBlind<E> {
80+
/// Samples a fresh witness commitment blinding factor.
81+
pub fn random() -> Self {
82+
Self(E::Scalar::random(&mut OsRng))
83+
}
84+
85+
/// Wraps a protocol-derived blinding factor.
86+
///
87+
/// The caller must ensure the scalar was derived from secret randomness with
88+
/// a unique domain and coordinates. Reuse it only to replay the same proof.
89+
pub fn from_scalar(blind: E::Scalar) -> Self {
90+
Self(blind)
91+
}
92+
93+
fn into_scalar(self) -> E::Scalar {
94+
self.0
95+
}
96+
}
97+
7198
/// A type that holds an R1CS instance
7299
#[serde_as]
73100
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
@@ -805,14 +832,24 @@ impl<E: Engine> R1CSShape<E> {
805832
}
806833

807834
impl<E: Engine> R1CSWitness<E> {
808-
/// A method to create a witness object using a vector of scalars
835+
/// Creates a witness with a fresh random commitment blinding factor.
836+
/// The sampled blind is available through [`Self::r_W`].
809837
pub fn new(S: &R1CSShape<E>, W: &[E::Scalar]) -> Result<R1CSWitness<E>, NovaError> {
838+
Self::new_with_blind(S, W, R1CSWitnessBlind::random())
839+
}
840+
841+
/// Creates a witness using an explicit typed commitment blinding factor.
842+
pub fn new_with_blind(
843+
S: &R1CSShape<E>,
844+
W: &[E::Scalar],
845+
blind: R1CSWitnessBlind<E>,
846+
) -> Result<R1CSWitness<E>, NovaError> {
810847
let mut W = W.to_vec();
811848
W.resize(S.num_vars, E::Scalar::ZERO);
812849

813850
Ok(R1CSWitness {
814851
W,
815-
r_W: E::Scalar::random(&mut OsRng),
852+
r_W: blind.into_scalar(),
816853
})
817854
}
818855

@@ -1436,6 +1473,29 @@ mod tests {
14361473
test_random_sample_with::<Secp256k1Engine>();
14371474
}
14381475

1476+
#[test]
1477+
fn test_witness_with_blind_is_deterministic() {
1478+
let shape = tiny_r1cs::<Bn256EngineKZG>(4);
1479+
let ck = R1CSShape::commitment_key(&[&shape], &[&*default_ck_hint()]).unwrap();
1480+
let values = vec![<Bn256EngineKZG as Engine>::Scalar::ONE; 3];
1481+
let blind = <Bn256EngineKZG as Engine>::Scalar::from(42_u64);
1482+
1483+
let witness_1 =
1484+
R1CSWitness::new_with_blind(&shape, &values, R1CSWitnessBlind::from_scalar(blind)).unwrap();
1485+
let witness_2 =
1486+
R1CSWitness::new_with_blind(&shape, &values, R1CSWitnessBlind::from_scalar(blind)).unwrap();
1487+
let witness_3 = R1CSWitness::new_with_blind(
1488+
&shape,
1489+
&values,
1490+
R1CSWitnessBlind::from_scalar(<Bn256EngineKZG as Engine>::Scalar::from(43_u64)),
1491+
)
1492+
.unwrap();
1493+
1494+
assert_eq!(witness_1, witness_2);
1495+
assert_eq!(witness_1.commit(&ck), witness_2.commit(&ck));
1496+
assert_ne!(witness_1.commit(&ck), witness_3.commit(&ck));
1497+
}
1498+
14391499
fn test_multiply_vec_pair_with<E: Engine>() {
14401500
// tiny_r1cs(4) has num_cons=4, num_vars=4, num_io=2
14411501
// z has length num_vars + 1 + num_io = 7

0 commit comments

Comments
 (0)