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: add from field method #87

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions export/test_add_BN.json

Large diffs are not rendered by default.

20 changes: 18 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 All @@ -26,6 +26,7 @@ pub trait BigNumTrait: Neg + Add + Sub + Mul + Div + Eq {
// fn default() -> Self { std::default::Default::default () }
pub fn new() -> Self;
pub fn one() -> Self;
pub fn from_field(field: Field) -> Self;
pub fn derive_from_seed<let SeedBytes: u32>(seed: [u8; SeedBytes]) -> Self;
pub unconstrained fn __derive_from_seed<let SeedBytes: u32>(seed: [u8; SeedBytes]) -> Self;
pub fn from_slice(limbs: [Field]) -> Self;
Expand Down Expand Up @@ -88,6 +89,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>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ideally I would like to use this method, but there's an issue with a similar impl in U60Repr

// 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 All @@ -113,6 +124,11 @@ where
result
}

fn from_field(field: Field) -> Self {
let params = Params::get_params();
Self { limbs: from_field::<N, MOD_BITS>(params, field) }
}

fn derive_from_seed<let SeedBytes: u32>(seed: [u8; SeedBytes]) -> Self {
let params = Params::get_params();
Self { limbs: derive_from_seed::<_, MOD_BITS, _>(params, seed) }
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
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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have to check the 254-bit bignum returned by __from_field is actually a grumpkin field element.
to do this I'm using the validate_gt function and comparing the result to the grumpkin_modulus.

At first, I wanted to use the validate_in_field function, but the issue is, it only works when the generics match the generics used to create the field (in this case grumpkin_Fq which will have 3 limbs. So instead I just did the validate_gt check.

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);
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]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess missing a test case where Field is large enough so it will produce 2 limbs

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added tests for 2 and 3 digits.

fn test_from_field_1_digit() {
let field: Field = 1;
let result = Fq::from_field(field);
assert(result == Fq::one());
}

#[test]
fn test_from_field_2_digits() {
let field: Field = 762576765071760201410184025311678064293966151975347778787092903729041075;
let result = Fq::from_field(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(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) {
// 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> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this would be my preferred way of doing the casting for U60Repr but the issue is as soon as I have the conversion like this, I can not use U60repr::from() anymore. because it does not know the type generic. @TomAFrench any suggestions on how to fix this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TomAFrench, I'm just pinging you to see if you have an idea if we can fix this.

// 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) };
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 {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very ugly, very ugly. but I'm not sure if there's a workaround.

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
Loading