Skip to content

Commit

Permalink
feat: implement From<Field> on BigNum (#87)
Browse files Browse the repository at this point in the history
Co-authored-by: Khashayar Barooti <[email protected]>
Co-authored-by: TomAFrench <[email protected]>
Co-authored-by: Tom French <[email protected]>
  • Loading branch information
4 people authored Jan 16, 2025
1 parent 241c26d commit 35bf983
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 16 deletions.
1 change: 1 addition & 0 deletions export/test_add_BN.json

Large diffs are not rendered by default.

16 changes: 13 additions & 3 deletions src/bignum.nr
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use crate::params::BigNumParamsGetter;

use crate::fns::{
constrained_ops::{
add, assert_is_not_equal, conditional_select, derive_from_seed, div, eq, mul, neg, sub,
udiv, udiv_mod, umod, validate_in_field, validate_in_range,
add, assert_is_not_equal, conditional_select, derive_from_seed, div, eq, from_field, mul,
neg, sub, udiv, udiv_mod, umod, validate_in_field, validate_in_range,
},
expressions::{__compute_quadratic_expression, evaluate_quadratic_expression},
serialization::{from_be_bytes, to_le_bytes},
Expand Down Expand Up @@ -88,6 +88,16 @@ pub trait BigNumTrait: Neg + Add + Sub + Mul + Div + Eq {
pub fn conditional_select(lhs: Self, rhs: Self, predicate: bool) -> Self;
}

impl<let N: u32, let MOD_BITS: u32, Params> std::convert::From<Field> for BigNum<N, MOD_BITS, Params>
where
Params: BigNumParamsGetter<N, MOD_BITS>,
{
fn from(input: Field) -> Self {
let params = Params::get_params();
Self { limbs: from_field::<N, MOD_BITS>(params, input) }
}
}

impl<let N: u32, let MOD_BITS: u32, Params> Neg for BigNum<N, MOD_BITS, Params>
where
Params: BigNumParamsGetter<N, MOD_BITS>,
Expand Down Expand Up @@ -226,7 +236,7 @@ where

unconstrained fn __tonelli_shanks_sqrt(self) -> std::option::Option<Self> {
let params = Params::get_params();
let maybe_limbs = unsafe { __tonelli_shanks_sqrt(params, self.limbs) };
let maybe_limbs = __tonelli_shanks_sqrt(params, self.limbs);
maybe_limbs.map(|limbs| Self { limbs })
}

Expand Down
40 changes: 37 additions & 3 deletions src/fns/constrained_ops.nr
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
use crate::params::BigNumParams as P;

use crate::fns::{
expressions::evaluate_quadratic_expression,
unconstrained_helpers::{
__add_with_flags, __neg_with_flags, __sub_with_flags, __validate_gt_remainder,
__add_with_flags, __from_field, __neg_with_flags, __sub_with_flags, __validate_gt_remainder,
__validate_in_field_compute_borrow_flags,
},
unconstrained_ops::{__div, __mul, __udiv_mod},
};
use crate::params::BigNumParams as P;

/**
* In this file:
Expand Down Expand Up @@ -36,6 +35,41 @@ use crate::fns::{
* We use a hash function that can be modelled as a random oracle
* This function *should* produce an output that is a uniformly randomly distributed value modulo BigNum::modulus()
**/
pub(crate) fn from_field<let N: u32, let MOD_BITS: u32>(
params: P<N, MOD_BITS>,
field: Field,
) -> [Field; N] {
// safty: we check that the resulting limbs represent the intended field element
// we check the bit length, the limbs being max 120 bits, and the value in total is less than the field modulus
let result = unsafe { __from_field::<N>(field) };
// validate the limbs are in range and the value in total is less than 2^254

let TWO_POW_120 = 0x1000000000000000000000000000000;
// validate that the last limb is less than the modulus
if N > 2 {
// validate that the result is less than the modulus
let mut grumpkin_modulus = [0; N];
grumpkin_modulus[0] = 0x33e84879b9709143e1f593f0000001;
grumpkin_modulus[1] = 0x4e72e131a029b85045b68181585d28;
grumpkin_modulus[2] = 0x3064;
validate_gt::<N, 254>(grumpkin_modulus, result);
// validate that the limbs are in range
validate_in_range::<N, 254>(result);
}
// validate the limbs sum up to the field value
let field_val = if N < 2 {
result[0]
} else if N == 2 {
validate_in_range::<N, 254>(result);
result[0] + result[1] * TWO_POW_120
} else {
validate_in_range::<N, 254>(result);
result[0] + result[1] * TWO_POW_120 + result[2] * TWO_POW_120 * TWO_POW_120
};
assert(field_val == field);
result
}

pub(crate) fn derive_from_seed<let N: u32, let MOD_BITS: u32, let SeedBytes: u32>(
params: P<N, MOD_BITS>,
seed: [u8; SeedBytes],
Expand Down
17 changes: 12 additions & 5 deletions src/fns/unconstrained_helpers.nr
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ global TWO_POW_60: u64 = 0x1000000000000000;
* __tonelli_shanks_sqrt
*/

pub(crate) unconstrained fn __from_field<let N: u32>(field: Field) -> [Field; N] {
// cast the field to a u60 representation
let res_u60: U60Repr<N, 2> = U60Repr::from_field(field);
let result: [Field; N] = U60Repr::into(res_u60);
result
}

pub(crate) unconstrained fn __validate_in_field_compute_borrow_flags<let N: u32, let MOD_BITS: u32>(
params: P<N, MOD_BITS>,
val: [Field; N],
Expand All @@ -35,8 +42,8 @@ pub(crate) unconstrained fn __validate_gt_remainder<let N: u32>(
lhs: [Field; N],
rhs: [Field; N],
) -> ([Field; N], [bool; N], [bool; N]) {
let a_u60: U60Repr<N, 2> = U60Repr::from(lhs);
let mut b_u60: U60Repr<N, 2> = U60Repr::from(rhs);
let a_u60: U60Repr<N, 2> = From::from(lhs);
let mut b_u60: U60Repr<N, 2> = From::from(rhs);

let underflow = b_u60.gte(a_u60);
b_u60 += U60Repr::one();
Expand Down Expand Up @@ -76,7 +83,7 @@ pub(crate) unconstrained fn __neg_with_flags<let N: u32, let MOD_BITS: u32>(
params: P<N, MOD_BITS>,
val: [Field; N],
) -> ([Field; N], [bool; N]) {
let x_u60: U60Repr<N, 2> = U60Repr::from(val);
let x_u60: U60Repr<N, 2> = From::from(val);
let mut result_u60: U60Repr<N, 2> = U60Repr { limbs: [0; 2 * N] };

let mut borrow_in: u64 = 0;
Expand All @@ -101,8 +108,8 @@ pub(crate) unconstrained fn __add_with_flags<let N: u32, let MOD_BITS: u32>(
lhs: [Field; N],
rhs: [Field; N],
) -> ([Field; N], [bool; N], [bool; N], bool) {
let a_u60: U60Repr<N, 2> = U60Repr::from(lhs);
let b_u60: U60Repr<N, 2> = U60Repr::from(rhs);
let a_u60: U60Repr<N, 2> = From::from(lhs);
let b_u60: U60Repr<N, 2> = From::from(rhs);
let add_u60 = a_u60 + b_u60;

let overflow = add_u60.gte(params.modulus_u60);
Expand Down
10 changes: 5 additions & 5 deletions src/runtime_bignum.nr
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub(crate) trait RuntimeBigNumTrait<let N: u32, let MOD_BITS: u32>: Neg + Add +
params: BigNumParams<N, MOD_BITS>,
seed: [u8; SeedBytes],
) -> Self;
pub fn __derive_from_seed<let SeedBytes: u32>(
pub unconstrained fn __derive_from_seed<let SeedBytes: u32>(
params: BigNumParams<N, MOD_BITS>,
seed: [u8; SeedBytes],
) -> Self;
Expand Down Expand Up @@ -138,16 +138,16 @@ impl<let N: u32, let MOD_BITS: u32> RuntimeBigNumTrait<N, MOD_BITS> for RuntimeB
params: BigNumParams<N, MOD_BITS>,
seed: [u8; SeedBytes],
) -> Self {
let limbs = unsafe { derive_from_seed::<_, MOD_BITS, _>(params, seed) };
let limbs = derive_from_seed::<_, MOD_BITS, _>(params, seed);
Self { limbs, params }
}

// UNCONSTRAINED! (Hence `__` prefix).
fn __derive_from_seed<let SeedBytes: u32>(
unconstrained fn __derive_from_seed<let SeedBytes: u32>(
params: BigNumParams<N, MOD_BITS>,
seed: [u8; SeedBytes],
) -> Self {
let limbs = unsafe { __derive_from_seed::<_, MOD_BITS, _>(params, seed) };
let limbs = __derive_from_seed::<_, MOD_BITS, _>(params, seed);
Self { limbs, params }
}

Expand Down Expand Up @@ -281,7 +281,7 @@ impl<let N: u32, let MOD_BITS: u32> RuntimeBigNumTrait<N, MOD_BITS> for RuntimeB
unconstrained fn __batch_invert_slice<let M: u32>(x: [Self]) -> [Self] {
let params = x[0].params;
assert(params.has_multiplicative_inverse);
let all_limbs = unsafe {
let all_limbs = {
let inv_slice =
__batch_invert_slice::<_, MOD_BITS>(params, x.map(|bn| Self::get_limbs(bn)));
inv_slice.as_array()
Expand Down
26 changes: 26 additions & 0 deletions src/tests/bignum_test.nr
Original file line number Diff line number Diff line change
Expand Up @@ -790,3 +790,29 @@ fn test_expressions() {
assert(wx_constrained.limbs == wx.limbs);
}

#[test]
fn test_from_field_1_digit() {
let field: Field = 1;
let result = Fq::from(field);
assert(result == Fq::one());
}

#[test]
fn test_from_field_2_digits() {
let field: Field = 762576765071760201410184025311678064293966151975347778787092903729041075;
let result = Fq::from(field);
let expected: Fq =
BigNum { limbs: [0xe88ed97f8f707abd3fa65763c80eb3, 0x6e7d8b5586595aa1fb2ee04d5cb4f5, 0x0] };
assert(result == expected);
}

#[test]
fn test_from_field_3_digits() {
let field: Field = -1;
let result = Fq::from(field);
let expected: Fq = BigNum {
limbs: [0x33e84879b9709143e1f593f0000000, 0x4e72e131a029b85045b68181585d28, 0x3064],
};
assert(result == expected);
}

24 changes: 24 additions & 0 deletions src/utils/split_bits.nr
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,30 @@ global TWO_POW_56: u64 = 0x100000000000000;
global TWO_POW_60: u64 = 0x1000000000000000;
global TWO_POW_64: Field = 0x10000000000000000;

//fields to u60rep conversion
// field elements are 254 bits
// so there will be 5 limbs
pub(crate) unconstrained fn field_to_u60rep(mut x: Field) -> (u64, u64, u64, u64, u64) {
// get the first 60 bits by casting to u64 and then taking the lower 60 bits
// we use the fact that this casting drops everything above 64 bits
let x_first_u64 = (x as u64);
let first: u64 = x_first_u64 % TWO_POW_60;
// this becomes the same as a integer division because we're removing the remainder
x = (x - (first as Field)) / (TWO_POW_60 as Field);
let x_second_u64 = (x as u64);
let second = x_second_u64 % TWO_POW_60;
x = (x - (second as Field)) / (TWO_POW_60 as Field);
let x_third_u64 = (x as u64);
let third = x_third_u64 % TWO_POW_60;
x = (x - (third as Field)) / (TWO_POW_60 as Field);
let x_fourth_u64 = (x as u64);
let fourth = x_fourth_u64 % TWO_POW_60;
x = (x - (fourth as Field)) / (TWO_POW_60 as Field);
let x_fifth_u64 = (x as u64);
let fifth = x_fifth_u64 % TWO_POW_60;
(first, second, third, fourth, fifth)
}

// Decomposes a single field into two 120 bit fields
pub unconstrained fn split_120_bits(mut x: Field) -> (Field, Field) {
// Here we're taking advantage of truncating 64 bit limbs from the input field
Expand Down
63 changes: 63 additions & 0 deletions src/utils/u60_representation.nr
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::utils::msb::get_msb64;
use crate::utils::split_bits;
use crate::utils::split_bits::field_to_u60rep;

/**
* @brief U60Repr represents a BigNum element as a sequence of 60-bit unsigned integers.
Expand Down Expand Up @@ -57,6 +58,29 @@ impl<let N: u32, let NumSegments: u32> std::convert::From<[Field; N]> for U60Rep
}
}

// impl<let N: u32, let NumSegments: u32> std::convert::From<Field> for U60Repr<N, NumSegments> {
// fn from(input: Field) -> Self {
// let (low, mid, high) = unsafe { field_to_u60rep(input) } ;
// let mut result: Self = U60Repr { limbs: [0; N * NumSegments] };
// let N_u60: u32 = N * NumSegments;
// assert(N_u60 >=1, "N must be at least 1");
// if N_u60 == 1 {
// assert((mid ==0) & (high == 0), "input field is too large to fit in a single limb");
// result.limbs[0] = low;
// }
// else if N_u60 == 2{
// assert(high == 0, "input field is too large to fit in two limbs");
// result.limbs[0] = low;
// result.limbs[1] = mid;
// }else{
// result.limbs[0] = low;
// result.limbs[1] = mid;
// result.limbs[2] = high;
// }
// result
// }
// }

impl<let N: u32, let NumSegments: u32> std::convert::Into<[Field; N]> for U60Repr<N, NumSegments> {
fn into(x: U60Repr<N, NumSegments>) -> [Field; N] {
let mut result: [Field; N] = [0; N];
Expand Down Expand Up @@ -94,6 +118,45 @@ impl<let N: u32, let NumSegments: u32> U60Repr<N, NumSegments> {
result
}

pub(crate) unconstrained fn from_field(input: Field) -> Self {
let (first, second, third, fourth, fifth) = field_to_u60rep(input);
let mut result: Self = U60Repr { limbs: [0; N * NumSegments] };
let N_u60: u32 = N * NumSegments;
assert(N_u60 >= 1, "N must be at least 1");
if N_u60 == 1 {
assert(
(second == 0) & (third == 0) & (fourth == 0) & (fifth == 0),
"input field is too large to fit in a single limb",
);
result.limbs[0] = first;
} else if N_u60 == 2 {
assert(
(third == 0) & (fourth == 0) & (fifth == 0),
"input field is too large to fit in two limbs",
);
result.limbs[0] = first;
result.limbs[1] = second;
} else if N_u60 == 3 {
assert((fourth == 0) & (fifth == 0), "input field is too large to fit in three limbs");
result.limbs[0] = first;
result.limbs[1] = second;
result.limbs[2] = third;
} else if N_u60 == 4 {
assert((fifth == 0), "input field is too large to fit in four limbs");
result.limbs[0] = first;
result.limbs[1] = second;
result.limbs[2] = third;
result.limbs[3] = fourth;
} else {
result.limbs[0] = first;
result.limbs[1] = second;
result.limbs[2] = third;
result.limbs[3] = fourth;
result.limbs[4] = fifth;
}
result
}

pub(crate) unconstrained fn into_field_array(
x: U60Repr<N, NumSegments>,
) -> [Field; N * NumSegments / 2] {
Expand Down

0 comments on commit 35bf983

Please sign in to comment.