diff --git a/README.md b/README.md index efc32af1..4d0ce801 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,25 @@ # noir-bignum -An optimized big number library for Noir. +An optimized big number library for Noir. Evaluate modular arithmetic for large integers of any length. -noir-bignum evaluates modular arithmetic for large integers of any length. +This library predefines parameters for common moduli, but it also allows for the creation of custom parameter sets, either at compile-time or at runtime. -BigNum instances are parametrised by a struct that satisfies BigNumParamsTrait. +## Quickstart -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. - -## Benchmarks - -TODO - -## Dependencies +### Prerequisites - Noir ≥v0.32.0 - Barretenberg ≥v0.46.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. -## Installation +### Add dependency In your _Nargo.toml_ file, add the version of this library you would like to install under dependency: ``` [dependencies] -bignum = { tag = "v0.2.2", git = "https://github.com/noir-lang/noir-bignum" } +bignum = { tag = "v0.3.0", git = "https://github.com/noir-lang/noir-bignum" } ``` ### Import @@ -38,203 +30,412 @@ Add imports at the top of your Noir code, for example: use dep::bignum::fields::U256::U256Params; use dep::bignum::BigNum; ``` +### Addition in U256 -## `bignum` +A simple 1 + 2 = 3 check in 256-bit unsigned integers. Note that for performing multiple arithmetic operations up to degree 2 it is recommended to use `evaluate_quadratic_expression` (see explanation below). -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. +```rust +use dep::bignum::fields::U256::U256Params; +use dep::bignum::BigNum; -If your field moduli is _also_ known at compile-time, use the `BigNumTrait` definition in `lib.nr` +type U256 = BigNum<3, U256Params>; + +fn main() { + let one: U256 = BigNum::from_array([1, 0, 0]); + let two: U256 = BigNum::from_array([2, 0, 0]); + let three: U256 = BigNum::from_array([3, 0, 0]); + assert((one + two) == three); +} +``` -### BigNum struct +## Concepts -Big numbers are instantiated with the BigNum struct: +### BigNum definition + +A BigNum is a number modulo `modulus` and is represented as an array of 120-bit limbs in little endian format. ```rust struct BigNum { limbs: [Field; N] } ``` +- `N` is the number of limbs needed to represent the number. Each limb is a `Field`, which contains max 120 bits (a field has 254 bits) +- `Params` is the parameters associated with the big number. The `modulus` is defined here. -- `N` is the number of `Field` limbs together holding the value of the big number -- `Params` is the parameters associated with the big number; refer to sections below for presets and customizations +This library includes predefined parameter sets. Refer to below for preset info and how to customize the `Params` correctly. -### Usage +The actual value of a BigNum can be calculated by multiplying each limb by an increasing power of $2^{120}$. For example `[1,20,300]` represents $1 \cdot 2^{120\cdot0} + 20 \cdot 2^{120 \cdot 1} + 300 \cdot 2^{120 \cdot 2}$. We say that the BigNum is represented in radix- $2^{120}$. -#### Example +NOTE: `N` must be defined at compile-time. The `modulus` can be defined at compile-time or runtime. See further explanation below. -A simple 1 + 2 = 3 check in 256-bit unsigned integers: +### BigNum methods -```rust -use dep::bignum::fields::U256::U256Params; -use dep::bignum::BigNum; +The library offers all standard modular arithmetic operations, constrained and unconstrained. When evaluating more than 1 arithmetic operations, it is recommended to perform unconstrained arithmetic operations and then constrain using `evaluate_quadratic_expression`. See further explanation below. -type U256 = BigNum<3, U256Params>; +Additionally there are some convenient methods to manipulate a BigNum or make assertions about it. -fn main() { - let one: U256 = BigNum::from_array([1, 0, 0]); - let two: U256 = BigNum::from_array([2, 0, 0]); - let three: U256 = BigNum::from_array([3, 0, 0]); - assert((one + two) == three); -} +#### Constrained functions + +Constrained arithmetic operations. These perform the expected arithmetic operations and reduce the result modulo `modulus`: +- `add` +- `sub` +- `mul` +- `neg` +- `div` - Expensive! + - Note: this method is only available for Fields, i.e if all elements have a multiplicative inverse. If this is not the case, use `udiv` +- `udiv`/`udiv_mod` - Expensive! + - Note: only use if you're not working with a Field +- `umod` - Expensive! + - Integer modular reduction which uses `udiv` + +For compile-time known moduli (see explanation below), these methods can be used using operators (`+`, `-`, `*`, `/`). + +> **Note:** `div`, `udiv` and `umod` 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) + + +Other constrained functions: +- `new`, returns a BigNum with value 0 +- `one`, returns a BigNum with value 1 +- `modulus`, returns `modulus` from the parameters +- `modulus_bits`, returns nr of bits from the parameters +- `num_limbs`, returns N, the number of limbs needed to represent the BigNum for the current parameters +- `evaluate_quadratic_expression`, more explanation below +- `validate_in_range`, validates the BigNum doesn't have more bits than `modulus_bits` +- `validate_in_field(val)`, validates `val` < `modulus` +- `assert_is_not_equal`, assert 2 BigNums are distinct +- `eq`, also available with `==` for compile-time known moduli +- `get(idx)`, return value of limbs `idx` (this is a field) +- `set_limb(idx, value)`, set limbs `idx` to new value +- `conditional_select(lhs, rhs, predicate)`, if `predicate` is 0 returns `lhs`, else if `predicate` is 1 returns `rhs` +- `to_le_bytes`, returns the little-endian byte representation of a BigNum + +#### Unconstrained functions + +All the constrained arithmetic methods have their unconstrained counterpart and there is an unconstrained method for exponentiation. The unconstrained methods are prefixed with `__`: +- `__add` +- `__sub` +- `__mul` +- `__neg` +- `__div` + - Note: this method is only available for fields, i.e if all elements have a multiplicative inverse. If this is not the case, use `__udiv` +- `__udiv`/`__udiv_mod` + - Note: only use if you're not working with a field +- `__pow` + +> **Note:** `__div`, `__udiv` and `__pow` 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, mentioned below) + + +For better performance, try to accumulate multiple arithmetic operations (up to degree 2), for example `a*b+c*e+d` and then constrain it once with `evaluate_quadratic_expression`. Find instructions on how to use that method below. + +Use the following unconstrained operations only when working with a field (otherwise the inverse is not defined): + +- `__invmod`, returns inverse of element +- `__batch_invert`, input is fixed size array. Returns inverses of all elements +- `__batch_invert_slice`, input is dynamically sized array. Returns inverses of all elements + +Other useful unconstrained functions: +- `__derive_from_seed`, only use for test purposes. Not cryptographically secure +- `__is_zero` +- `__eq` + +#### `evaluate_quadratic_expression` + +For a lower gatecount and thus better performance use `BigNumInstance::evaluate_quadratic_expression`. The goal is to perform multiple unconstrained arithmetic operations and then constrain with this function. E.g. if we wanted to compute `a * b + (c + d) * e + f = g`, instead of calling all five arithmetic operations in their constrained form, compute `g` via unconstrained functions and then constrain it at once with `evaluate_quadratic_expression`. + +Unconstrained functions `__mul, __add, __sub, __div, __pow` can be used to compute witnesses that can then be fed into `evaluate_quadratic_expression`. + +The method `evaluate_quadratic_expression` has the following interface: + +```rust + fn evaluate_quadratic_expression( + self, + lhs_terms: [[BN; LHS_N]; NUM_PRODUCTS], + lhs_flags: [[bool; LHS_N]; NUM_PRODUCTS], + rhs_terms: [[BN; RHS_N]; NUM_PRODUCTS], + rhs_flags: [[bool; RHS_N]; NUM_PRODUCTS], + linear_terms: [BN; ADD_N], + linear_flags: [bool; ADD_N] + ); ``` -#### Methods +`NUM_PRODUCTS` represents the number of multiplications being summed. + +`LHS_N, RHS_N` represents the number of `BigNum` objects being summed in the left and right operands of each product. -TODO: Document all available methods +`ADD_N` represents the number of `BigNum` objects being added -#### Moduli presets +The flag parameters `lhs_flags, rhs_flags, add_flags` define whether an operand in the expression will be negated. -##### Big Unsigned Integers +For example, for the earlier example we wanted to calculate `a * b + (c + d) * e + f = g`. To constrain this, we pass `a * b + (c + d) * e + f - g = 0` to `evaluate_quadratic_expression`. The parameters then become: +- `NUM_PRODUCTS` = 2. The products are `a * b` and `(c + d) * e` +- `LHS_N` = 2, `RHS_N` = 1. For the left hand side the first multiplication has 1 operand and the other 2, so take the upper limit and pad with zeroes +- `ADD_N` = 2. The linear terms are `f` and `-g` -BigNum supports operations over unsigned integers, with predefined types for 256, 384, 512, 768, 1024, 2048, 4096 and 8192 bit integers. -All arithmetic operations are supported including integer div and mod functions (`udiv`, `umod`). Bit shifts and comparison operators are not yet implemented. +To constrain the example, first calculate `g = a * b + (c + d) * e + f` in unconstrained functions. +```rust +// First product term a * b +let t0 = a.__mul(b); +// Second product term (c + d) * e +let t1 = (c.__add(d)).__mul(e); +let g = t0.__add(t1).__add(f); +``` -e.g. + +Then define the terms and flags to constrain `a * b + (c + d) * e + f - g = 0` and call `evaluate_quadratic_expression`. ```rust -use dep::bignum::fields::U256::U256Params; +// (a + 0) * b +let lhs_terms = [[a, BigNum::new()], [c, d]]; +let lhs_flags = [[false, false], [false, false]]; +// (c + d) * e +let rhs_terms = [[b], [e]]; +let rhs_flags = [[false], [false]]; +// + f + (-g) +let add_terms = [f, g]; +let add_flags = [false, true]; + +BigNum::evaluate_quadratic_expression(lhs_terms, lhs_flags, rhs_terms, rhs_flags, linear_terms, linear_flags); +``` + +##### Example usage evaluate_quadratic_expression + +In the base field of Ed25519, which is the integers mod $2^{255}-19$, perform simple arithmetic operations `(x1 * x2) + x3` and assert this equals `expected`. + +```rust +use dep::bignum::fields::ed25519Fq::{ED25519_Fq_Params, ED25519_Fq_Instance}; +use dep::bignum::runtime_bignum::BigNumInstance; use dep::bignum::BigNum; -type U256 = BigNum<3, U256Params>; +// Prime field mod 2^255-19 +type Fq = BigNum<3, ED25519_Fq_Params>; -fn foo(x: U256, y: U256) -> U256 { - x.udiv(y) +// Check that (x1 * x2) + x3 equals `expected` +fn main(x1: Fq, x2: Fq, x3: Fq, expected: Fq) { + // Step 1: calculate res = (x1 * x2) + x3 in unconstrained functions + let res = x1.__mul(x2).__add(x3); + + // Step 2: Constrain (x1 * x2) + x3 - res == 0 mod 2^255-19 + // (until now we have value res, but "unchecked") + + // `evaluate_quadratic_expression` takes: + // (1) x1, `false` sign flag + // (2) x2, `false` sign flag + // (3) + // - x3, `false` sign flag + // - res, `true` sign flag + // Combines to: (x1 * x2) + x3 + (-res) + BigNum::evaluate_quadratic_expression([[x1]], [[false]], [[x2]], [[false]], [x3, res], [false, true]); + + // Step 3: check res equals `expected` + assert(res == expected); // Equality operation on BigNums } ``` -##### Fields +See `bignum_test.nr` for more examples. -`BigNum::fields` contains `BigNumInstance` constructors for common fields. +### Types -Feature requests and/or pull requests welcome for missing fields you need. +BigNum operations are evaluated using two structs and a trait: `BigNumParamsTrait`, `BigNum`, `BigNumInstance`. -TODO: Document existing field presets (e.g. bls, ed25519, secp256k1) +`BigNumParamsTrait` 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?!) -## `runtime_bignum` +`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`)). -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 modulus 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. -### Types -bignum operations are evaluated using two structs and a trait: `ParamsTrait`, `BigNum`, `BigNumInstance` +### `modulus` -`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?!) +The modulus can be either known at compile-time or run-time. The following parameters are important to know to be able to work with modular arithmetic: -`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`)) +- `modulus` represents the BigNum modulus, encoded as an array of `Field` elements that each encode 120 bits of the modulus. The first array element represents the least significant 120 bits -The `BigNum` struct represents individual big numbers. +- `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) -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. +- `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 -### Usage +#### Compile-time known modulus -#### Example +For a modulus that is known at compile-time there are 2 options: +- use a predefined set of parameters, which are defined in `bignum/fields`. These are commonly used fields and unsigned integers +- create a new parameter set. For this you need to implement the `RuntimeBigNumParamsTrait` and `BigNumParamsTrait`, and create a `BigNumInstance`. [This tool](https://github.com/noir-lang/noir-bignum-paramgen) can be used, and see an example of that below + +In the case of a compile-time known modulus, the arithmetic can be executed directly on the BigNums themselves: ```rust -use crate::bignum::fields::bn254Fq{BNParams, BN254INSTANCE}; -use crate::bignum::runtime_bignum::BigNumInstance; -use crate::bignum::BigNum; +let a: U256 = BigNum::from_array([10, 0, 0]); +let b: U256 = BigNum::from_array([2, 0, 0]); -type Fq = BigNum<3, BNParams>; -type FqInst = BigNumInstance<3, BNParams>; +let c = a * b; // <- operator use only possible when modulus is known at compile-time + +assert(c == BigNum::from_array([20, 0, 0])); +``` -fn example(Fq a, Fq b) -> Fq { - let instance: FqInst = BN254INSTANCE; - instance.mul(a, b) +##### Predefined Unsigned Integers + +BigNum supports operations over unsigned integers, with predefined types for 256, 384, 512, 768, 1024, 2048, 4096 and 8192 bit integers. Keep in mind these are not Fields. + +All arithmetic operations are supported including integer div and mod functions (make sure to use udiv, umod). Bit shifts and comparison operators are not yet implemented. + +```rust +use dep::bignum::fields::U256::U256Params; +use dep::bignum::BigNum; + +type U256 = BigNum<3, U256Params>; + +fn foo(x: U256, y: U256) -> U256 { + x.udiv(y) } ``` -#### Methods +##### Predefined Fields + +This library includes the follow field presets: -##### Arithmetics +| Curve | Base Field Instance | Scalar Field Instance | +|-------------|------------------------------------|------------------------------------| +| [BLS12-377](https://eprint.iacr.org/2020/351) | `BLS12_377_Fq_Instance` | `BLS12_377_Fr_Instance` | +| [BLS12-381](https://electriccoin.co/blog/new-snark-curve/) | `BLS12_381_Fq_Instance` | `BLS12_381_Fr_Instance` | +| [BN254](https://zips.z.cash/protocol/protocol.pdf) | `BN254INSTANCE` | (Scalar field is the native field in Noir) | +| [Curve25519](https://cr.yp.to/ecdh/curve25519-20060209.pdf) | `ED25519_Fq_Instance` | `ED25519_Fr_Instance` | +| [MNT4-753](https://eprint.iacr.org/2014/595) | `MNT4_753_Fq_Instance` | `MNT4_753_Fr_Instance` | +| [MNT6-753](https://eprint.iacr.org/2014/595) | `MNT6_753_Fq_Instance` | `MNT6_753_Fr_Instance` | +| [Pallas](https://electriccoin.co/blog/the-pasta-curves-for-halo-2-and-beyond/)* | `Pallas_Fr_Instance` | `Pallas_Fq_Instance` | +| [Secp256k1](https://en.bitcoin.it/wiki/Secp256k1) | `Secp256k1_Fq_Instance` | `Secp256k1_Fr_Instance` | +| [Secp256r1](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf) | `Secp256r1_Fq_Instance` | `Secp256r1_Fr_Instance` | +| [Secp384r1](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf) | `Secp384r1_Fq_Instance` | `Secp384r1_Fr_Instance` | -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). +* TODO: this should probably be adjusted, to avoid confusion -##### Unconstrained arithmetics +Feature requests and/or pull requests welcome for missing fields you need. -Unconstrained functions `__mul, __add, __sub, __div, __pow` 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) +##### Create a new parameter set for custom modulus -e.g. if we wanted to compute `(a + b) * c + (d - e) * f = g` by evaluating the above example, `g` can be derived via: +The easiest way to generate everything you need for a parameter set is to use [this tool](https://github.com/noir-lang/noir-bignum-paramgen). +For example, after cloning and building the tool, for a `modulus` of 1024 bits for RSA run `./target/release/paramgen instance RSA1024_example > out.txt`. This prints the parameter set to `out.txt`. Since this is not a field, also add: ```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 g = bn.__add(t0, t1); +fn has_multiplicative_inverse() -> bool { false } ``` +to the traits implementations for the parameter set. -See `bignum_test.nr` for more examples. - -##### `evaluate_quadratic_expression` -The method `evaluate_quadratic_expression` has the following interface: +This should give a result like this: ```rust - fn evaluate_quadratic_expression( - self, - lhs_terms: [[BN; LHS_N]; NUM_PRODUCTS], - lhs_flags: [[bool; LHS_N]; NUM_PRODUCTS], - rhs_terms: [[BN; RHS_N]; NUM_PRODUCTS], - rhs_flags: [[bool; RHS_N]; NUM_PRODUCTS], - linear_terms: [BN; ADD_N], - linear_flags: [bool; ADD_N] - ); +struct RSA1024Params {} + +impl RuntimeBigNumParamsTrait<9> for RSA1024Params { + fn modulus_bits() -> u32 { + 1024 + } + fn has_multiplicative_inverse() -> bool { false } + +} +impl BigNumParamsTrait<9> for RSA1024Params { + fn get_instance() -> BigNumInstance<9, Self> { + MyMod_Instance + } + fn modulus_bits() -> u32 { + 1024 + } + fn has_multiplicative_inverse() -> bool { false } +} + +let MyMod_Instance: BigNumInstance<9, RSA1024Params> = BigNumInstance::new { + modulus: [..], // here the actual calculated values will be displayed + double_modulus: [..], + modulus_u60: .., + modulus_u60_x4: .., + redc_param: [..] +}; ``` -`NUM_PRODUCTS` represents the number of multiplications being summed (e.g. for `a*b + c*d == 0`, `NUM_PRODUCTS` = 2). +#### Run-time known modulus -`LHS_N, RHS_N` represents the number of `BigNum` objects being summed in the left and right operands of each product. For example, for `(a + b) * c + (d + e) * f == 0`, `LHS_N = 2`, `RHS_N = 1`. +When the modulus is known at run-time a `BigNumInstance` object will be used to perform the arithmetic operations on the given BigNums. The `BigNumInstance` can only be created from the actual parameters (that depend on the `modulus`). -`ADD_N` represents the number of `BigNum` objects being added into the product (e.g. for `a * b + c + d == 0`, `ADD_N = 2`). +At compile-time the bitsize of the modulus has to be known. From that it can be deduced how many limbs are needed to represent the modulus in a `BigNum` object. Then the following types can be defined and used throughout the program: -The flag parameters `lhs_flags, rhs_flags, add_flags` define whether an operand in the expression will be negated. For example, for `(a + b) * c + (d - e) * f - g == 0`, we would have: +```rust +struct RSA1024Params {} +impl RuntimeBigNumParamsTrait<9> for RSA1024Params { + fn modulus_bits() -> u32 { + 1024 + } + fn has_multiplicative_inverse() -> bool { false } +} +type RSA1024 = BigNum<9, RSA1024Params>; +type RSA1024Instance = BigNumInstance<9, RSA1024Params>; +``` + +Next, either pass a `BigNumInstance` to your program or create the instance on the fly with given `modulus` and `redc_param`. + +For example, passing on the `BigNumInstance`: ```rust -let lhs_terms = [[a, b], [d, e]]; -let lhs_flags = [[false, false], [false, true]]; -let rhs_terms = [[c], [f]]; -let rhs_flags = [[false], [false]]; -let add_terms = [g]; -let add_flags = [true]; -BigNum::evaluate_quadratic_expresson(lhs_terms, lhs_flags, rhs_terms, rhs_flags, linear_terms, linear_flags); +fn main(BNInstance: RSA1024Instance, a: RSA1024, b: RSA1024, expected: RSA1024) { + let c = BNInstance.add(a, b); + assert(BNInstance.eq(expected, c)); +} +``` + +Or pass on the 2 parameters and determine the `BigNumInstance` from those: +```rust +fn main(modulus: [Field; 9], redc_param: [Field; 9], a: RSA1024, b: RSA1024, expected: RSA1024) { + let BNInstance: RSA1024Instance = BigNumInstance::new(modulus, redc_param); + + let c = BNInstance.add(a, b); + assert(BNInstance.eq(expected, c)); +} ``` -##### TODO: Document other available methods +Note that in this case, performing `a*b` or `expected == c` is not possible directly; rather, the arithmetic operations must be executed using the `BigNumInstance`. + +Using your custom `modulus`, generate the actual values needed for `BigNumInstance` (`double_modulus`, `redc_param` etc) using the [paramgen tool](https://github.com/noir-lang/noir-bignum-paramgen). + +TODO: should modulus and redc_param be passed in as witnesses? Because of this comment: +> 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. + +## Benchmarks -#### Deriving BigNumInstance parameters: `modulus`, `redc_param` +Benchmarks can be run using [this repo](https://github.com/hashcloak/noir-bigint-bench/). -For common fields, BigNumInstance parameters can be pulled from the presets in `BigNum::fields`. +A full benchmark report based on that repo can be found [here](https://docs.google.com/spreadsheets/d/1KBc6mhMZ4iQYIZhsyF8E6-juhob7mM94sBKqVKS-v-8/edit?gid=0#gid=0). Below a few numbers are repeated. -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: +Number of gates: +| BigNum | 100 mult | 100 add/sub | +|:-----|:--------:|:------:| +| **U256** | 8507 | 5510 | +| **U1024** | 31960 | 11832 | +| **U2048** | 86935 | 20857 | -`modulus` represents the BigNum modulus, encoded as an array of `Field` elements that each encode 120 bits of the modulus. The first array element represents the least significant 120 bits. +Proving time, UP = UltraPlonk, UH = UltraHonk, in milliseconds: -`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) +| BigNum | 100 mult UP | 100 add UP | 100 mult HP | 100 add HP | +|:-----|:--------:|:------:|:--------:|:------:| +| **U256** | 548.6 | 313.1 | 329.8 | 208 | +| **U1024** | 1026.1 | 498.4 | 614.1 | 292 | +| **U2048** | 3101.2 | 842.3 | 1404.4 | 436.9 | -`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) +## Additional usage example -## Additional usage examples +Elliptic curve point doubling using `evaluate_quadratic_expression`: ```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::BNParams; +use dep::bignum::fields::bn254Fq::BN254INSTANCE; +use dep::bignum::BigNum; +use dep::bignum::runtime_bignum::BigNumInstance; type Fq = BigNum<3, BNParams>; -fn example_mul(Fq a, Fq b) -> Fq { - a * b -} - -fn example_ecc_double(Fq x, Fq y) -> (Fq, Fq) { +fn example_ecc_double(x: Fq, y: Fq) -> (Fq, Fq) { // Step 1: construct witnesses // lambda = 3*x*x / 2y let mut lambda_numerator = x.__mul(x); diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..d34304c5 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,2 @@ +These examples will be moved to another repo so that they can be tested/updated. +`nargo` currently ascends the directory structure to the highest found Nargo.toml file. diff --git a/examples/custom_modulus_example/Nargo.toml b/examples/custom_modulus_example/Nargo.toml new file mode 100644 index 00000000..808ce275 --- /dev/null +++ b/examples/custom_modulus_example/Nargo.toml @@ -0,0 +1,8 @@ +[package] +name = "custom_modulus_example" +type = "bin" +authors = [""] +compiler_version = ">=0.34.0" + +[dependencies] +bignum = { path = "../.." } \ No newline at end of file diff --git a/examples/custom_modulus_example/README.md b/examples/custom_modulus_example/README.md new file mode 100644 index 00000000..c2b60d75 --- /dev/null +++ b/examples/custom_modulus_example/README.md @@ -0,0 +1,82 @@ +# Custom (compile-time) modulus example + +This example assumes you have a modulus of 2048 bits and want to use it at compile-time. + +## Generate parameters + +Generate parameters for BigNum library using the [paramgen tool](https://github.com/noir-lang/noir-bignum-paramgen). After cloning and building the repo, run for the desired modulus (replace value): +``` +./target/release/paramgen instance 20456684803303048347530423697020751814399302502110667150078323718905269101172974800905356684386150937514420094865028635518785372677390301711491016948450207607846221304930728973797522378506999567139943652134234877561436074026268205649910255182621594085008284441316448901381890082286184776564686504132516001962221244304143672537300570792891349927421038112529453088606957312408143395552575078898208723518244571879698149587589463599543806422441264913231441141752207832659135584572831493077819920330893012366189267930979049769425009576366557305457228626690969723792844542486959558350660501921357172974372066702504546151411 RSA2048_example > out.txt +``` + +Result: +```rust +struct RSA2048_example_Params {} +impl RuntimeBigNumParamsTrait<18> for RSA2048_example_Params { + pub fn modulus_bits() -> u64 { + 2048 + } +} +impl BigNumParamsTrait<18> for RSA2048_example_Params { + pub fn get_instance() -> BigNumInstance<18, Self> { + RSA2048_example_Instance + } + pub fn modulus_bits() -> u64 { + 2048 + } +} +global RSA2048_example_Instance: BigNumInstance<18, RSA2048_example_Params> = BigNumInstance { + modulus: [ + 0xca067270cbaa2f334deca1472a2bf3, 0x6610de1958b4206e3e34a14af22618, 0xe59e76000fcfb05956e5503a499841, 0x3d429c951fdec78afafacd2381782c, 0x617806aa0cf2d15b8c2a41bfd38ed9, 0xa4a58ff8737791b2d5571ea75c92d3, 0x49f85ae321bfbc161162bc1034f586, 0xa40bf68ca724dff0cf63605975cf48, 0xd5c5fe2ab9a1adf232351085a7d591, 0x1b0d1c9077b8f0794b2cb4af4d294f, 0x9401a841ae63b1f566fd6b69e62ccd, 0xd6958bad7bcf453c6cb489538831e0, 0x51bdf4c4cd4c92887a1d178d6bc3ce, 0xd153e9c43d7aa9b0d7a2c1af84ea38, 0x9deb9b61f4e25c1bd0d53e4e0e61e1, 0xbed33da20e52d7c916d486f235202b, 0x0c541718be73e9ce00f430c086b205, 0xa2 + ], + double_modulus: [ + 0x01940ce4e197545e669bd9428e5457e6, 0x01cc21bc32b16840dc7c694295e44c30, 0x01cb3cec001f9f60b2adcaa074933081, 0x017a85392a3fbd8f15f5f59a4702f058, 0x01c2f00d5419e5a2b71854837fa71db1, 0x01494b1ff0e6ef2365aaae3d4eb925a5, 0x0193f0b5c6437f782c22c5782069eb0c, 0x014817ed194e49bfe19ec6c0b2eb9e8f, 0x01ab8bfc5573435be4646a210b4fab22, 0x01361a3920ef71e0f29659695e9a529e, 0x01280350835cc763eacdfad6d3cc5999, 0x01ad2b175af79e8a78d96912a71063c0, 0x01a37be9899a992510f43a2f1ad7879c, 0x01a2a7d3887af55361af45835f09d46f, 0x013bd736c3e9c4b837a1aa7c9c1cc3c2, 0x017da67b441ca5af922da90de46a4056, 0x0118a82e317ce7d39c01e861810d640a, 0x0143 + ], + modulus_u60: U60Repr { limbs: ArrayX { segments: [[ + 0x034deca1472a2bf3, 0x0ca067270cbaa2f3, 0x0e3e34a14af22618, 0x06610de1958b4206, 0x0956e5503a499841, 0x0e59e76000fcfb05, 0x0afafacd2381782c, 0x03d429c951fdec78, 0x0b8c2a41bfd38ed9, 0x0617806aa0cf2d15, 0x02d5571ea75c92d3, 0x0a4a58ff8737791b, 0x061162bc1034f586, 0x049f85ae321bfbc1, 0xcf63605975cf48, 0x0a40bf68ca724dff, 0x0232351085a7d591, 0x0d5c5fe2ab9a1adf], [0x094b2cb4af4d294f, 0x01b0d1c9077b8f07, 0x0566fd6b69e62ccd, 0x09401a841ae63b1f, 0x0c6cb489538831e0, 0x0d6958bad7bcf453, 0x087a1d178d6bc3ce, 0x051bdf4c4cd4c928, 0xd7a2c1af84ea38, 0x0d153e9c43d7aa9b, 0x0bd0d53e4e0e61e1, 0x09deb9b61f4e25c1, 0x0916d486f235202b, 0x0bed33da20e52d7c, 0x0e00f430c086b205, 0xc541718be73e9c, 0xa2, 0x00]] } }, + modulus_u60_x4: U60Repr { limbs: ArrayX { segments: [[ + 0x034deca1472a2bf3, 0x0ca067270cbaa2f3, 0x0e3e34a14af22618, 0x06610de1958b4206, 0x0956e5503a499841, 0x0e59e76000fcfb05, 0x0afafacd2381782c, 0x03d429c951fdec78, 0x0b8c2a41bfd38ed9, 0x0617806aa0cf2d15, 0x02d5571ea75c92d3, 0x0a4a58ff8737791b, 0x061162bc1034f586, 0x049f85ae321bfbc1, 0xcf63605975cf48, 0x0a40bf68ca724dff, 0x0232351085a7d591, 0x0d5c5fe2ab9a1adf], [0x094b2cb4af4d294f, 0x01b0d1c9077b8f07, 0x0566fd6b69e62ccd, 0x09401a841ae63b1f, 0x0c6cb489538831e0, 0x0d6958bad7bcf453, 0x087a1d178d6bc3ce, 0x051bdf4c4cd4c928, 0xd7a2c1af84ea38, 0x0d153e9c43d7aa9b, 0x0bd0d53e4e0e61e1, 0x09deb9b61f4e25c1, 0x0916d486f235202b, 0x0bed33da20e52d7c, 0x0e00f430c086b205, 0xc541718be73e9c, 0xa2, 0x00], [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]] } }, + redc_param: [ + 0x1fdcdafc0b4a1e7e76fea4d047f13c, 0xe9f3127f19f73583a5378c5e3fb4a4, 0x61d4721841efbeac85a785dd48b58e, 0x747ed55336a2218c1f011c5f730478, 0xf8e29e2ca49c4df7aa57cc4defb877, 0x67541ffb558ce4fea6409c61e5b96a, 0xc616b60523693e15226453d9c1a735, 0x8e609df55b7e417c40ec232ac85f2c, 0xec4a3a8702ea1a8836d5bf2d63dc4c, 0x73ee26edb819fac7c1a771ed2d2447, 0x84e660e9c009e0eadd88a0c2d957c5, 0xb9c718c32762b643b6c29606ea3766, 0x9077b6f87aa1ba000b853a4841ea29, 0x28e68f0218123e2f911bfe4f2afdb1, 0xc45f6a9c6e8f5c18e38415b847eba6, 0xfd74428019998d5f69bda9954cd6d4, 0x6c48d17d943d2d123a0cb7f1044254, 0x0194 + ] +}; +``` + +## Add parameters to file + +Create a file `src/custom_params.nr` and add the generated code. + +Slightly adjust parameters: +- remove the `pub` keywords +- add dependencies +- change return type of `modulus_bits` to `u32` +- add to `RuntimeBigNumParamsTrait` and `BigNumParamsTrait`: +```rust +fn has_multiplicative_inverse() -> bool { false } +``` + > Note: if you are generating modulus parameters for a field, this is not needed since the multiplicative inverse exists for all elements. + +## Use the params in Noir program + +Import the parameters like this: +```rust +use custom_params::RSA2048_example_Params; +``` + +and define the type of the BigNums for this modulus: +```rust +type RSA2048 = BigNum<18, RSA2048_example_Params>; +``` + +Now arithmetic operations can be performed: +```rust +fn main(a: RSA2048, b: RSA2048, expected: RSA2048) { + let c = a + b; // modular addition (constrained) + assert(c == expected); +} +``` + +Test this function +``` +nargo test +``` \ No newline at end of file diff --git a/examples/custom_modulus_example/src/custom_params.nr b/examples/custom_modulus_example/src/custom_params.nr new file mode 100644 index 00000000..d409427d --- /dev/null +++ b/examples/custom_modulus_example/src/custom_params.nr @@ -0,0 +1,39 @@ +use dep::bignum::BigNum; +use dep::bignum::BigNumParamsTrait; +use dep::bignum::runtime_bignum::BigNumInstance; +use dep::bignum::runtime_bignum::BigNumParamsTrait as RuntimeBigNumParamsTrait; +use dep::bignum::utils::u60_representation::U60Repr; +use dep::bignum::utils::arrayX::ArrayX; + +struct RSA2048_example_Params {} +impl RuntimeBigNumParamsTrait<18> for RSA2048_example_Params { + fn modulus_bits() -> u32 { + 2048 + } + fn has_multiplicative_inverse() -> bool { false } +} +impl BigNumParamsTrait<18> for RSA2048_example_Params { + fn get_instance() -> BigNumInstance<18, Self> { + RSA2048_example_Instance + } + fn modulus_bits() -> u32 { + 2048 + } + fn has_multiplicative_inverse() -> bool { false } +} +global RSA2048_example_Instance: BigNumInstance<18, RSA2048_example_Params> = BigNumInstance { + modulus: [ + 0xca067270cbaa2f334deca1472a2bf3, 0x6610de1958b4206e3e34a14af22618, 0xe59e76000fcfb05956e5503a499841, 0x3d429c951fdec78afafacd2381782c, 0x617806aa0cf2d15b8c2a41bfd38ed9, 0xa4a58ff8737791b2d5571ea75c92d3, 0x49f85ae321bfbc161162bc1034f586, 0xa40bf68ca724dff0cf63605975cf48, 0xd5c5fe2ab9a1adf232351085a7d591, 0x1b0d1c9077b8f0794b2cb4af4d294f, 0x9401a841ae63b1f566fd6b69e62ccd, 0xd6958bad7bcf453c6cb489538831e0, 0x51bdf4c4cd4c92887a1d178d6bc3ce, 0xd153e9c43d7aa9b0d7a2c1af84ea38, 0x9deb9b61f4e25c1bd0d53e4e0e61e1, 0xbed33da20e52d7c916d486f235202b, 0x0c541718be73e9ce00f430c086b205, 0xa2 + ], + double_modulus: [ + 0x01940ce4e197545e669bd9428e5457e6, 0x01cc21bc32b16840dc7c694295e44c30, 0x01cb3cec001f9f60b2adcaa074933081, 0x017a85392a3fbd8f15f5f59a4702f058, 0x01c2f00d5419e5a2b71854837fa71db1, 0x01494b1ff0e6ef2365aaae3d4eb925a5, 0x0193f0b5c6437f782c22c5782069eb0c, 0x014817ed194e49bfe19ec6c0b2eb9e8f, 0x01ab8bfc5573435be4646a210b4fab22, 0x01361a3920ef71e0f29659695e9a529e, 0x01280350835cc763eacdfad6d3cc5999, 0x01ad2b175af79e8a78d96912a71063c0, 0x01a37be9899a992510f43a2f1ad7879c, 0x01a2a7d3887af55361af45835f09d46f, 0x013bd736c3e9c4b837a1aa7c9c1cc3c2, 0x017da67b441ca5af922da90de46a4056, 0x0118a82e317ce7d39c01e861810d640a, 0x0143 + ], + modulus_u60: U60Repr { limbs: ArrayX { segments: [[ + 0x034deca1472a2bf3, 0x0ca067270cbaa2f3, 0x0e3e34a14af22618, 0x06610de1958b4206, 0x0956e5503a499841, 0x0e59e76000fcfb05, 0x0afafacd2381782c, 0x03d429c951fdec78, 0x0b8c2a41bfd38ed9, 0x0617806aa0cf2d15, 0x02d5571ea75c92d3, 0x0a4a58ff8737791b, 0x061162bc1034f586, 0x049f85ae321bfbc1, 0xcf63605975cf48, 0x0a40bf68ca724dff, 0x0232351085a7d591, 0x0d5c5fe2ab9a1adf], [0x094b2cb4af4d294f, 0x01b0d1c9077b8f07, 0x0566fd6b69e62ccd, 0x09401a841ae63b1f, 0x0c6cb489538831e0, 0x0d6958bad7bcf453, 0x087a1d178d6bc3ce, 0x051bdf4c4cd4c928, 0xd7a2c1af84ea38, 0x0d153e9c43d7aa9b, 0x0bd0d53e4e0e61e1, 0x09deb9b61f4e25c1, 0x0916d486f235202b, 0x0bed33da20e52d7c, 0x0e00f430c086b205, 0xc541718be73e9c, 0xa2, 0x00]] } }, + modulus_u60_x4: U60Repr { limbs: ArrayX { segments: [[ + 0x034deca1472a2bf3, 0x0ca067270cbaa2f3, 0x0e3e34a14af22618, 0x06610de1958b4206, 0x0956e5503a499841, 0x0e59e76000fcfb05, 0x0afafacd2381782c, 0x03d429c951fdec78, 0x0b8c2a41bfd38ed9, 0x0617806aa0cf2d15, 0x02d5571ea75c92d3, 0x0a4a58ff8737791b, 0x061162bc1034f586, 0x049f85ae321bfbc1, 0xcf63605975cf48, 0x0a40bf68ca724dff, 0x0232351085a7d591, 0x0d5c5fe2ab9a1adf], [0x094b2cb4af4d294f, 0x01b0d1c9077b8f07, 0x0566fd6b69e62ccd, 0x09401a841ae63b1f, 0x0c6cb489538831e0, 0x0d6958bad7bcf453, 0x087a1d178d6bc3ce, 0x051bdf4c4cd4c928, 0xd7a2c1af84ea38, 0x0d153e9c43d7aa9b, 0x0bd0d53e4e0e61e1, 0x09deb9b61f4e25c1, 0x0916d486f235202b, 0x0bed33da20e52d7c, 0x0e00f430c086b205, 0xc541718be73e9c, 0xa2, 0x00], [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]] } }, + redc_param: [ + 0x1fdcdafc0b4a1e7e76fea4d047f13c, 0xe9f3127f19f73583a5378c5e3fb4a4, 0x61d4721841efbeac85a785dd48b58e, 0x747ed55336a2218c1f011c5f730478, 0xf8e29e2ca49c4df7aa57cc4defb877, 0x67541ffb558ce4fea6409c61e5b96a, 0xc616b60523693e15226453d9c1a735, 0x8e609df55b7e417c40ec232ac85f2c, 0xec4a3a8702ea1a8836d5bf2d63dc4c, 0x73ee26edb819fac7c1a771ed2d2447, 0x84e660e9c009e0eadd88a0c2d957c5, 0xb9c718c32762b643b6c29606ea3766, 0x9077b6f87aa1ba000b853a4841ea29, 0x28e68f0218123e2f911bfe4f2afdb1, 0xc45f6a9c6e8f5c18e38415b847eba6, 0xfd74428019998d5f69bda9954cd6d4, 0x6c48d17d943d2d123a0cb7f1044254, 0x0194 + ] +}; + diff --git a/examples/custom_modulus_example/src/main.nr b/examples/custom_modulus_example/src/main.nr new file mode 100644 index 00000000..8b3d28fa --- /dev/null +++ b/examples/custom_modulus_example/src/main.nr @@ -0,0 +1,25 @@ +mod custom_params; + +use custom_params::RSA2048_example_Params; +use dep::bignum::BigNum; + +type RSA2048 = BigNum<18, RSA2048_example_Params>; + +fn main(a: RSA2048, b: RSA2048, expected: RSA2048) { + let c = a + b; + assert(c == expected); +} + +#[test] + +fn test_main() { + let mut a: RSA2048 = BigNum::new(); + a.limbs[0] = 10; + let mut b: RSA2048 = BigNum::new(); + b.limbs[0] = 20; + + let mut expected: RSA2048 = BigNum::new(); + expected.limbs[0] = 30; + + main(a, b, expected); +} diff --git a/examples/ed25519_example/Nargo.toml b/examples/ed25519_example/Nargo.toml new file mode 100644 index 00000000..503a0527 --- /dev/null +++ b/examples/ed25519_example/Nargo.toml @@ -0,0 +1,8 @@ +[package] +name = "ed25519_example" +type = "bin" +authors = [""] +compiler_version = ">=0.34.0" + +[dependencies] +bignum = { path = "../.." } \ No newline at end of file diff --git a/examples/ed25519_example/Prover.toml b/examples/ed25519_example/Prover.toml new file mode 100644 index 00000000..6069220d --- /dev/null +++ b/examples/ed25519_example/Prover.toml @@ -0,0 +1,20 @@ +[a] +limbs = ["1", "0", "0"] + +[b] +limbs = ["2", "0", "0"] + +[c] +limbs = ["3", "0", "0"] + +[d] +limbs = ["4", "0", "0"] + +[e] +limbs = ["5", "0", "0"] + +[expected] +limbs = ["43", "0", "0"] + +[f] +limbs = ["6", "0", "0"] diff --git a/examples/ed25519_example/README.md b/examples/ed25519_example/README.md new file mode 100644 index 00000000..5ed6a8c0 --- /dev/null +++ b/examples/ed25519_example/README.md @@ -0,0 +1,31 @@ +# ed25519 Example + +## Testing +Run test: +``` +nargo test +``` + +## Execution +The testvalues are also in the `Prover.toml`. Adjust them to prove with other values. +Execute the Noir program: +``` +nargo execute example1 +``` +The witness is written to `./target/example1.gz`. + +Prove valid execution (with default Barretenberg backend): +``` +bb prove -b ./target/ed25519_example.json -w ./target/example1.gz -o ./target/proof +``` +The generated proof will be in `./target/proof`. + +Verify proof by computing the verification key: +``` +bb write_vk -b ./target/ed25519_example.json -o ./target/vk +``` +.. and then verifying the proof: +``` +bb verify -k ./target/vk -p ./target/proof +``` +If successful, you see nothing! Otherwise an error will show. \ No newline at end of file diff --git a/examples/ed25519_example/src/main.nr b/examples/ed25519_example/src/main.nr new file mode 100644 index 00000000..02a48464 --- /dev/null +++ b/examples/ed25519_example/src/main.nr @@ -0,0 +1,57 @@ +use dep::bignum::fields::ed25519Fq::ED25519_Fq_Params; +use dep::bignum::runtime_bignum::BigNumInstance; +use dep::bignum::BigNum; + +// Prime field mod 2^255-19 +type Ed25519Fq = BigNum<3, ED25519_Fq_Params>; + +// Check that `a * b + (c + d) * e + f` equals `expected` +// (this constrains `a * b + (c + d) * e + f - expected = 0`) +fn main( + a: Ed25519Fq, + b: Ed25519Fq, + c: Ed25519Fq, + d: Ed25519Fq, + e: Ed25519Fq, + f: Ed25519Fq, + expected: Ed25519Fq +) { + // Step 1: calculate g = a * b + (c + d) * e + f in unconstrained functions + // First product term a * b + let t0 = a.__mul(b); + // Second product term (c + d) * e + let t1 = (c.__add(d)).__mul(e); + let g = t0.__add(t1).__add(f); + + // Step 2: + // product term 1 a * b. Rewrite to (a + 0) * b + // product term 2 (c + d) * e + // linear terms f, g + BigNum::evaluate_quadratic_expression( + [[a, BigNum::new()], [c, d]], + [[false, false], [false, false]], + [[b], [e]], + [[false], [false]], + [f, g], + [false, true] + ); + + // Step 3: check res equals `expected` + assert(g == expected); +} + +// Simple test +#[test] +fn test_main() { + // a=1, b=2, c=3, d=4, e=5, f=6 + let a: Ed25519Fq = BigNum::from_array([1, 0, 0]); + let b: Ed25519Fq = BigNum::from_array([2, 0, 0]); + let c: Ed25519Fq = BigNum::from_array([3, 0, 0]); + let d: Ed25519Fq = BigNum::from_array([4, 0, 0]); + let e: Ed25519Fq = BigNum::from_array([5, 0, 0]); + let f: Ed25519Fq = BigNum::from_array([6, 0, 0]); + // 1*2 + (3+4) * 5 + 6 = 43 + let expected: Ed25519Fq = BigNum::from_array([43, 0, 0]); + + main(a, b, c, d, e, f, expected); +} diff --git a/examples/runtime_modulus_example/Nargo.toml b/examples/runtime_modulus_example/Nargo.toml new file mode 100644 index 00000000..cb709821 --- /dev/null +++ b/examples/runtime_modulus_example/Nargo.toml @@ -0,0 +1,8 @@ +[package] +name = "runtime_modulus_example" +type = "bin" +authors = [""] +compiler_version = ">=0.34.0" + +[dependencies] +bignum = { path = "../.." } \ No newline at end of file diff --git a/examples/runtime_modulus_example/Prover.toml b/examples/runtime_modulus_example/Prover.toml new file mode 100644 index 00000000..c35ac5b1 --- /dev/null +++ b/examples/runtime_modulus_example/Prover.toml @@ -0,0 +1,12 @@ +modulus = ["0xca067270cbaa2f334deca1472a3e31", "0x6610de1958b4206e3e34a14af22618", "0xe59e76000fcfb05956e5503a499841", "0x3d429c951fdec78afafacd2381782c", "0x617806aa0cf2d15b8c2a41bfd38ed9", "0xa5bd5539ec839696d39cf8dfdd92d3", "0xe9bed0c391a2d4050d6d2367c6a943", "0x2d2150ff6bb93bf3a7d1163587d488", "0x852ed0ee51bf01f3"] +redc_param = ["0xb6b68cb415434ea6273830091e7ba4", "0x9da115c204677051818a8c033cbc85", "0x8cb24ce7044f7c1fe2cd6d5425e397", "0x478c0c53482aacf0b50d3649c1b096", "0xc7b7c730e69ec6b458f1ea2faf1c28", "0x6b733155f8756e0d0cc2bce80185da", "0xf34103a45581aa68206e1aac5ecdca", "0x59c9f258f9e3905b9629a277b6752f", "0x01ec13452bee67c5dc"] + +[a] +limbs = ["10", "0", "0", "0", "0", "0", "0", "0", "0"] + +[b] +limbs = ["20", "0", "0", "0", "0", "0", "0", "0", "0"] + +[expected] +limbs = ["30", "0", "0", "0", "0", "0", "0", "0", "0"] + diff --git a/examples/runtime_modulus_example/README.md b/examples/runtime_modulus_example/README.md new file mode 100644 index 00000000..8ddc18fd --- /dev/null +++ b/examples/runtime_modulus_example/README.md @@ -0,0 +1,64 @@ +# Runtime modulus example + +## Create program + +As explained in the general BigNum README, for a runtime modulus a `BigNumInstance` either has to be passed into the program or generated using the `modulus` and `redc_param`. In this example we'll do the latter. + +This is an example assuming RSA for a modulus of 1024 bits. + +At compile-time we know the number of bits of the modulus and can define the types: +```rust +struct RSA1024Params {} +impl RuntimeBigNumParamsTrait<9> for RSA1024Params { + fn modulus_bits() -> u32 { + 1024 + } + fn has_multiplicative_inverse() -> bool { false } +} + +type RSA1024 = BigNum<9, RSA1024Params>; +type RSA1024Instance = BigNumInstance<9, RSA1024Params>; +``` + +The `main` function expects the `modulus` and `redc_param` in order to generate the `BigNumInstance` and consequently use it to perform arithmetic operations. + +```rust +fn main(modulus: [Field; 9], redc_param: [Field; 9], a: RSA1024, b: RSA1024, expected: RSA1024) { + let BNInstance: RSA1024Instance = BigNumInstance::new(modulus, redc_param); + + let c = BNInstance.add(a, b); + assert(BNInstance.eq(expected, c)); +} +``` + +## Test program + +The test defines `modulus` and `redc_param`, which have been obtained using [this tool](https://github.com/noir-lang/noir-bignum-paramgen). To run the test: +``` +nargo test +``` + +## Execute, prove & verify program + +The testvalues are also in the `Prover.toml`. Adjust them to prove with other values. +Execute the Noir program: +``` +nargo execute example2 +``` +The witness is written to `./target/example2.gz`. + +Prove valid execution (with default Barretenberg backend): +``` +bb prove -b ./target/custom_modulus_example.json -w ./target/example2.gz -o ./target/proof +``` +The generated proof will be in `./target/proof`. + +Verify proof by computing the verification key: +``` +bb write_vk -b ./target/custom_modulus_example.json -o ./target/vk +``` +.. and then verifying the proof: +``` +bb verify -k ./target/vk -p ./target/proof +``` +If successful, you see nothing! Otherwise an error will show. \ No newline at end of file diff --git a/examples/runtime_modulus_example/src/main.nr b/examples/runtime_modulus_example/src/main.nr new file mode 100644 index 00000000..0380c6c9 --- /dev/null +++ b/examples/runtime_modulus_example/src/main.nr @@ -0,0 +1,42 @@ +use dep::bignum::{BigNum, BigNumParamsTrait}; +use dep::bignum::runtime_bignum::{BigNumTrait, BigNumInstanceTrait, BigNumParamsTrait as RuntimeBigNumParamsTrait}; +use dep::bignum::runtime_bignum::BigNumInstance; +use dep::bignum::utils::u60_representation::U60Repr; +use dep::bignum::utils::arrayX::ArrayX; + +struct RSA1024Params {} +impl RuntimeBigNumParamsTrait<9> for RSA1024Params { + fn modulus_bits() -> u32 { + 1024 + } + fn has_multiplicative_inverse() -> bool { false } +} + +type RSA1024 = BigNum<9, RSA1024Params>; +type RSA1024Instance = BigNumInstance<9, RSA1024Params>; + +fn main(modulus: [Field; 9], redc_param: [Field; 9], a: RSA1024, b: RSA1024, expected: RSA1024) { + let BNInstance: RSA1024Instance = BigNumInstance::new(modulus, redc_param); + + let c = BNInstance.add(a, b); + assert(BNInstance.eq(expected, c)); +} + +#[test] +fn test_add() { + let mut a: RSA1024 = BigNum::new(); + a.limbs[0] = 10; + let mut b: RSA1024 = BigNum::new(); + b.limbs[0] = 20; + + let mut expected: RSA1024 = BigNum::new(); + expected.limbs[0] = 30; + + let modulus = [ + 0xca067270cbaa2f334deca1472a3e31, 0x6610de1958b4206e3e34a14af22618, 0xe59e76000fcfb05956e5503a499841, 0x3d429c951fdec78afafacd2381782c, 0x617806aa0cf2d15b8c2a41bfd38ed9, 0xa5bd5539ec839696d39cf8dfdd92d3, 0xe9bed0c391a2d4050d6d2367c6a943, 0x2d2150ff6bb93bf3a7d1163587d488, 0x852ed0ee51bf01f3 + ]; + let redc_param = [ + 0xb6b68cb415434ea6273830091e7ba4, 0x9da115c204677051818a8c033cbc85, 0x8cb24ce7044f7c1fe2cd6d5425e397, 0x478c0c53482aacf0b50d3649c1b096, 0xc7b7c730e69ec6b458f1ea2faf1c28, 0x6b733155f8756e0d0cc2bce80185da, 0xf34103a45581aa68206e1aac5ecdca, 0x59c9f258f9e3905b9629a277b6752f, 0x01ec13452bee67c5dc + ]; + main(modulus, redc_param, a, b, expected); +}