-
Notifications
You must be signed in to change notification settings - Fork 241
Add gadget extensions #428
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
This commit adds several utility functions and methods for circuit gadgets that are useful for building advanced applications like MicroNova: ecc.rs - AllocatedPoint: - alloc_constant: Allocate a point with constant x,y coords - absorb_in_ro: Absorb point into random oracle circuit (handles infinity) - enforce_equal: Enforce equality constraints between points ecc.rs - AllocatedPointNonInfinity: - alloc: Allocate a new point from coordinates - conditionally_select2: Conditional select using AllocatedNum - enforce_equal: Enforce equality constraints utils.rs: - alloc_constant: Allocate constant value in circuit - base_as_bigint: Convert base field element to BigInt - le_num_to_num: Convert little-endian AllocatedNum bits to number - fingerprint: Compute running sum for fingerprinting values nonnative/bignat.rs - BigNat: - as_limb_values: Get copy of limb values - fold_bn: Fold two BigNats: self + r * other mod modulus - inputize: Make limbs into public inputs - decompose_allocated: Decompose to allocated bit vector - sub_mod: Subtraction modulo nonnative/util.rs: - get_base_modulus: Get base field modulus as BigInt - absorb_bignat_in_ro: Absorb BigNat into RO circuit - absorb_bignat_in_ro_native: Native RO absorption for scalar - fingerprint_bignat: Fingerprint a BigNat's limbs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This pull request adds gadget extensions to the nova-snark library, introducing utility functions for constraint systems, BigNat operations, and elliptic curve point manipulations. The changes support version 0.45.0.
- Adds utility functions for allocating constants, fingerprinting values, and converting field elements
- Extends BigNat with folding, decomposition, subtraction modulo, and public input operations
- Adds methods for elliptic curve points including constant allocation, equality enforcement, and random oracle absorption
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/gadgets/utils.rs | Adds alloc_constant, base_as_bigint, le_num_to_num, and fingerprint utility functions for circuit constraints |
| src/gadgets/nonnative/util.rs | Adds BigNat absorption functions for random oracles and fingerprinting, plus base modulus getter |
| src/gadgets/nonnative/bignat.rs | Extends BigNat with fold_bn, inputize, decompose_allocated, sub_mod, and as_limb_values methods |
| src/gadgets/ecc.rs | Adds alloc_constant, absorb_in_ro, and enforce_equal methods for AllocatedPoint and AllocatedPointNonInfinity |
| Cargo.toml | Version bump from 0.44.0 to 0.45.0 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /// Make the limbs of this BigNat into public inputs. | ||
| pub fn inputize<CS: ConstraintSystem<Scalar>>(&self, mut cs: CS) -> Result<(), SynthesisError> { | ||
| for (i, l) in self.limbs.iter().enumerate() { | ||
| let mut c = cs.namespace(|| format!("limb {i}")); |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The constraint on line 703 enforces 0 * 0 = v - l, which means v - l must be zero. However, the constraint is written in an unusual way. A more standard and clearer form would be to use cs.enforce(|| "eq", |lc| lc + v - l, |lc| lc + CS::one(), |lc| lc); to enforce (v - l) * 1 = 0.
| // comb = limb[0] + limb[1] * 2^limb_width | ||
| let comb: AllocatedNum<E::Base> = AllocatedNum::alloc( | ||
| cs.namespace(|| format!("combine two limbs into one scalar {i}")), | ||
| || Ok(*w[0].get_value().get()? + *w[1].get_value().get()? * pow_2_limb_width), | ||
| )?; | ||
|
|
||
| // check that comb = limb[0] + limb[1] * 2^limb_width | ||
| cs.enforce( | ||
| || format!("check combine two limbs into one scalar {i}"), | ||
| |lc| lc + w[0].get_variable() + (pow_2_limb_width, w[1].get_variable()), | ||
| |lc| lc + CS::one(), | ||
| |lc| lc + comb.get_variable(), | ||
| ); |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function uses chunks(2) on limbs without checking if the number of limbs is even. If the BigNat has an odd number of limbs, the last chunk will only have one element, causing a panic when accessing w[1] on line 283. Consider adding a check or handling odd-sized chunks differently.
| // comb = limb[0] + limb[1] * 2^limb_width | |
| let comb: AllocatedNum<E::Base> = AllocatedNum::alloc( | |
| cs.namespace(|| format!("combine two limbs into one scalar {i}")), | |
| || Ok(*w[0].get_value().get()? + *w[1].get_value().get()? * pow_2_limb_width), | |
| )?; | |
| // check that comb = limb[0] + limb[1] * 2^limb_width | |
| cs.enforce( | |
| || format!("check combine two limbs into one scalar {i}"), | |
| |lc| lc + w[0].get_variable() + (pow_2_limb_width, w[1].get_variable()), | |
| |lc| lc + CS::one(), | |
| |lc| lc + comb.get_variable(), | |
| ); | |
| // Handle both full (2-limb) chunks and a possible final single-limb chunk. | |
| let comb: AllocatedNum<E::Base> = if w.len() == 2 { | |
| // comb = limb[0] + limb[1] * 2^limb_width | |
| AllocatedNum::alloc( | |
| cs.namespace(|| format!("combine two limbs into one scalar {i}")), | |
| || Ok(*w[0].get_value().get()? + *w[1].get_value().get()? * pow_2_limb_width), | |
| )? | |
| } else { | |
| // Only one limb in this chunk; treat comb as just limb[0]. | |
| AllocatedNum::alloc( | |
| cs.namespace(|| format!("pass-through single limb {i}")), | |
| || Ok(*w[0].get_value().get()?), | |
| )? | |
| }; | |
| if w.len() == 2 { | |
| // check that comb = limb[0] + limb[1] * 2^limb_width | |
| cs.enforce( | |
| || format!("check combine two limbs into one scalar {i}"), | |
| |lc| lc + w[0].get_variable() + (pow_2_limb_width, w[1].get_variable()), | |
| |lc| lc + CS::one(), | |
| |lc| lc + comb.get_variable(), | |
| ); | |
| } else { | |
| // check that comb = limb[0] for a single-limb chunk | |
| cs.enforce( | |
| || format!("check pass-through single limb {i}"), | |
| |lc| lc + w[0].get_variable(), | |
| |lc| lc + CS::one(), | |
| |lc| lc + comb.get_variable(), | |
| ); | |
| } |
| // comb = limb[0] + limb[1] * 2^limb_width | ||
| let comb = AllocatedNum::alloc( | ||
| cs.namespace(|| format!("combine two limbs into one scalar {i}")), | ||
| || Ok(*w[0].get_value().get()? + *w[1].get_value().get()? * pow_2_limb_width), | ||
| )?; | ||
|
|
||
| // check that comb = limb[0] + limb[1] * 2^limb_width | ||
| cs.enforce( | ||
| || format!("check combine two limbs into one scalar {i}"), | ||
| |lc| lc + w[0].get_variable() + (pow_2_limb_width, w[1].get_variable()), | ||
| |lc| lc + CS::one(), | ||
| |lc| lc + comb.get_variable(), | ||
| ); | ||
|
|
||
| Ok(comb) |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function uses chunks(2) on limbs without checking if the number of limbs is even. If the BigNat has an odd number of limbs, the last chunk will only have one element, causing a panic when accessing w[1] on line 388. Consider adding a check or handling odd-sized chunks differently.
| // comb = limb[0] + limb[1] * 2^limb_width | |
| let comb = AllocatedNum::alloc( | |
| cs.namespace(|| format!("combine two limbs into one scalar {i}")), | |
| || Ok(*w[0].get_value().get()? + *w[1].get_value().get()? * pow_2_limb_width), | |
| )?; | |
| // check that comb = limb[0] + limb[1] * 2^limb_width | |
| cs.enforce( | |
| || format!("check combine two limbs into one scalar {i}"), | |
| |lc| lc + w[0].get_variable() + (pow_2_limb_width, w[1].get_variable()), | |
| |lc| lc + CS::one(), | |
| |lc| lc + comb.get_variable(), | |
| ); | |
| Ok(comb) | |
| match w { | |
| // comb = limb[0] + limb[1] * 2^limb_width | |
| [ref limb0, ref limb1] => { | |
| let comb = AllocatedNum::alloc( | |
| cs.namespace(|| format!("combine two limbs into one scalar {i}")), | |
| || Ok(*limb0.get_value().get()? + *limb1.get_value().get()? * pow_2_limb_width), | |
| )?; | |
| // check that comb = limb[0] + limb[1] * 2^limb_width | |
| cs.enforce( | |
| || format!("check combine two limbs into one scalar {i}"), | |
| |lc| lc + limb0.get_variable() + (pow_2_limb_width, limb1.get_variable()), | |
| |lc| lc + CS::one(), | |
| |lc| lc + comb.get_variable(), | |
| ); | |
| Ok(comb) | |
| } | |
| // Handle an odd number of limbs: the last chunk has a single limb. | |
| [ref limb0] => { | |
| let comb = AllocatedNum::alloc( | |
| cs.namespace(|| format!("combine remaining single limb into one scalar {i}")), | |
| || Ok(*limb0.get_value().get()?), | |
| )?; | |
| // Enforce comb = limb[0] | |
| cs.enforce( | |
| || format!("check combine remaining single limb into one scalar {i}"), | |
| |lc| lc + limb0.get_variable(), | |
| |lc| lc + CS::one(), | |
| |lc| lc + comb.get_variable(), | |
| ); | |
| Ok(comb) | |
| } | |
| _ => unreachable!(), | |
| } |
| })?; | ||
|
|
||
| cs.enforce( | ||
| || format!("eq scalar limb_{i}"), |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The panic message "We should have never reached there" contains a grammatical error. It should be "We should have never reached here" or "This should never be reached".
| let limbs_packed: Vec<AllocatedNum<E::Base>> = limbs | ||
| .chunks(2) | ||
| .enumerate() | ||
| .map(|(i, w)| { | ||
| // comb = limb[0] + limb[1] * 2^limb_width | ||
| let comb: AllocatedNum<E::Base> = AllocatedNum::alloc( | ||
| cs.namespace(|| format!("combine two limbs into one scalar {i}")), | ||
| || Ok(*w[0].get_value().get()? + *w[1].get_value().get()? * pow_2_limb_width), | ||
| )?; | ||
|
|
||
| // check that comb = limb[0] + limb[1] * 2^limb_width | ||
| cs.enforce( | ||
| || format!("check combine two limbs into one scalar {i}"), | ||
| |lc| lc + w[0].get_variable() + (pow_2_limb_width, w[1].get_variable()), | ||
| |lc| lc + CS::one(), | ||
| |lc| lc + comb.get_variable(), | ||
| ); | ||
|
|
||
| Ok(comb) | ||
| }) | ||
| .collect::<Result<Vec<AllocatedNum<E::Base>>, SynthesisError>>()?; |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is significant code duplication between absorb_bignat_in_ro and absorb_bignat_in_ro_scalar. Both functions perform the same limb-packing logic with chunks(2). Consider extracting the common packing logic into a helper function to reduce duplication and improve maintainability.
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
No description provided.