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

Add quad hint and integration test #715

Merged
merged 17 commits into from
Apr 12, 2023
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ felt = { package = "cairo-felt", path = "./felt", version = "0.1.0" }

[dev-dependencies]
iai = "0.1"
proptest = "1.0.0"
assert_matches = "1.5.0"

[dev-dependencies.rusty-hook]
Expand Down
67 changes: 67 additions & 0 deletions cairo_programs/is_quad_residue_test.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
%builtins output
from starkware.cairo.common.serialize import serialize_word
from starkware.cairo.common.math import is_quad_residue
from starkware.cairo.common.alloc import alloc

func fill_array(array_start: felt*, iter: felt) -> () {
if (iter == 32) {
return ();
}
assert array_start[iter] = iter;
return fill_array(array_start, iter + 1);
}

func check_quad_res{output_ptr: felt*}(inputs: felt*, expected: felt*, iter: felt) {
if (iter == 32) {
return ();
}
serialize_word(inputs[iter]);
serialize_word(expected[iter]);

assert is_quad_residue(inputs[iter]) = expected[iter];
return check_quad_res(inputs, expected, iter + 1);
}

func main{output_ptr: felt*}() {
alloc_locals;
let (inputs: felt*) = alloc();
fill_array(inputs, 0);

let (expected: felt*) = alloc();
assert expected[0] = 1;
assert expected[1] = 1;
assert expected[2] = 1;
assert expected[3] = 0;
assert expected[4] = 1;
assert expected[5] = 1;
assert expected[6] = 0;
assert expected[7] = 1;
assert expected[8] = 1;
assert expected[9] = 1;
assert expected[10] = 1;
assert expected[11] = 1;
assert expected[12] = 0;
assert expected[13] = 1;
assert expected[14] = 1;
assert expected[15] = 0;
assert expected[16] = 1;
assert expected[17] = 1;
assert expected[18] = 1;
assert expected[19] = 0;
assert expected[20] = 1;
assert expected[21] = 0;
assert expected[22] = 1;
assert expected[23] = 0;
assert expected[24] = 0;
assert expected[25] = 1;
assert expected[26] = 1;
assert expected[27] = 0;
assert expected[28] = 1;
assert expected[29] = 0;
assert expected[30] = 0;
assert expected[31] = 1;

check_quad_res(inputs, expected, 0);

return();
}
99 changes: 78 additions & 21 deletions felt/src/bigint_felt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,35 @@ impl FeltOps for FeltBigInt<FIELD_HIGH, FIELD_LOW> {
self.val.clone()
}

fn sqrt(&self) -> FeltBigInt<FIELD_HIGH, FIELD_LOW> {
FeltBigInt {
val: self.val.sqrt(),
fn sqrt(&self) -> Self {
// Based on Tonelli-Shanks' algorithm for finding square roots
// and sympy's library implementation of said algorithm.
if self.is_zero() || self.is_one() {
return self.clone();
}

let max_felt = FeltBigInt::max_value();
let trailing_prime = FeltBigInt::max_value() >> 192; // 0x800000000000011
let a = self.pow(&trailing_prime);
let d = (&FeltBigInt::new(3_i32)).pow(&trailing_prime);
let mut m = FeltBigInt::zero();
let mut exponent = FeltBigInt::one() << 191_u32;
let mut adm;
for i in 0..192_u32 {
adm = &a * &(&d).pow(&m);
adm = (&adm).pow(&exponent);
exponent >>= 1;
// if adm ≡ -1 (mod CAIRO_PRIME)
if adm == max_felt {
m += FeltBigInt::one() << i;
Copy link
Contributor

Choose a reason for hiding this comment

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

We can make a lookup table with the 192 possible powers of 2 for this.

}
}
let root_1 = self.pow((trailing_prime + 1_u32) >> 1) * (&d).pow(m >> 1);
let root_2 = &max_felt - &root_1 + 1_usize;
if root_1 < root_2 {
root_1
} else {
root_2
}
}

Expand Down Expand Up @@ -478,6 +504,22 @@ impl<'a, const PH: u128, const PL: u128> Pow<u32> for &'a FeltBigInt<PH, PL> {
}
}

impl<'a, const PH: u128, const PL: u128> Pow<&'a FeltBigInt<PH, PL>> for &'a FeltBigInt<PH, PL> {
type Output = FeltBigInt<PH, PL>;
fn pow(self, rhs: Self) -> Self::Output {
FeltBigInt {
val: self.val.modpow(&rhs.val, &CAIRO_PRIME),
}
}
}

impl<'a, const PH: u128, const PL: u128> Pow<FeltBigInt<PH, PL>> for &'a FeltBigInt<PH, PL> {
type Output = FeltBigInt<PH, PL>;
fn pow(self, rhs: Self::Output) -> Self::Output {
self.pow(&rhs)
}
}

impl<const PH: u128, const PL: u128> Div for FeltBigInt<PH, PL> {
type Output = Self;
// In Felts `x / y` needs to be expressed as `x * y^-1`
Expand Down Expand Up @@ -1008,6 +1050,7 @@ mod tests {

proptest! {
#[test]
#[allow(deprecated)]
// Property-based test that ensures, for 100 pairs of values that are randomly generated each time tests are run, that performing a subtraction returns a result that is inside of the range [0, p].
fn sub_bigint_felt_within_field(ref x in "([1-9][0-9]*)", ref y in "([1-9][0-9]*)") {
let x = FeltBigInt::<FIELD_HIGH, FIELD_LOW>::parse_bytes(x.as_bytes(), 10).unwrap();
Expand All @@ -1019,6 +1062,7 @@ mod tests {
}

#[test]
#[allow(deprecated)]
// Property-based test that ensures, for 100 pairs of values that are randomly generated each time tests are run, that performing a subtraction returns a result that is inside of the range [0, p].
fn sub_assign_bigint_felt_within_field(ref x in "([1-9][0-9]*)", ref y in "([1-9][0-9]*)") {
let mut x = FeltBigInt::<FIELD_HIGH, FIELD_LOW>::parse_bytes(x.as_bytes(), 10).unwrap();
Expand All @@ -1040,6 +1084,7 @@ mod tests {
}
// Tests that the result of adding two random large bigint felts falls within the range [0, p]. This test is performed 100 times each run.
#[test]
#[allow(deprecated)]
fn add_bigint_felts_within_field(ref x in "([1-9][0-9]*)", ref y in "([1-9][0-9]*)") {
let x = FeltBigInt::<FIELD_HIGH, FIELD_LOW>::parse_bytes(x.as_bytes(), 10).unwrap();
let y = FeltBigInt::<FIELD_HIGH, FIELD_LOW>::parse_bytes(y.as_bytes(), 10).unwrap();
Expand All @@ -1050,6 +1095,7 @@ mod tests {

}
#[test]
#[allow(deprecated)]
// Tests that the result of performing add assign on two random large bigint felts falls within the range [0, p]. This test is performed 100 times each run.
fn add_assign_bigint_felts_within_field(ref x in "([1-9][0-9]*)", ref y in "([1-9][0-9]*)") {
let mut x = FeltBigInt::<FIELD_HIGH, FIELD_LOW>::parse_bytes(x.as_bytes(), 10).unwrap();
Expand All @@ -1061,6 +1107,7 @@ mod tests {
}

#[test]
#[allow(deprecated)]
// Tests that the result of performing the bitwise "and" operation on two random large bigint felts falls within the range [0, p]. This test is performed 100 times each run.
fn bitand_bigint_felts_within_field(ref x in "([1-9][0-9]*)", ref y in "([1-9][0-9]*)") {
let x = FeltBigInt::<FIELD_HIGH, FIELD_LOW>::parse_bytes(x.as_bytes(), 10).unwrap();
Expand All @@ -1071,6 +1118,7 @@ mod tests {
prop_assert!(as_uint < p, "{}", as_uint);
}
#[test]
#[allow(deprecated)]
// Tests that the result of performing the bitwise "or" operation on two random large bigint felts falls within the range [0, p]. This test is performed 100 times each run.
fn bitor_bigint_felts_within_field(ref x in "([1-9][0-9]*)", ref y in "([1-9][0-9]*)") {
let x = FeltBigInt::<FIELD_HIGH, FIELD_LOW>::parse_bytes(x.as_bytes(), 10).unwrap();
Expand All @@ -1081,6 +1129,7 @@ mod tests {
prop_assert!(as_uint < p, "{}", as_uint);
}
#[test]
#[allow(deprecated)]
// Tests that the result of performing the bitwise "xor" operation on two random large bigint felts falls within the range [0, p]. This test is performed 100 times each run.
fn bitxor_bigint_felts_within_field(ref x in "([1-9][0-9]*)", ref y in "([1-9][0-9]*)") {
let x = FeltBigInt::<FIELD_HIGH, FIELD_LOW>::parse_bytes(x.as_bytes(), 10).unwrap();
Expand All @@ -1091,6 +1140,7 @@ mod tests {
prop_assert!(as_uint < p, "{}", as_uint);
}
#[test]
#[allow(deprecated)]
// Tests that the result dividing two random large bigint felts falls within the range [0, p]. This test is performed 100 times each run.
fn div_bigint_felts_within_field(ref x in "([1-9][0-9]*)", ref y in "([1-9][0-9]*)") {
let x = FeltBigInt::<FIELD_HIGH, FIELD_LOW>::parse_bytes(x.as_bytes(), 10).unwrap();
Expand All @@ -1101,6 +1151,7 @@ mod tests {
prop_assert!(as_uint < p, "{}", as_uint);
}
#[test]
#[allow(deprecated)]
// Tests that the result multiplying two random large bigint felts falls within the range [0, p]. This test is performed 100 times each run.
fn mul_bigint_felts_within_field(ref x in "([1-9][0-9]*)", ref y in "([1-9][0-9]*)") {
let x = FeltBigInt::<FIELD_HIGH, FIELD_LOW>::parse_bytes(x.as_bytes(), 10).unwrap();
Expand All @@ -1111,6 +1162,7 @@ mod tests {
prop_assert!(as_uint < p, "{}", as_uint);
}
#[test]
#[allow(deprecated)]
// Tests that the result of performing a multiplication with assignment between two random large bigint felts falls within the range [0, p]. This test is performed 100 times each run.
fn mul_assign_bigint_felts_within_field(ref x in "([1-9][0-9]*)", ref y in "([1-9][0-9]*)") {
let mut x = FeltBigInt::<FIELD_HIGH, FIELD_LOW>::parse_bytes(x.as_bytes(), 10).unwrap();
Expand All @@ -1121,6 +1173,7 @@ mod tests {
prop_assert!(as_uint < p, "{}", as_uint);
}
#[test]
#[allow(deprecated)]
// Tests that the result of applying the negative operation to a large bigint felt falls within the range [0, p]. This test is performed 100 times each run.
fn neg_bigint_felt_within_field(ref x in "([1-9][0-9]*)") {
let x = FeltBigInt::<FIELD_HIGH, FIELD_LOW>::parse_bytes(x.as_bytes(), 10).unwrap();
Expand All @@ -1131,17 +1184,19 @@ mod tests {
}

#[test]
// Property-based test that ensures, for 100 {value}s that are randomly generated each time tests are run, that performing a bit shift to the left by an amount {y} of bits (between 0 and 999) returns a result that is inside of the range [0, p].
fn shift_left_bigint_felt_within_field(ref x in "([1-9][0-9]*)", ref y in "[0-9]{1,3}") {
let x = FeltBigInt::<FIELD_HIGH, FIELD_LOW>::parse_bytes(x.as_bytes(), 10).unwrap();
let y = y.parse::<u32>().unwrap();
let p:BigUint = BigUint::parse_bytes(CAIRO_PRIME.to_string().as_bytes(), 16).unwrap();
let result = x << y;
let as_uint = &result.to_biguint();
prop_assert!(as_uint < &p, "{}", as_uint);
#[allow(deprecated)]
// Property-based test that ensures, for 100 {value}s that are randomly generated each time tests are run, that performing a bit shift to the left by an amount {y} of bits (between 0 and 999) returns a result that is inside of the range [0, p].
fn shift_left_bigint_felt_within_field(ref x in "([1-9][0-9]*)", ref y in "[0-9]{1,3}") {
let x = FeltBigInt::<FIELD_HIGH, FIELD_LOW>::parse_bytes(x.as_bytes(), 10).unwrap();
let y = y.parse::<u32>().unwrap();
let p:BigUint = BigUint::parse_bytes(CAIRO_PRIME.to_string().as_bytes(), 16).unwrap();
let result = x << y;
let as_uint = &result.to_biguint();
prop_assert!(as_uint < &p, "{}", as_uint);
}

#[test]
#[allow(deprecated)]
// Property-based test that ensures, for 100 {value}s that are randomly generated each time tests are run, that performing a bit shift to the right by an amount {y} of bits (between 0 and 999) returns a result that is inside of the range [0, p].
fn shift_right_bigint_felt_within_field(ref x in "([1-9][0-9]*)", ref y in "[0-9]{1,3}") {
let x = FeltBigInt::<FIELD_HIGH, FIELD_LOW>::parse_bytes(x.as_bytes(), 10).unwrap();
Expand All @@ -1150,20 +1205,22 @@ mod tests {
let result = x >> y;
let as_uint = &result.to_biguint();
prop_assert!(as_uint < &p, "{}", as_uint);
}
}

#[test]
// Property-based test that ensures, for 100 {value}s that are randomly generated each time tests are run, that performing a bit shift to the right with assignment by an amount {y} of bits (between 0 and 999) returns a result that is inside of the range [0, p].
fn shift_right_assign_bigint_felt_within_field(ref x in "([1-9][0-9]*)", ref y in "[0-9]{1,3}") {
let mut x = FeltBigInt::<FIELD_HIGH, FIELD_LOW>::parse_bytes(x.as_bytes(), 10).unwrap();
let y = y.parse::<u32>().unwrap();
let p:BigUint = BigUint::parse_bytes(CAIRO_PRIME.to_string().as_bytes(), 16).unwrap();
x >>= y.try_into().unwrap();
let as_uint = &x.to_biguint();
prop_assert!(as_uint < &p, "{}", as_uint);
#[test]
#[allow(deprecated)]
// Property-based test that ensures, for 100 {value}s that are randomly generated each time tests are run, that performing a bit shift to the right with assignment by an amount {y} of bits (between 0 and 999) returns a result that is inside of the range [0, p].
fn shift_right_assign_bigint_felt_within_field(ref x in "([1-9][0-9]*)", ref y in "[0-9]{1,3}") {
let mut x = FeltBigInt::<FIELD_HIGH, FIELD_LOW>::parse_bytes(x.as_bytes(), 10).unwrap();
let y = y.parse::<u32>().unwrap();
let p:BigUint = BigUint::parse_bytes(CAIRO_PRIME.to_string().as_bytes(), 16).unwrap();
x >>= y.try_into().unwrap();
let as_uint = &x.to_biguint();
prop_assert!(as_uint < &p, "{}", as_uint);
}

#[test]
#[allow(deprecated)]
// Property-based test that ensures, vectors of three of values that are randomly generated each time tests are run, that performing an iterative sum returns a result that is inside of the range [0, p]. The test is performed 100 times each run.
fn sum_bigint_felt_within_field(ref x in "([1-9][0-9]*)", ref y in "([1-9][0-9]*)", ref z in "([1-9][0-9]*)") {
let x = FeltBigInt::<FIELD_HIGH, FIELD_LOW>::parse_bytes(x.as_bytes(), 10).unwrap();
Expand Down
32 changes: 29 additions & 3 deletions felt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,15 @@ impl<'a> Pow<u32> for &'a Felt {
}
}

impl<'a> Pow<Felt> for &'a Felt {
type Output = Felt;
fn pow(self, rhs: Felt) -> Self::Output {
Self::Output {
value: (&self.value).pow(rhs.value),
}
}
}

impl Div for Felt {
type Output = Self;
fn div(self, rhs: Self) -> Self {
Expand Down Expand Up @@ -729,7 +738,8 @@ macro_rules! assert_felt_impl {
fn assert_mul<T: Mul>() {}
fn assert_mul_ref<'a, T: Mul<&'a $type>>() {}
fn assert_mul_assign_ref<'a, T: MulAssign<&'a $type>>() {}
fn assert_pow<T: Pow<u32>>() {}
fn assert_pow_u32<T: Pow<u32>>() {}
fn assert_pow_felt<T: Pow<$type>>() {}
fn assert_div<T: Div>() {}
fn assert_ref_div<T: Div<$type>>() {}
fn assert_rem<T: Rem>() {}
Expand Down Expand Up @@ -779,8 +789,9 @@ macro_rules! assert_felt_impl {
assert_mul::<&$type>();
assert_mul_ref::<$type>();
assert_mul_assign_ref::<$type>();
assert_pow::<$type>();
assert_pow::<&$type>();
assert_pow_u32::<$type>();
assert_pow_u32::<&$type>();
assert_pow_felt::<&$type>();
assert_div::<$type>();
assert_div::<&$type>();
assert_ref_div::<&$type>();
Expand Down Expand Up @@ -1001,6 +1012,21 @@ mod test {
prop_assert!(as_uint < p, "{}", as_uint);
}

#[test]
// Test for sqrt of a quadratic residue. Result should be the minimum root.
fn sqrt_felt_test(ref x in "([1-9][0-9]*)") {
println!("{x}");
let x = &Felt::parse_bytes(x.as_bytes(), 10).unwrap();
let x_sq = x * x;
let sqrt = x_sq.sqrt();

if &sqrt != x {
assert_eq!(Felt::max_value() - sqrt + 1_usize, *x);
} else {
assert_eq!(&sqrt, x);
}
}

#[test]
// Property based test that ensures, for 100 pairs of values {x} and {y} generated at random each time tests are run, that performing a Sum operation between them returns a result that is inside of the range [0, p].
fn sum_in_range(ref x in "[1-9][0-9]*", ref y in "[0-9][0-9]*"){
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ impl HintProcessor for BuiltinHintProcessor {
hint_code::ASSERT_NOT_ZERO => {
assert_not_zero(vm, &hint_data.ids_data, &hint_data.ap_tracking)
}
hint_code::IS_QUAD_RESIDUE => {
is_quad_residue(vm, &hint_data.ids_data, &hint_data.ap_tracking)
}
hint_code::VM_EXIT_SCOPE => exit_scope(exec_scopes),
hint_code::MEMCPY_ENTER_SCOPE => {
memcpy_enter_scope(vm, exec_scopes, &hint_data.ids_data, &hint_data.ap_tracking)
Expand Down
9 changes: 9 additions & 0 deletions src/hint_processor/builtin_hint_processor/hint_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,15 @@ assert -ids.bound <= q < ids.bound, \

ids.biased_q = q + ids.bound"#;

pub(crate) const IS_QUAD_RESIDUE: &str = r#"from starkware.crypto.signature.signature import FIELD_PRIME
from starkware.python.math_utils import div_mod, is_quad_residue, sqrt

x = ids.x
if is_quad_residue(x, FIELD_PRIME):
ids.y = sqrt(x, FIELD_PRIME)
else:
ids.y = sqrt(div_mod(x, 3, FIELD_PRIME), FIELD_PRIME)"#;

pub(crate) const FIND_ELEMENT: &str = r#"array_ptr = ids.array_ptr
elm_size = ids.elm_size
assert isinstance(elm_size, int) and elm_size > 0, \
Expand Down
Loading