Skip to content

Commit

Permalink
feat!: refactor library architecture (#36)
Browse files Browse the repository at this point in the history
* refactor

* silence warnings

* silence more warnings

* rebase & silence warnings

* fmt

* Update src/runtime_bignum.nr

* Update src/utils/map.nr

* reduce constraint counts

* update in line w/ Zac's changes, incl macro tests

* fix merge mistake

* add sqrt code from zac

* fix sqrt test

* fmt

* fmt

* riddle with MOD_BITS

* tonelli

* option

* tidy

* warnings

* warnings

* fmt

* latest nightly

* fix to work with latest aztec-packages version of nargo

* README

* tidy filing

* version bump

* fmt

* use nargo 0.36 for ci

* Update test.yml

---------

Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com>
Co-authored-by: sirasistant <sirasistant@gmail.com>
Co-authored-by: kashbrti <kashbrti@gmail.com>
  • Loading branch information
4 people authored Oct 25, 2024
1 parent 20e03b0 commit 4fa65f6
Show file tree
Hide file tree
Showing 54 changed files with 6,195 additions and 4,254 deletions.
28 changes: 26 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
strategy:
fail-fast: false
matrix:
toolchain: [nightly, 0.35.0]
toolchain: [nightly, 0.36.0]
steps:
- name: Checkout sources
uses: actions/checkout@v4
Expand All @@ -38,7 +38,31 @@ jobs:
- name: Install Nargo
uses: noir-lang/noirup@v0.1.3
with:
toolchain: 0.35.0
toolchain: 0.36.0

- name: Run formatter
run: nargo fmt --check


# This is a job which depends on all test jobs and reports the overall status.
# This allows us to add/remove test jobs without having to update the required workflows.
tests-end:
name: Noir End
runs-on: ubuntu-latest
# We want this job to always run (even if the dependant jobs fail) as we want this job to fail rather than skipping.
if: ${{ always() }}
needs:
- test
- format

steps:
- name: Report overall success
run: |
if [[ $FAIL == true ]]; then
exit 1
else
exit 0
fi
env:
# We treat any cancelled, skipped or failing jobs as a failure for the workflow as a whole.
FAIL: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped') }}
2 changes: 1 addition & 1 deletion Nargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
name = "bignum"
type = "lib"
authors = [""]
compiler_version = ">=0.35.0"
compiler_version = ">=0.36.0"

[dependencies]
90 changes: 47 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ TODO

## Dependencies

- Noir ≥v0.32.0
- Barretenberg ≥v0.46.1
- Noir ≥v0.36.0
- Barretenberg ≥v0.56.1

Refer to [Noir's docs](https://noir-lang.org/docs/getting_started/installation/) and [Barretenberg's docs](https://github.com/AztecProtocol/aztec-packages/blob/master/barretenberg/cpp/src/barretenberg/bb/readme.md#installation) for installation steps.

Expand Down Expand Up @@ -50,12 +50,13 @@ If your field moduli is _also_ known at compile-time, use the `BigNumTrait` defi
Big numbers are instantiated with the BigNum struct:

```rust
struct BigNum<let N: u64, Params> {
struct BigNum<let N: u32, let MOD_BITS: u32, Params> {
limbs: [Field; N]
}
```

- `N` is the number of `Field` limbs together holding the value of the big number
- `MOD_BITS` is the bit-length of the modulus of the big number.
- `Params` is the parameters associated with the big number; refer to sections below for presets and customizations

### Usage
Expand All @@ -68,7 +69,7 @@ A simple 1 + 2 = 3 check in 256-bit unsigned integers:
use dep::bignum::fields::U256::U256Params;
use dep::bignum::BigNum;

type U256 = BigNum<3, U256Params>;
type U256 = BigNum<3, 257, U256Params>;

fn main() {
let one: U256 = BigNum::from_array([1, 0, 0]);
Expand Down Expand Up @@ -96,7 +97,7 @@ e.g.
use dep::bignum::fields::U256::U256Params;
use dep::bignum::BigNum;

type U256 = BigNum<3, U256Params>;
type U256 = BigNum<3, 257, U256Params>;

fn foo(x: U256, y: U256) -> U256 {
x.udiv(y)
Expand All @@ -105,70 +106,74 @@ fn foo(x: U256, y: U256) -> U256 {

##### Fields

`BigNum::fields` contains `BigNumInstance` constructors for common fields.
`BigNum::fields` contains `BigNumParams` for common fields.

Feature requests and/or pull requests welcome for missing fields you need.

TODO: Document existing field presets (e.g. bls, ed25519, secp256k1)

## `runtime_bignum`
## `RuntimeBigNum`

If your field moduli is _not_ known at compile-time (e.g. RSA verification), use the traits and structs defined in `runtime_bignum`: `runtime_bignum::BigNumTrait` and `runtime_bignum::BigNumInstanceTrait`
If your field moduli is _not_ known at compile-time (e.g. RSA verification), use the `RuntimeBigNum` struct defined in `runtime_bignum.nr`: `runtime_bignum::RuntimeBigNum`.

A `runtime_bignum::BigNumInstance` wraps the bignum modulus (as well as a derived parameter used internally to perform Barret reductions). A `BigNumInstance` object is required to evaluate most bignum operations.
```rust
use dep::bignum::fields::bn254Fq::BN254_Fq_Params;

### Types
// Notice how we don't provide the params here, because we're pretending they're
// not known at compile-time, for illustration purposes.
type My_RBN = RuntimeBigNum<3, 254>;

bignum operations are evaluated using two structs and a trait: `ParamsTrait`, `BigNum<N, Params>`, `BigNumInstance<N, Params>`
fn main() {
let params = BN254_Fq_Params::get_params(); // or some other params known at runtime.

`ParamsTrait` defines the compile-time properties of a BigNum instance: the number of modulus bits and the Barret reduction parameter `k` (TODO: these two values should be the same?!)
// Notice how we feed the params in, because we're pretending they're not
// known at compile-time.
let one: My_RBN = RuntimeBigNum::from_array(params, [1, 0, 0]);
let two: My_RBN = RuntimeBigNum::from_array(params, [2, 0, 0]);
let three: My_RBN = RuntimeBigNum::from_array(params, [3, 0, 0]);

`BigNumInstance` is a generator type that is used to create `BigNum` objects and evaluate operations on `BigNum` objects. It wraps BigNum parameters that may not be known at compile time (the `modulus` and a reduction parameter required for Barret reductions (`redc_param`))
assert((one + two) == three);
}
```

The `BigNum` struct represents individual big numbers.
### Types

BigNumInstance parameters (`modulus`, `redc_param`) can be provided at runtime via witnesses (e.g. RSA verification). The `redc_param` is only used in unconstrained functions and does not need to be derived from `modulus` in-circuit.
User-facing structs:

### Usage
`BigNum`: big numbers whose parameters are all known at compile-time.

#### Example
`RuntimeBigNum`: big numbers whose parameters are only known at runtime. (Note: the number of bits of the modulus of the bignum must be known at compile-time).

```rust
use crate::bignum::fields::bn254Fq{BNParams, BN254INSTANCE};
use crate::bignum::runtime_bignum::BigNumInstance;
use crate::bignum::BigNum;
If creating custom bignum params:

type Fq = BigNum<3, BNParams>;
type FqInst = BigNumInstance<3, BNParams>;
`BigNumParams` is needed, to declare your params. These parameters (`modulus`, `redc_param`) can be provided at runtime via witnesses (e.g. RSA verification). The `redc_param` is only used in unconstrained functions and does not need to be derived from `modulus` in-circuit.

fn example(Fq a, Fq b) -> Fq {
let instance: FqInst = BN254INSTANCE;
instance.mul(a, b)
}
```
`BigNumParamsGetter` is a convenient wrapper around params, which is needed if declaring a new type of `BigNum`.

#### Methods

##### Arithmetics

Basic expressions can be evaluated using `BigNumInstance::add, BigNumInstance::sub, BigNumInstance::mul`. However, when evaluating relations (up to degree 2) that are more complex than single operations, the function `BigNumInstance::evaluate_quadratic_expression` is more efficient (due to needing only a single modular reduction).
Basic expressions can be evaluated using the `BigNum` and `RuntimeBigNum` operators `+`,`-`,`*`,`/`. However, when evaluating relations (up to degree 2) that are more complex than single operations, the static methods `BigNum::evaluate_quadratic_expression` or `RuntimeBigNum::evaluate_quadratic_expression` are much more efficient (due to needing only a single modular reduction).

##### Unconstrained arithmetics

Unconstrained functions `__mul, __add, __sub, __div, __pow` can be used to compute witnesses that can then be fed into `BigNumInstance::evaluate_quadratic_expression`.
Unconstrained functions `__mul, __add, __sub, __div, __pow` etc. can be used to compute witnesses that can then be fed into `BigNumInstance::evaluate_quadratic_expression`.

> **Note:** `__div`, `__pow` and `div` are expensive due to requiring modular exponentiations during witness computation. It is worth modifying witness generation algorithms to minimize the number of modular exponentiations required. (for example, using batch inverses)
> **Note:** `__div`, `__pow` and `div` are expensive due to requiring modular exponentiations during witness computation. It is worth modifying witness generation algorithms to minimize the number of modular exponentiations required. (for example, using batch inverses).
e.g. if we wanted to compute `(a + b) * c + (d - e) * f = g` by evaluating the above example, `g` can be derived via:

```rust
let bn: BigNumInstance<3, BNParams> = BNInstance();
let t0 = bn.__mul(bn.__add(a, b), c);
let t1 = bn.__mul(bn.__add(d, bn.__neg(e)), f);
let a: BigNumInstance<3, 254, BN254_Fq_Params> = BigNum::new();
let t0 = c.__mul(a.__add(b));
let t1 = f.__mul(d.__sub(e));
let g = bn.__add(t0, t1);
```

See `bignum_test.nr` for more examples.
then the values can be arranged and fed-into `evaluate_quadratic_expression`.

See `bignum_test.nr` and `runtime_bignum_test.nr` for more examples.

##### `evaluate_quadratic_expression`

Expand Down Expand Up @@ -206,9 +211,9 @@ BigNum::evaluate_quadratic_expresson(lhs_terms, lhs_flags, rhs_terms, rhs_flags,

##### TODO: Document other available methods

#### Deriving BigNumInstance parameters: `modulus`, `redc_param`
#### Deriving BigNumParams parameters: `modulus`, `redc_param`

For common fields, BigNumInstance parameters can be pulled from the presets in `BigNum::fields`.
For common fields, BigNumParams parameters can be pulled from the presets in `bignum/fields/`.

For other moduli (e.g. those used in RSA verification), both `modulus` and `redc_param` must be computed and formatted according to the following speficiations:

Expand All @@ -218,17 +223,16 @@ For other moduli (e.g. those used in RSA verification), both `modulus` and `redc

`double_modulus` is derived via the method `compute_double_modulus` in `runtime_bignum.nr`. If you want to provide this value as a compile-time constant (see `fields/bn254Fq.nr` for an example), follow the algorithm `compute_double_modulus` as this parameter is _not_ structly 2 \* modulus. Each limb except the most significant limb borrows 2^120 from the next most significant limb. This ensure that when performing limb subtractions `double_modulus.limbs[i] - x.limbs[i]`, we know that the result will not underflow.

BigNumInstance parameters can be derived from a known modulus using the rust crate `noir-bignum-paramgen` (https://crates.io/crates/noir-bignum-paramgen)
BigNumParams parameters can be derived from a known modulus using the rust crate `noir-bignum-paramgen` (https://crates.io/crates/noir-bignum-paramgen)

## Additional usage examples

```rust
use crate::bignum::fields::bn254Fq::BNParams;
use crate::bignum::fields::BN254Instance;
use crate::bignum::BigNum;
use crate::bignum::runtime_bignum::BigNumInstance;
use dep::bignum::fields::bn254Fq::BN254_Fq_Params;

use dep::bignum::BigNum;

type Fq = BigNum<3, BNParams>;
type Fq = BigNum<3, 254, BN254_Fq_Params>;

fn example_mul(Fq a, Fq b) -> Fq {
a * b
Expand Down
Loading

0 comments on commit 4fa65f6

Please sign in to comment.