Skip to content

Commit

Permalink
added back a bignum variant whose modulus parameters are defined at c…
Browse files Browse the repository at this point in the history
…ompile time.

This version is an attempt at the best of both worlds. If your modulus is a witness parameter you can use `runtime_bignum`, else the structs and traits in `lib.nr` can be used.

The methods in `lib.nr` now call the `runtime_bignum.nr` methods when relevant so there is no duplicate algorithm logic
  • Loading branch information
zac-williamson committed Jul 24, 2024
1 parent f0d37e0 commit 8125902
Show file tree
Hide file tree
Showing 8 changed files with 2,447 additions and 1,404 deletions.
126 changes: 99 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,44 +11,65 @@ Multiplication operations for a 2048-bit prime field cost approx. 930 gates.

bignum can evaluate large integer arithmetic by defining a modulus() that is a power of 2.

# Types

bignum operations are evaluated using two structs and a trait: `ParamsTrait`, `BigNum<N, Params>`, `BigNumInstance<N, Params>`

`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?!)

`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`))

The `BigNum` struct represents individual big numbers.

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.

# Usage

Example usage:
Example usage

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

type Fq = BigNum<3, BNParams>;
type FqInst = BigNumInstance<3, BNParams>;

fn example(Fq a, Fq b) -> Fq {
let instance = BN254Instance();
instance.mul(a, b)
fn example_mul(Fq a, Fq b) -> Fq {
a * b
}
```

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).

Unconstrained functions `__mulmod, __addmod, __submod, __divmod, __powmod` can be used to compute witnesses that can then be fed into `BigNumInstance::evaluate_quadratic_expression`.

See `bignum_test.nr` for examples.

Note: `__divmod`, `__powmod` 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)
fn example_ecc_double(Fq x, Fq y) -> (Fq, Fq) {
// Step 1: construct witnesses
// lambda = 3*x*x / 2y
let mut lambda_numerator = x.__mulmod(x);
lambda_numerator = lambda_numerator.__addmod(lambda_numerator.__addmod(lambda_numerator));
let lambda_denominator = y.__addmod(y);
let lambda = lambda_numerator / lambda_denominator;
// x3 = lambda * lambda - x - x
let x3 = lambda.__mulmod(lambda).__submod(x.__addmod(x));
// y3 = lambda * (x - x3) - y
let y3 = lambda.__mulmod(x.__submod(x3)).__submod(y);

// Step 2: constrain witnesses to be correct using minimal number of modular reductions (3)
// 2y * lambda - 3*x*x = 0
BigNum::evaluate_quadratic_expression(
[[lambda]],
[[false]],
[[y,y]],
[[false, false]],
[x,x,x],
[true, true, true]
);
// lambda * lambda - x - x - x3 = 0
BigNum::evaluate_quadratic_expression(
[[lambda]],
[[false]],
[[lambda]],
[[false]],
[x3,x,x],
[true, true, true]
);
// lambda * (x - x3) - y = 0
BigNum::evaluate_quadratic_expression(
[[lambda]],
[[false]],
[[x, x3]],
[[false, true]],
[y],
[true]
);
(x3, y3)
}
```

### `evaluate_quadratic_expression`

Expand Down Expand Up @@ -81,9 +102,58 @@ let rhs_terms = [[c], [f]];
let rhs_flags = [[false], [false]];
let add_terms = [g];
let add_flags = [true];
instance.evaluate_quadratic_expresson(lhs_terms, lhs_flags, rhs_terms, rhs_flags, linear_terms, linear_flags);
BigNum::evaluate_quadratic_expresson(lhs_terms, lhs_flags, rhs_terms, rhs_flags, linear_terms, linear_flags);
```

# BigNum and runtime_bignum::BigNum

BigNum members are represented as arrays of 120-bit limbs. The number of 120-bit limbs required to represent a given BigNum object must be defined at compile-time.

If your field moduli is _also_ known at compile-time, use the `BigNumTrait` definition in `lib.nr`

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`

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.

# runtime_bignum.nr Types

bignum operations are evaluated using two structs and a trait: `ParamsTrait`, `BigNum<N, Params>`, `BigNumInstance<N, Params>`

`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?!)

`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`))

The `BigNum` struct represents individual big numbers.

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.

# Usage (runtime_bignum)

Example usage:

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

type Fq = BigNum<3, BNParams>;
type FqInst = BigNumInstance<3, BNParams>;

fn example(Fq a, Fq b) -> Fq {
let instance = BN254Instance();
instance.mul(a, b)
}
```

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).

Unconstrained functions `__mulmod, __addmod, __submod, __divmod, __powmod` can be used to compute witnesses that can then be fed into `BigNumInstance::evaluate_quadratic_expression`.

See `bignum_test.nr` for examples.

Note: `__divmod`, `__powmod` 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)

### Computing witness values

When computing inputs to `evaluate_quadratic_expresson` , unconstrained functions `__addmod`, `__submod`, `__mulmod`, `__divmod` can be used to compute witness values.
Expand All @@ -107,4 +177,6 @@ For other moduli (e.g. those used in RSA verification), both `modulus` and `redc

`redc_param` is equal to `(1 << (2 * Params::modulus_bits())) / modulus` . This must be computed outside of the circuit and provided either as a private witness or hardcoded constant. (computing it via an unconstrained function would be very expensive until noir witness computation times improve)

`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.

For example derivations see `https://github.com/noir-lang/noir_rsa`
Loading

0 comments on commit 8125902

Please sign in to comment.