Skip to content

Commit 2c5e927

Browse files
committed
chore(circuit): define upper-limit of wire vars.
The current circuit API is very primitive and basic, and does not impose any limit on the wire value, nor it checks for overflow, incorrect usages of zip, etc. Additionally, downstream of this crate, implementations will most likely need to convert the wire index into a field elements. This commit tries to improve the status quo with an upper-limit of wire vars of 2^30 (so it can just fit a group element for most finite fields used in zkps). Along the way, we improve the API for setting public vars
1 parent be99383 commit 2c5e927

6 files changed

Lines changed: 315 additions & 30 deletions

File tree

Cargo.lock

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,13 @@ bls12_381 = "^0.8.0"
5555
bytemuck = "^1.22"
5656
curve25519-dalek = "^4.1"
5757
digest = "0.11.2"
58+
hashbrown = { version = "0.15.5", default-features = false, features = [
59+
"allocator-api2",
60+
"default-hasher",
61+
"inline-more",
62+
] }
5863
hex = "0.4.3"
64+
itertools = { version = "0.14.0", features = ["use_alloc"], default-features = false }
5965
k12 = "0.4.0-rc.1"
6066
k256 = "^0.13"
6167
keccak = "^0.1.5"

circuit/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ readme.workspace = true
77
homepage.workspace = true
88
license.workspace = true
99
repository.workspace = true
10-
version.workspace = true
10+
version = "0.6.2"
1111

1212
[package.metadata.docs.rs]
1313
all-features = true
@@ -18,6 +18,8 @@ p3-baby-bear = ["dep:p3-baby-bear", "spongefish/p3-baby-bear"]
1818

1919
[dependencies]
2020

21+
hashbrown = { workspace = true }
22+
itertools = { workspace = true }
2123
# Optional Plonky3/BabyBear support
2224
p3-baby-bear = { workspace = true, optional = true }
2325
p3-field = { workspace = true }

circuit/src/allocator.rs

Lines changed: 83 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,43 @@
11
//! Defines the allocator and wires to be used for computing the key-derivation steps.
22
33
use alloc::{sync::Arc, vec::Vec};
4+
use core::borrow::Borrow;
45

6+
use hashbrown::HashMap;
7+
use itertools::Itertools;
58
use spin::RwLock;
69
use spongefish::Unit;
710

811
/// A symbolic wire over which we perform out computation.
9-
/// Wraps over a [`usize`]
10-
#[derive(Clone, Copy, Default, Hash, PartialEq, Eq, Unit)]
11-
pub struct FieldVar(pub usize);
12+
#[derive(Clone, Copy, Default, Hash, PartialEq, Eq)]
13+
pub struct FieldVar(usize);
14+
15+
impl FieldVar {
16+
/// Maximum number of variables supported by the circuit allocator.
17+
pub const MAX_COUNT: usize = 1 << 30;
18+
/// The distinguished zero variable.
19+
pub const ZERO: Self = Self(0);
20+
21+
/// Return the variable index.
22+
#[must_use]
23+
pub const fn index(self) -> usize {
24+
self.0
25+
}
26+
27+
/// Construct a variable from an index when it is within the supported range.
28+
#[must_use]
29+
pub const fn try_from_index(index: usize) -> Option<Self> {
30+
if index < Self::MAX_COUNT {
31+
Some(Self(index))
32+
} else {
33+
None
34+
}
35+
}
36+
}
37+
38+
impl Unit for FieldVar {
39+
const ZERO: Self = Self::ZERO;
40+
}
1241

1342
impl core::fmt::Debug for FieldVar {
1443
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
@@ -27,7 +56,7 @@ pub struct VarAllocator<T> {
2756

2857
struct AllocatorState<T> {
2958
vars_count: usize,
30-
public_values: Vec<(FieldVar, T)>,
59+
public_values: HashMap<FieldVar, T>,
3160
}
3261

3362
impl<T: Clone + Unit> Default for VarAllocator<T> {
@@ -40,17 +69,24 @@ impl<T: Clone + Unit> VarAllocator<T> {
4069
#[must_use]
4170
pub fn new() -> Self {
4271
let zero_var = FieldVar::ZERO;
72+
let mut public_values = HashMap::new();
73+
public_values.insert(zero_var, T::ZERO);
4374
Self {
4475
state: Arc::new(RwLock::new(AllocatorState {
4576
vars_count: 1,
46-
public_values: Vec::from([(zero_var, T::ZERO)]),
77+
public_values,
4778
})),
4879
}
4980
}
5081

5182
#[must_use]
5283
pub fn new_field_var(&self) -> FieldVar {
5384
let mut state = self.state.write();
85+
assert!(
86+
state.vars_count < FieldVar::MAX_COUNT,
87+
"variable count exceeds supported maximum {}",
88+
FieldVar::MAX_COUNT,
89+
);
5490
let var = FieldVar(state.vars_count);
5591
state.vars_count += 1;
5692
var
@@ -67,6 +103,18 @@ impl<T: Clone + Unit> VarAllocator<T> {
67103

68104
#[must_use]
69105
pub fn allocate_vars_vec(&self, count: usize) -> Vec<FieldVar> {
106+
{
107+
let state = self.state.read();
108+
let new_count = state
109+
.vars_count
110+
.checked_add(count)
111+
.expect("variable count overflow");
112+
assert!(
113+
new_count <= FieldVar::MAX_COUNT,
114+
"variable count exceeds supported maximum {}",
115+
FieldVar::MAX_COUNT,
116+
);
117+
}
70118
(0..count).map(|_| self.new_field_var()).collect()
71119
}
72120

@@ -87,27 +135,51 @@ impl<T: Clone + Unit> VarAllocator<T> {
87135
self.state.read().vars_count
88136
}
89137

90-
pub fn set_public_var(&self, val: FieldVar, var: T) {
91-
self.state.write().public_values.push((val, var));
138+
#[must_use]
139+
pub fn is_allocated(&self, var: FieldVar) -> bool {
140+
var.index() < self.vars_count()
141+
}
142+
143+
/// Assigns the wire variable `var` to `val`.
144+
///
145+
/// If the wire was already present, it is over-written.
146+
pub fn set_public_var(&self, var: FieldVar, val: T) {
147+
self.state.write().public_values.insert(var, val);
92148
}
93149

150+
/// Sets a list of public variables.
151+
///
152+
/// Takes as input two iterators (for wires and values respectively),
153+
/// and sets each of them to public values.
154+
///
155+
/// # Panics
156+
///
157+
/// If the iterators have different length, this function will panic.
94158
pub fn set_public_vars<Val, Var>(
95159
&self,
96160
vars: impl IntoIterator<Item = Var>,
97161
vals: impl IntoIterator<Item = Val>,
98162
) where
99-
Var: core::borrow::Borrow<FieldVar>,
100-
Val: core::borrow::Borrow<T>,
163+
Var: Borrow<FieldVar>,
164+
Val: Borrow<T>,
101165
{
102166
self.state.write().public_values.extend(
103167
vars.into_iter()
104-
.zip(vals)
168+
.zip_eq(vals)
105169
.map(|(var, val)| (*var.borrow(), val.borrow().clone())),
106170
);
107171
}
108172

109173
#[must_use]
110174
pub fn public_vars(&self) -> Vec<(FieldVar, T)> {
111-
self.state.read().public_values.clone()
175+
let mut public_values = self
176+
.state
177+
.read()
178+
.public_values
179+
.iter()
180+
.map(|(var, val)| (*var, val.clone()))
181+
.collect::<Vec<_>>();
182+
public_values.sort_unstable_by_key(|(var, _)| var.index());
183+
public_values
112184
}
113185
}

circuit/src/permutation.rs

Lines changed: 96 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::allocator::{FieldVar, VarAllocator};
1111
#[derive(Clone)]
1212
pub struct PermutationInstanceBuilder<T, const WIDTH: usize> {
1313
allocator: VarAllocator<T>,
14-
permutation_constraints: Arc<RwLock<PermutationInstance<WIDTH>>>,
14+
query_answers: Arc<RwLock<Vec<QueryAnswerPair<FieldVar, WIDTH>>>>,
1515
linear_constraints: Arc<RwLock<LinearConstraints<FieldVar, T>>>,
1616
}
1717

@@ -82,11 +82,50 @@ pub struct PermutationWitnessBuilder<P: Permutation<WIDTH>, const WIDTH: usize>
8282
linear_constraints: Arc<RwLock<LinearConstraints<P::U, P::U>>>,
8383
}
8484

85-
/// The internal state of the instance,
86-
/// holding the input-output pairs of the wires to be proven.
87-
#[derive(Clone, Default)]
88-
struct PermutationInstance<const WIDTH: usize> {
89-
state: Vec<QueryAnswerPair<FieldVar, WIDTH>>,
85+
/// An immutable snapshot of a permutation relation instance.
86+
#[derive(Clone, Debug, PartialEq, Eq)]
87+
pub struct PermutationInstance<T, const WIDTH: usize> {
88+
pub vars_count: usize,
89+
pub public_values: Vec<(FieldVar, T)>,
90+
/// The input-output wires to be proven
91+
pub query_answers: Vec<QueryAnswerPair<FieldVar, WIDTH>>,
92+
pub linear_constraints: LinearConstraints<FieldVar, T>,
93+
}
94+
95+
impl<T, const WIDTH: usize> PermutationInstance<T, WIDTH> {
96+
#[must_use]
97+
pub fn constraints(&self) -> impl AsRef<[QueryAnswerPair<FieldVar, WIDTH>]> + '_ {
98+
&self.query_answers
99+
}
100+
101+
#[must_use]
102+
pub const fn linear_constraints(&self) -> &LinearConstraints<FieldVar, T> {
103+
&self.linear_constraints
104+
}
105+
106+
#[must_use]
107+
pub fn public_vars(&self) -> &[(FieldVar, T)] {
108+
&self.public_values
109+
}
110+
}
111+
112+
/// An immutable snapshot of a permutation witness.
113+
#[derive(Clone, Debug, PartialEq, Eq)]
114+
pub struct PermutationWitness<T, const WIDTH: usize> {
115+
pub trace: Vec<QueryAnswerPair<T, WIDTH>>,
116+
pub linear_constraints: LinearConstraints<T, T>,
117+
}
118+
119+
impl<T, const WIDTH: usize> PermutationWitness<T, WIDTH> {
120+
#[must_use]
121+
pub fn trace(&self) -> impl AsRef<[QueryAnswerPair<T, WIDTH>]> + '_ {
122+
&self.trace
123+
}
124+
125+
#[must_use]
126+
pub const fn linear_constraints(&self) -> &LinearConstraints<T, T> {
127+
&self.linear_constraints
128+
}
90129
}
91130

92131
impl<T: Unit, const WIDTH: usize> Permutation<WIDTH> for PermutationInstanceBuilder<T, WIDTH> {
@@ -118,7 +157,7 @@ impl<T: Clone + Unit, const WIDTH: usize> PermutationInstanceBuilder<T, WIDTH> {
118157
pub fn with_allocator(allocator: VarAllocator<T>) -> Self {
119158
Self {
120159
allocator,
121-
permutation_constraints: Default::default(),
160+
query_answers: Default::default(),
122161
linear_constraints: Default::default(),
123162
}
124163
}
@@ -141,19 +180,47 @@ impl<T: Clone + Unit, const WIDTH: usize> PermutationInstanceBuilder<T, WIDTH> {
141180
}
142181

143182
pub fn add_permutation(&self, input: [FieldVar; WIDTH], output: [FieldVar; WIDTH]) {
144-
self.permutation_constraints
183+
debug_assert!(input
184+
.iter()
185+
.chain(output.iter())
186+
.all(|var| self.allocator.is_allocated(*var)));
187+
self.query_answers
145188
.write()
146-
.state
147189
.push(QueryAnswerPair::new(input, output));
148190
}
149191

150-
pub fn add_equation(&self, equation: LinearEquation<FieldVar, T>) {
192+
pub fn add_equation(&self, equation: LinearEquation<FieldVar, T>)
193+
where
194+
T: PartialEq,
195+
{
196+
let constraints = self.query_answers.read();
197+
for (_, var) in &equation.linear_combination {
198+
assert!(
199+
self.allocator.is_allocated(*var),
200+
"unallocated variable {}",
201+
var.index(),
202+
);
203+
}
204+
for (term_idx, (coeff, var)) in equation.linear_combination.iter().enumerate() {
205+
if *coeff == T::ZERO {
206+
continue;
207+
}
208+
assert!(
209+
constraints
210+
.iter()
211+
.flat_map(|pair| pair.input.iter().chain(pair.output.iter()))
212+
.any(|known_var| known_var == var),
213+
"linear equation term {term_idx} references variable {}, \
214+
but nonzero linear terms must reference a permutation input or output variable",
215+
var.index(),
216+
);
217+
}
151218
self.linear_constraints.write().equations.push(equation);
152219
}
153220

154221
#[must_use]
155222
pub fn constraints(&self) -> impl AsRef<[QueryAnswerPair<FieldVar, WIDTH>]> {
156-
self.permutation_constraints.read().state.clone()
223+
self.query_answers.read().clone()
157224
}
158225

159226
#[must_use]
@@ -165,6 +232,16 @@ impl<T: Clone + Unit, const WIDTH: usize> PermutationInstanceBuilder<T, WIDTH> {
165232
pub fn public_vars(&self) -> Vec<(FieldVar, T)> {
166233
self.allocator.public_vars()
167234
}
235+
236+
#[must_use]
237+
pub fn snapshot(&self) -> PermutationInstance<T, WIDTH> {
238+
PermutationInstance {
239+
vars_count: self.allocator.vars_count(),
240+
public_values: self.allocator.public_vars(),
241+
query_answers: self.constraints().as_ref().to_vec(),
242+
linear_constraints: self.linear_constraints(),
243+
}
244+
}
168245
}
169246

170247
impl<P: Permutation<WIDTH>, const WIDTH: usize> From<P> for PermutationWitnessBuilder<P, WIDTH> {
@@ -209,4 +286,12 @@ impl<P: Permutation<WIDTH>, const WIDTH: usize> PermutationWitnessBuilder<P, WID
209286
pub fn linear_constraints(&self) -> LinearConstraints<P::U, P::U> {
210287
self.linear_constraints.read().clone()
211288
}
289+
290+
#[must_use]
291+
pub fn snapshot(&self) -> PermutationWitness<P::U, WIDTH> {
292+
PermutationWitness {
293+
trace: self.trace().as_ref().to_vec(),
294+
linear_constraints: self.linear_constraints(),
295+
}
296+
}
212297
}

0 commit comments

Comments
 (0)