Skip to content

Commit 30debfe

Browse files
authored
Merge pull request #3272 from ProvableHQ/blockwide-deployment-limit
Blockwide deployment limits
2 parents cb84da6 + 8bcffd3 commit 30debfe

65 files changed

Lines changed: 1412 additions & 324 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.circleci/config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1325,6 +1325,7 @@ workflows:
13251325
or pipeline.git.branch == "testnet"
13261326
or pipeline.git.branch == "mainnet"
13271327
or pipeline.git.branch == "move_spend_limits"
1328+
or pipeline.git.branch == "blockwide-deployment-limit"
13281329
jobs:
13291330
- check-unused-dependencies # This can be cleaned up before releases
13301331
- check-cargo-semver-checks # This can be cleaned up before releases

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

circuit/environment/src/canary_circuit.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ type Field = <console::CanaryV0 as console::Environment>::Field;
2525
thread_local! {
2626
static VARIABLE_LIMIT: Cell<Option<u64>> = const { Cell::new(None) };
2727
static CONSTRAINT_LIMIT: Cell<Option<u64>> = const { Cell::new(None) };
28+
static NON_ZERO_LIMIT: Cell<Option<(u64, u64, u64)>> = const { Cell::new(None) };
2829
pub(super) static CANARY_CIRCUIT: RefCell<R1CS<Field>> = RefCell::new(R1CS::new());
2930
static IN_WITNESS: Cell<bool> = const { Cell::new(false) };
3031
static ZERO: LinearCombination<Field> = LinearCombination::zero();
@@ -58,6 +59,8 @@ impl Environment for CanaryCircuit {
5859
// Ensure that we do not surpass the variable limit for the circuit.
5960
VARIABLE_LIMIT.with(|variable_limit| {
6061
if let Some(limit) = variable_limit.get() {
62+
// Note: because we check > here, one could theoretically construct a
63+
// circuit with variable_limit + 1 variables.
6164
if Self::num_variables() > limit {
6265
Self::halt(format!("Surpassed the variable limit ({limit})"))
6366
}
@@ -136,6 +139,8 @@ impl Environment for CanaryCircuit {
136139
// Ensure that we do not surpass the constraint limit for the circuit.
137140
CONSTRAINT_LIMIT.with(|constraint_limit| {
138141
if let Some(limit) = constraint_limit.get() {
142+
// Note: because we check > here, one could theoretically construct a
143+
// circuit with constraint_limit + 1 constraints.
139144
if circuit.borrow().num_constraints() > limit {
140145
Self::halt(format!("Surpassed the constraint limit ({limit})"))
141146
}
@@ -160,6 +165,23 @@ impl Environment for CanaryCircuit {
160165
false => {
161166
// Construct the constraint object.
162167
let constraint = Constraint(circuit.borrow().scope(), a, b, c);
168+
169+
// Ensure that we do not surpass the density limit for the circuit.
170+
NON_ZERO_LIMIT.with(|non_zero_limit| {
171+
if let Some((limit_a, limit_b, limit_c)) = non_zero_limit.get() {
172+
let (curr_d_a, curr_d_b, curr_d_c) = circuit.borrow().num_nonzeros();
173+
let (d_a, d_b, d_c) = constraint.num_nonzeros();
174+
if curr_d_a.saturating_add(d_a) > limit_a
175+
|| curr_d_b.saturating_add(d_b) > limit_b
176+
|| curr_d_c.saturating_add(d_c) > limit_c
177+
{
178+
Self::halt(format!(
179+
"Surpassed the circuit density limit (A: {limit_a}, B: {limit_b}, C: {limit_c}). Was ({curr_d_a}, {curr_d_b}, {curr_d_c}) before, tried to add ({d_a}, {d_b}, {d_c})"
180+
))
181+
}
182+
}
183+
});
184+
163185
// Append the constraint.
164186
circuit.borrow_mut().enforce(constraint)
165187
}
@@ -257,6 +279,16 @@ impl Environment for CanaryCircuit {
257279
CONSTRAINT_LIMIT.with(|current_limit| current_limit.replace(limit));
258280
}
259281

282+
/// Returns the density limit for the circuit, if one exists.
283+
fn get_non_zero_limit() -> Option<(u64, u64, u64)> {
284+
NON_ZERO_LIMIT.with(|current_limit| current_limit.get())
285+
}
286+
287+
/// Sets the density limit for the circuit.
288+
fn set_non_zero_limit(limit: Option<(u64, u64, u64)>) {
289+
NON_ZERO_LIMIT.with(|current_limit| current_limit.replace(limit));
290+
}
291+
260292
/// Halts the program from further synthesis, evaluation, and execution in the current environment.
261293
fn halt<S: Into<String>, T>(message: S) -> T {
262294
let error = message.into();
@@ -293,6 +325,8 @@ impl Environment for CanaryCircuit {
293325
Self::set_variable_limit(None);
294326
// Reset the constraint limit.
295327
Self::set_constraint_limit(None);
328+
// Reset the density limit.
329+
Self::set_non_zero_limit(None);
296330
// Eject the R1CS instance.
297331
let r1cs = circuit.replace(R1CS::<<Self as Environment>::BaseField>::new());
298332
// Ensure the circuit is now empty.
@@ -315,6 +349,8 @@ impl Environment for CanaryCircuit {
315349
Self::set_variable_limit(None);
316350
// Reset the constraint limit.
317351
Self::set_constraint_limit(None);
352+
// Reset the density limit.
353+
Self::set_non_zero_limit(None);
318354
// Eject the R1CS instance.
319355
let r1cs = circuit.replace(R1CS::<<Self as Environment>::BaseField>::new());
320356
assert_eq!(0, circuit.borrow().num_constants());
@@ -336,6 +372,8 @@ impl Environment for CanaryCircuit {
336372
Self::set_variable_limit(None);
337373
// Reset the constraint limit.
338374
Self::set_constraint_limit(None);
375+
// Reset the density limit.
376+
Self::set_non_zero_limit(None);
339377
// Reset the circuit.
340378
*circuit.borrow_mut() = R1CS::<<Self as Environment>::BaseField>::new();
341379
assert_eq!(0, circuit.borrow().num_constants());

circuit/environment/src/circuit.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ type Field = <console::MainnetV0 as console::Environment>::Field;
2525
thread_local! {
2626
static VARIABLE_LIMIT: Cell<Option<u64>> = const { Cell::new(None) };
2727
static CONSTRAINT_LIMIT: Cell<Option<u64>> = const { Cell::new(None) };
28+
static NON_ZERO_LIMIT: Cell<Option<(u64, u64, u64)>> = const { Cell::new(None) };
2829
pub(super) static CIRCUIT: RefCell<R1CS<Field>> = RefCell::new(R1CS::new());
2930
static IN_WITNESS: Cell<bool> = const { Cell::new(false) };
3031
static ZERO: LinearCombination<Field> = LinearCombination::zero();
@@ -58,6 +59,8 @@ impl Environment for Circuit {
5859
// Ensure that we do not surpass the variable limit for the circuit.
5960
VARIABLE_LIMIT.with(|variable_limit| {
6061
if let Some(limit) = variable_limit.get() {
62+
// Note: because we check > here, one could theoretically construct a
63+
// circuit with variable_limit + 1 variables.
6164
if Self::num_variables() > limit {
6265
Self::halt(format!("Surpassed the variable limit ({limit})"))
6366
}
@@ -160,6 +163,8 @@ impl Environment for Circuit {
160163
// Ensure that we do not surpass the constraint limit for the circuit.
161164
CONSTRAINT_LIMIT.with(|constraint_limit| {
162165
if let Some(limit) = constraint_limit.get() {
166+
// Note: because we check > here, one could theoretically construct a
167+
// circuit with constraint_limit + 1 constraints.
163168
if circuit.borrow().num_constraints() > limit {
164169
Self::halt(format!("Surpassed the constraint limit ({limit})"))
165170
}
@@ -184,6 +189,23 @@ impl Environment for Circuit {
184189
false => {
185190
// Construct the constraint object.
186191
let constraint = Constraint(circuit.borrow().scope(), a, b, c);
192+
193+
// Ensure that we do not surpass the density limit for the circuit.
194+
NON_ZERO_LIMIT.with(|non_zero_limit| {
195+
if let Some((limit_a, limit_b, limit_c)) = non_zero_limit.get() {
196+
let (curr_d_a, curr_d_b, curr_d_c) = circuit.borrow().num_nonzeros();
197+
let (d_a, d_b, d_c) = constraint.num_nonzeros();
198+
if curr_d_a.saturating_add(d_a) > limit_a
199+
|| curr_d_b.saturating_add(d_b) > limit_b
200+
|| curr_d_c.saturating_add(d_c) > limit_c
201+
{
202+
Self::halt(format!(
203+
"Surpassed the circuit density limit (A: {limit_a}, B: {limit_b}, C: {limit_c}). Was ({curr_d_a}, {curr_d_b}, {curr_d_c}) before, tried to add ({d_a}, {d_b}, {d_c})"
204+
))
205+
}
206+
}
207+
});
208+
187209
// Append the constraint.
188210
circuit.borrow_mut().enforce(constraint)
189211
}
@@ -281,6 +303,16 @@ impl Environment for Circuit {
281303
CONSTRAINT_LIMIT.with(|current_limit| current_limit.replace(limit));
282304
}
283305

306+
/// Returns the density limit for the circuit, if one exists.
307+
fn get_non_zero_limit() -> Option<(u64, u64, u64)> {
308+
NON_ZERO_LIMIT.with(|current_limit| current_limit.get())
309+
}
310+
311+
/// Sets the density limit for the circuit.
312+
fn set_non_zero_limit(limit: Option<(u64, u64, u64)>) {
313+
NON_ZERO_LIMIT.with(|current_limit| current_limit.replace(limit));
314+
}
315+
284316
/// Halts the program from further synthesis, evaluation, and execution in the current environment.
285317
fn halt<S: Into<String>, T>(message: S) -> T {
286318
let error = message.into();
@@ -317,6 +349,8 @@ impl Environment for Circuit {
317349
Self::set_variable_limit(None);
318350
// Reset the constraint limit.
319351
Self::set_constraint_limit(None);
352+
// Reset the density limit.
353+
Self::set_non_zero_limit(None);
320354
// Eject the R1CS instance.
321355
let r1cs = circuit.replace(R1CS::<<Self as Environment>::BaseField>::new());
322356
// Ensure the circuit is now empty.
@@ -339,6 +373,8 @@ impl Environment for Circuit {
339373
Self::set_variable_limit(None);
340374
// Reset the constraint limit.
341375
Self::set_constraint_limit(None);
376+
// Reset the density limit.
377+
Self::set_non_zero_limit(None);
342378
// Eject the R1CS instance.
343379
let r1cs = circuit.replace(R1CS::<<Self as Environment>::BaseField>::new());
344380
assert_eq!(0, circuit.borrow().num_constants());
@@ -360,6 +396,8 @@ impl Environment for Circuit {
360396
Self::set_variable_limit(None);
361397
// Reset the constraint limit.
362398
Self::set_constraint_limit(None);
399+
// Reset the density limit.
400+
Self::set_non_zero_limit(None);
363401
// Reset the circuit.
364402
*circuit.borrow_mut() = R1CS::<<Self as Environment>::BaseField>::new();
365403
assert_eq!(0, circuit.borrow().num_constants());

circuit/environment/src/environment.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,12 @@ pub trait Environment: 'static + Copy + Clone + fmt::Debug + fmt::Display + Eq +
174174
/// Sets the constraint limit for the circuit.
175175
fn set_constraint_limit(limit: Option<u64>);
176176

177+
/// Returns the density limit for the circuit, if one exists.
178+
fn get_non_zero_limit() -> Option<(u64, u64, u64)>;
179+
180+
/// Sets the density limit for the circuit.
181+
fn set_non_zero_limit(limit: Option<(u64, u64, u64)>);
182+
177183
/// Halts the program from further synthesis, evaluation, and execution in the current environment.
178184
fn halt<S: Into<String>, T>(message: S) -> T {
179185
<Self::Network as console::Environment>::halt(message)

circuit/environment/src/helpers/linear_combination.rs

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,11 +137,35 @@ impl<F: PrimeField> LinearCombination<F> {
137137
}
138138

139139
/// Returns the number of nonzeros in the linear combination.
140+
// This now combines the Public(0) variable with the linear combination's constant, as was
141+
// always done by generate_constraints at the end of circuit synthesis (and therefore, as
142+
// exposed in circuit verifying keys)
140143
pub(super) fn num_nonzeros(&self) -> u64 {
141-
// Increment by one if the constant is nonzero.
142-
match self.constant.is_zero() {
143-
true => self.terms.len() as u64,
144-
false => (self.terms.len() as u64).saturating_add(1),
144+
let n_terms = self.terms.len() as u64;
145+
146+
// We operate depending on whether the Public(0) variable appears in the linear combination.
147+
if let Some(constant_as_var_coeff) = self
148+
.terms
149+
.iter()
150+
.find_map(|(var, coeff)| if var.index() == 0 && var.is_public() { Some(*coeff) } else { None })
151+
{
152+
if constant_as_var_coeff + self.constant == F::zero() {
153+
// The the Public(0) variable (counted in n_terms) and self.constant will cancel out
154+
// when added together. Note this subtraction is safe as n_terms contains Public(0).
155+
n_terms - 1
156+
} else {
157+
// Public(0) + self.constant != 0 will merge into a single non-zero entry, already
158+
// counted in n_terms.
159+
n_terms
160+
}
161+
} else if self.constant.is_zero() {
162+
// No term for the Public(0) variable and no constant in the final linear
163+
// combination.
164+
n_terms
165+
} else {
166+
// No term for the Public(0) variable, but the constant will appear in the final
167+
// linear combination.
168+
n_terms + 1
145169
}
146170
}
147171

circuit/environment/src/testnet_circuit.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ type Field = <console::TestnetV0 as console::Environment>::Field;
2525
thread_local! {
2626
static VARIABLE_LIMIT: Cell<Option<u64>> = const { Cell::new(None) };
2727
static CONSTRAINT_LIMIT: Cell<Option<u64>> = const { Cell::new(None) };
28+
static NON_ZERO_LIMIT: Cell<Option<(u64, u64, u64)>> = const { Cell::new(None) };
2829
pub(super) static TESTNET_CIRCUIT: RefCell<R1CS<Field>> = RefCell::new(R1CS::new());
2930
static IN_WITNESS: Cell<bool> = const { Cell::new(false) };
3031
static ZERO: LinearCombination<Field> = LinearCombination::zero();
@@ -58,6 +59,8 @@ impl Environment for TestnetCircuit {
5859
// Ensure that we do not surpass the variable limit for the circuit.
5960
VARIABLE_LIMIT.with(|variable_limit| {
6061
if let Some(limit) = variable_limit.get() {
62+
// Note: because we check > here, one could theoretically construct a
63+
// circuit with variable_limit + 1 variables.
6164
if Self::num_variables() > limit {
6265
Self::halt(format!("Surpassed the variable limit ({limit})"))
6366
}
@@ -136,6 +139,8 @@ impl Environment for TestnetCircuit {
136139
// Ensure that we do not surpass the constraint limit for the circuit.
137140
CONSTRAINT_LIMIT.with(|constraint_limit| {
138141
if let Some(limit) = constraint_limit.get() {
142+
// Note: because we check > here, one could theoretically construct a
143+
// circuit with constraint_limit + 1 constraints.
139144
if circuit.borrow().num_constraints() > limit {
140145
Self::halt(format!("Surpassed the constraint limit ({limit})"))
141146
}
@@ -160,6 +165,23 @@ impl Environment for TestnetCircuit {
160165
false => {
161166
// Construct the constraint object.
162167
let constraint = Constraint(circuit.borrow().scope(), a, b, c);
168+
169+
// Ensure that we do not surpass the density limit for the circuit.
170+
NON_ZERO_LIMIT.with(|non_zero_limit| {
171+
if let Some((limit_a, limit_b, limit_c)) = non_zero_limit.get() {
172+
let (curr_d_a, curr_d_b, curr_d_c) = circuit.borrow().num_nonzeros();
173+
let (d_a, d_b, d_c) = constraint.num_nonzeros();
174+
if curr_d_a.saturating_add(d_a) > limit_a
175+
|| curr_d_b.saturating_add(d_b) > limit_b
176+
|| curr_d_c.saturating_add(d_c) > limit_c
177+
{
178+
Self::halt(format!(
179+
"Surpassed the circuit density limit (A: {limit_a}, B: {limit_b}, C: {limit_c}). Was ({curr_d_a}, {curr_d_b}, {curr_d_c}) before, tried to add ({d_a}, {d_b}, {d_c})"
180+
))
181+
}
182+
}
183+
});
184+
163185
// Append the constraint.
164186
circuit.borrow_mut().enforce(constraint)
165187
}
@@ -257,6 +279,16 @@ impl Environment for TestnetCircuit {
257279
CONSTRAINT_LIMIT.with(|current_limit| current_limit.replace(limit));
258280
}
259281

282+
/// Returns the density limit for the circuit, if one exists.
283+
fn get_non_zero_limit() -> Option<(u64, u64, u64)> {
284+
NON_ZERO_LIMIT.with(|current_limit| current_limit.get())
285+
}
286+
287+
/// Sets the density limit for the circuit.
288+
fn set_non_zero_limit(limit: Option<(u64, u64, u64)>) {
289+
NON_ZERO_LIMIT.with(|current_limit| current_limit.replace(limit));
290+
}
291+
260292
/// Halts the program from further synthesis, evaluation, and execution in the current environment.
261293
fn halt<S: Into<String>, T>(message: S) -> T {
262294
let error = message.into();
@@ -293,6 +325,8 @@ impl Environment for TestnetCircuit {
293325
Self::set_variable_limit(None);
294326
// Reset the constraint limit.
295327
Self::set_constraint_limit(None);
328+
// Reset the density limit.
329+
Self::set_non_zero_limit(None);
296330
// Eject the R1CS instance.
297331
let r1cs = circuit.replace(R1CS::<<Self as Environment>::BaseField>::new());
298332
// Ensure the circuit is now empty.
@@ -315,6 +349,8 @@ impl Environment for TestnetCircuit {
315349
Self::set_variable_limit(None);
316350
// Reset the constraint limit.
317351
Self::set_constraint_limit(None);
352+
// Reset the density limit.
353+
Self::set_non_zero_limit(None);
318354
// Eject the R1CS instance.
319355
let r1cs = circuit.replace(R1CS::<<Self as Environment>::BaseField>::new());
320356
assert_eq!(0, circuit.borrow().num_constants());
@@ -336,6 +372,8 @@ impl Environment for TestnetCircuit {
336372
Self::set_variable_limit(None);
337373
// Reset the constraint limit.
338374
Self::set_constraint_limit(None);
375+
// Reset the density limit.
376+
Self::set_non_zero_limit(None);
339377
// Reset the circuit.
340378
*circuit.borrow_mut() = R1CS::<<Self as Environment>::BaseField>::new();
341379
assert_eq!(0, circuit.borrow().num_constants());

circuit/network/src/canary_v0.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,16 @@ impl Environment for AleoCanaryV0 {
523523
E::set_constraint_limit(limit)
524524
}
525525

526+
/// Returns the density limit for the circuit, if one exists.
527+
fn get_non_zero_limit() -> Option<(u64, u64, u64)> {
528+
E::get_non_zero_limit()
529+
}
530+
531+
/// Sets the density limit for the circuit.
532+
fn set_non_zero_limit(limit: Option<(u64, u64, u64)>) {
533+
E::set_non_zero_limit(limit)
534+
}
535+
526536
/// Halts the program from further synthesis, evaluation, and execution in the current environment.
527537
fn halt<S: Into<String>, T>(message: S) -> T {
528538
E::halt(message)

0 commit comments

Comments
 (0)