Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement From<Field> on BigNum #87

Merged
merged 16 commits into from
Jan 16, 2025
1 change: 1 addition & 0 deletions export/test_add_BN.json

Large diffs are not rendered by default.

14 changes: 12 additions & 2 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
39 changes: 36 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,40 @@ 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 shift = 0x1000000000000000000000000000000;
// validate that the last limb is less than the modulus
kashbrti marked this conversation as resolved.
Show resolved Hide resolved
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);
kashbrti marked this conversation as resolved.
Show resolved Hide resolved
// validate that the limbs are in range
kashbrti marked this conversation as resolved.
Show resolved Hide resolved
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] * shift
} else {
validate_in_range::<N, 254>(result);
result[0] + result[1] * shift + result[2] * shift * shift
};
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);
kashbrti marked this conversation as resolved.
Show resolved Hide resolved
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
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]
TomAFrench marked this conversation as resolved.
Show resolved Hide resolved
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 unconstrained fn field_to_u60rep(mut x: Field) -> (u64, u64, u64, u64, u64) {
TomAFrench marked this conversation as resolved.
Show resolved Hide resolved
// 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> {
TomAFrench marked this conversation as resolved.
Show resolved Hide resolved
// 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) fn from_field(input: Field) -> Self {
let (first, second, third, fourth, fifth) = unsafe { field_to_u60rep(input) };
TomAFrench marked this conversation as resolved.
Show resolved Hide resolved
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 {
TomAFrench marked this conversation as resolved.
Show resolved Hide resolved
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",
);
TomAFrench marked this conversation as resolved.
Show resolved Hide resolved
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
}
TomAFrench marked this conversation as resolved.
Show resolved Hide resolved

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