lurk-lab / bellpepper Goto Github PK
View Code? Open in Web Editor NEWSNARK Circuit library
License: Other
SNARK Circuit library
License: Other
Is there any way that one could import existing circom templates to use as gadgets in bellpepper?
The rust version specified in rust-toolchain.toml
(1.75.0) is out of date with the latest stable (1.76.0).
Check the rust version check workflow for details.
This issue was raised by the workflow at https://github.com/lurk-lab/bellpepper/actions/runs/7879590416/workflow.
WitnessCS::new
pre-populates ONE among the inputs:
bellpepper/crates/bellpepper/src/util_cs/witness_cs.rs
Lines 93 to 100 in cdff470
But WitnessCS::with_capacity
, introduced in #60 does not, which is likely to trip users:
bellpepper/crates/bellpepper/src/util_cs/witness_cs.rs
Lines 66 to 73 in cdff470
We should fix this before any release of bellpepper.
The recent PR #88 introduces a new behavior for Num::add
, favouring None
to Some(v)
as a result in (Some(v), None) | (None, Some(v)
cases.
This new behavior seems to break the current implementation of PoseidonROCircuit::squeeze
in arecibo
. In this method we actually call the SpongeCircuit::absorb
method with the current state elements, that do not have any values at that stage.
In neptune
absorb
will call add_rate_element
with a Num::add
that results on a None
variant, transmitted to the set_element
method that leverages an unwrap
on the value and thus panicking.
I'm not sure about the correct way to fix this, so I'm looking for inputs.
This bug has been found while leveraging the Bn256EngineKZG
engine at PublicParams::setup
both for Nova & Supernova. You can reproduce it on your machine by running this test on ethereum-lc/aggpk
I was also able to reproduce it on the aptos-lc
if patching both belpepper
& bellpepper-core
to dev
.
When folding an R1CS circuit, the cross-term computation
can be computed more efficiently when the circuit takes advantage of linear constraints of the form:
Specifically, this corresponds to a constraint where the linear combinations in the rows
If this is the case, then the
Moreover, the computation of the commitment to
In order to encourage the use of linear constraints, I propose adding the two following methods to the ConstraintSystem trait.
/// Enforce that `A` = 0. The `annotation` function is invoked in testing contexts
/// in order to derive a unique name for the constraint in the current namespace.
fn enforce_eq_zero<A, AR, LA>(&mut self, annotation: A, a: LA)
where
A: FnOnce() -> AR,
AR: Into<String>,
LA: FnOnce(LinearCombination<Scalar>) -> LinearCombination<Scalar> {
self.enforce(annotation, |_| LinearCombination::zero(), |_| LinearCombination::zero(), a);
}
/// Enforce that `A` = `B`. The `annotation` function is invoked in testing contexts
/// in order to derive a unique name for the constraint in the current namespace.
fn enforce_eq<A, AR, LA, LB>(&mut self, annotation: A, a: LA, b: LB)
where
A: FnOnce() -> AR,
AR: Into<String>,
LA: FnOnce(LinearCombination<Scalar>) -> LinearCombination<Scalar>,
LB: FnOnce(LinearCombination<Scalar>) -> LinearCombination<Scalar> {
let zero = a(LinearCombination::zero()) - b(LinearCombination::zero());
self.enforce_eq_zero(annotation, |_| zero);
}
Both of these functions can have default implementation defined in terms of the existing enforce
function definition, avoiding backwards-compatibility issues. However, it allows folding-specific ConstraintSystem
s to choose to handle linear constraints differently.
CI equivalent to bellperson and branch protection equivalent to neptune/lurk-rs should be set up.
Some rought spots publishing 0.2.0 with @porcuquine today:
I'm learning the implementation of bellpepper. The Indexer
in lc
mod caught my attention, it is mainly used to maintain some (k, v)
pairs and is required to be sorted by k
. Its insert_or_update
method will affect the performance of LinearCombination
.
This is the original implementation of Indexer
and its insert_or_update
:
struct Indexer<T> {
/// Stores a list of `T` indexed by the number in the first slot of the tuple.
values: Vec<(usize, T)>,
/// `(index, key)` of the last insertion operation. Used to optimize consecutive operations
last_inserted: Option<(usize, usize)>,
}
I think the function can also be achieved with BTreeMap
, and the insertion of BTreeMap
may be faster than Vector
. Because when inserting in the middle of Vector, all remaining elements need to be moved.
Here is the modified one:
struct Indexer<T> {
values: BTreeMap<usize, T>,
}
But the results show that origin implementation is faster in most cases:
Condition | Vec + pointer | BTreeMap |
---|---|---|
add Fr with order | 853.55 ns | 2.8 us |
add Fr with rev order | 3.9 us | 3.2 us |
add Fr with random order | 1.1 us | 1.4 us |
add lc | 777 ns | 1.4 us |
detail: https://github.com/zkp-learning/bellpepper-bench#readme
I think this is because the time complexity of the original implementation is O(1) when inserting in order, while BTreeMap is O(log n).
For example : #8
There may be reasons for this, as I suspect those PRs do not emit events, so as to not create recursive CI runs.
/cc @samuelburnham
Completely unconstrained allocations are a common source of programmer error, leading to soundness bugs, when writing circuits. This class of error can be completely detected at circuit-shape-synthesis-time.
Bellpepper already inherits (from bellperson/bellman) an error intended to signal this condition:
bellpepper/crates/bellpepper-core/src/constraint_system.rs
Lines 45 to 47 in 542bf03
We should add support to check synthesized constraint systems for this — either at program run-time, or in tests. Ideally, this would be implemented such that there is no cost if the check is never performed.
Since there are legitimate reasons to allocate unconstrained values, we will need to add a new operation (say, AllocatedNum::alloc_unconstrained(…)
), allowing circuit programmers to white-list such allocations. This will greatly improve clarity when this is actually desired, by forcing it to be declared explicitly.
When performing the optional check on a constraint system supporting it, unconstrained allocations that are declared as above should not result in an error. Conversely, declared unconstrained allocations that are in fact constrained should also result in an error. We will need a new error type for this.
The principle suggested here could be taken further, since constrained/unconstrained is a crude binary hammer. Consider that an AllocatedBit
will be 'constrained' at the R1CS level, but may still represent a conceptually unconstrained allocation. Ideally, we could similarly catch such unconstrained (except for the booleanity constraint) AllocatedBit
s — with an equivalent whitelisted-allocation form.
Of course, this could get complicated and expensive. One possibility that might be viable in general is to include metadata during shape synthesis (when opting into a more expensive analysis mode) so that 'partial constraints' of the kind created by AllocatedBit
on construction are recorded and can be ignored when attempting to determine whether a given variable has been fully constrained.
These checks should apply both to inputs and aux variables.
If we want this to be shared by many constraint systems, we might need some refactoring and common traits. In that case, Nova's ShapeCS
, for example, should be considered.
The most obvious implementation approach would involve a set of all variables (or their indices), with variables removed from the set as they are encountered in (qualifying — if/when we implement that nuance) constraints. A bit field might be a reasonably performant way to instantiate the idea.
For example, a circuit with n constraints might have a field initialized to [0xFFu8; (n + 7) / 8]
. Then as each constraint is encountered, the bit at position corresponding to its index is set to 0. If we also explicitly clear the bits corresponding to whitelisted vars, then any remaining non-zero bits correspond to unconstrained allocations which should be reported.
Or maybe that's overkill, and a simpler but heavier data structure will suffice.
As a final note, the existing error (which should be retained for compatibility by consumers who still use Groth16 with bellpepper) doesn't report which var was unconstrained — but it will be much more useful for debugging purposes to include it. So we may need a new error for this, as well as the constrained-but-whitelisted case (which should also report the offending variable).
The rust version specified in rust-toolchain.toml
(1.76.0) is out of date with the latest stable (1.78.0).
Check the rust version check workflow for details.
This issue was raised by the workflow at https://github.com/lurk-lab/bellpepper/actions/runs/8977420345/workflow.
From #55 (comment):
If we are establishing a new set of gadgets, it may be worth taking a moment to consider unification of similar alternate approaches for two reasons:
To that end, let's take a moment to make an active decision. The current function (alloc_num_is_zero
) can be implemented in terms of the (also important) alloc_equal_const
(https://github.com/lurk-lab/lurk-rs/blob/dae0d6c478a83c80e3b3753c29f89e4ecd2a6313/src/circuit/gadgets/constraints.rs#L495) [UPDATE: actually alloc_num_is_zero
operates on a Num — so my points here are not quite germane to this work. The general point stands as something we should sort out as part of a 'base gadgets' project, though.].
Arecibo, for path-dependent reasons associated with its own internal consistency has the slightly-differently-named alloc_num_equals_const
: https://github.com/lurk-lab/arecibo/blob/6a9e8707e868068abf8c6b8da999443c3f3d0895/src/gadgets/utils.rs#L200-L237.
In addition to the slight naming difference, that function has a more concise (arguably simpler — if also less didactic) definition, itself derived from its alloc_num_equals
(https://github.com/lurk-lab/arecibo/blob/6a9e8707e868068abf8c6b8da999443c3f3d0895/src/gadgets/utils.rs#L156-L197), which has a similar relationship to Lurk's equivalent alloc_num_equal
(https://github.com/lurk-lab/lurk-rs/blob/dae0d6c478a83c80e3b3753c29f89e4ecd2a6313/src/circuit/gadgets/constraints.rs#L440-L492C2).
The point of this comment is to suggest we take a moment to actively decide:
lurk-rs
names;nova
/arecibo
names;lurk-rs
implementations;nova
/arecibo
implementations;The reason this is worth considering is that any refactoring of both arecibo
(which implies the hope for an eventual change in nova
) and lurk-rs
will have audit/correctness implications for at least one of those projects. We should seek to minimize global cost and maximize eventual global confidence in correctness with whatever we do here.
I don't think we need to overthink this, just make an active decision. I suggest there are only four reasonable choices, and all are more-or-less acceptable with the right argument:
lurk-rs
names and implementations (status quo of this PR)arecibo
names and implementations (status quo if we had started from the premise of refactoring arecibo)lurk-rs
names and arecibo
implementations (possible lessening of the lower-level crypto's auditing burden as a matter of policy, or in deference to the lowest-level's cryptographic weight).arecibo
names and lurk-rs
implementations (in deference to lurk-rs
and many of the gadgets in question more broadly than just this PR having undergone one round of audit).I would say that the last option probably makes little sense. If the logic for lurk-rs
implementations is strong, we should probably also use lurk-rs
names. This is because lurk-rs
names are probably the better choice anyway, and changes of naming for purpose of gadget standardization should be non-threatening to arecibo
/nova
.
In the end, I think my vote is for lurk-rs
names and either implementation. If we know we will prefer one implementation over the other, it's probably best to initialize this work with that choice so we can follow a policy of not gratuitously changing implementations of gadgets — since this has a substantive effect on any circuit depending on them; making it a deeply breaking change in ways that are not instantly obvious.
cc: @adr1anh @gabriel-barrett @huitseeker
Originally posted by @porcuquine in #55 (comment)
See lurk-lab/neptune#250 (comment)
We want to not update bellpepper-core versions unless absolutely necessary.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.