diff --git a/simf/lib/u128.simf b/simf/lib/u128.simf index 5695081..c97c7ce 100644 --- a/simf/lib/u128.simf +++ b/simf/lib/u128.simf @@ -94,12 +94,10 @@ pub fn lt_128(a: u128, b: u128) -> bool { match jet::lt_64(a_high, b_high) { true => true, - false => { - match jet::eq_64(a_high, b_high) { - true => jet::lt_64(a_low, b_low), - false => false, - } - } + false => match jet::eq_64(a_high, b_high) { + true => jet::lt_64(a_low, b_low), + false => false, + }, } } @@ -110,13 +108,11 @@ pub fn le_128(a: u128, b: u128) -> bool { match jet::lt_64(a_high, b_high) { true => true, - false => { - match jet::eq_64(a_high, b_high) { - true => jet::le_64(a_low, b_low), - false => false, - } - } - } + false => match jet::eq_64(a_high, b_high) { + true => jet::le_64(a_low, b_low), + false => false, + }, + } } /// Check if an integer is greater than another integer @@ -141,7 +137,7 @@ pub fn add_128(a: u128, b: u128) -> (bool, u128) { (carry_high, res) } -/// Adds the 128-bit integer with the 64-bit integer. Returns a tuple of the sum and the carry +/// Adds the 128-bit integer with the 64-bit integer and returns the carry pub fn add_128_64(a: u128, b: u64) -> (bool, u128) { let (a_high, a_low): (u64, u64) = ::into(a); @@ -151,6 +147,18 @@ pub fn add_128_64(a: u128, b: u64) -> (bool, u128) { (carry_high, <(u64, u64)>::into((res_high, res_low))) } +/// Adds two integers. Takes a carry-in and returns a carry-out +pub fn full_add_128(carry_in: bool, a: u128, b: u128) -> (bool, u128) { + let (a_high, a_low): (u64, u64) = ::into(a); + let (b_high, b_low): (u64, u64) = ::into(b); + + let (carry_low, sum_low): (bool, u64) = jet::full_add_64(carry_in, a_low, b_low); + let (carry_out, sum_high): (bool, u64) = jet::full_add_64(carry_low, a_high, b_high); + + let res: u128 = <(u64, u64)>::into((sum_high, sum_low)); + (carry_out, res) +} + /// Returns the sum of two u128 values wrapped in Some, or None if the result overflows u128 pub fn checked_add_128(a: u128, b: u128) -> Option { let (carry, sum): (bool, u128) = add_128(a, b); @@ -178,6 +186,18 @@ pub fn sub_128(a: u128, b: u128) -> (bool, u128) { (borrow_high, res) } +/// Subtracts the second integer from the first integer, takes a borrow-in and returns a borrow-out +pub fn full_sub_128(borrow_in: bool, a: u128, b: u128) -> (bool, u128) { + let (a_high, a_low): (u64, u64) = ::into(a); + let (b_high, b_low): (u64, u64) = ::into(b); + + let (borrow_low, diff_low): (bool, u64) = jet::full_subtract_64(borrow_in, a_low, b_low); + let (borrow_out, diff_high): (bool, u64) = jet::full_subtract_64(borrow_low, a_high, b_high); + + let res: u128 = <(u64, u64)>::into((diff_high, diff_low)); + (borrow_out, res) +} + /// Returns the difference of two u128 values wrapped in Some, or None if the result overflows u128 pub fn checked_sub_128(a: u128, b: u128) -> Option { let (borrow, diff): (bool, u128) = sub_128(a, b); @@ -248,26 +268,12 @@ pub fn safe_mul_128(a: u128, b: u128) -> u128 { unwrap(checked_mul_128(a, b)) } -/// Splits the u256 integer into four u64 integers -// TODO: Move to u256 once that module is added. -pub fn split_256_into_64(a: u256) -> ((u64, u64), (u64, u64)) { - let (high, low): (u128, u128) = ::into(a); - - (::into(high), ::into(low)) -} - -/// Helper function, can be used with jet::div_mod_128_64. -/// Normalizes two u128 values by multiplying both by the same factor, -/// ensuring that the most significant non-zero word of `b` is at least 2^63. -/// -/// If `is_b_u128` is true, expects the upper half of `b` to be non-zero. -/// If `is_b_u128` is false, expects `b` to fit into u64. -/// -/// Division algorithms operate in base 2^64, so the normalization threshold is 2^63. -pub fn normalize_to_threshold(a: u128, b: u128, is_b_u128: bool) -> (u256, u128) { - // Compile-time constant: 2^63. Avoids a runtime jet::left_shift_64 call. +/// Helper function that can be used with jet::div_mod_128_64 or Algorithm D. +/// Returns the normalization factor by which `b` should be multiplied so that +/// its most significant non-zero word is greater than or equal to 2^63 +pub fn calculate_normalizer_base_64(b: u128, is_b_u128: bool) -> u64 { + // Compile-time constant: 2^63. Avoids a runtime jet::left_shift_64 call let threshold: u64 = 0x8000000000000000; - let (b_high, b_low): (u64, u64) = ::into(b); let b_highest_word: u64 = match is_b_u128 { @@ -281,43 +287,48 @@ pub fn normalize_to_threshold(a: u128, b: u128, is_b_u128: bool) -> (u256, u128) let (norm, remainder): (u64, u64) = jet::div_mod_64(threshold, b_highest_word); - let norm: u64 = match jet::is_zero_64(remainder) { + match jet::is_zero_64(remainder) { true => norm, false => { let (_, norm): (bool, u64) = jet::add_64(norm, 1); // norm <= 2^63, so norm + 1 can not overflow norm } - }; - let norm: u128 = <(u64, u64)>::into((0, norm)); - - match jet::lt_64(b_highest_word, threshold) { - true => (mul_128(a, norm), safe_mul_128(b, norm)), - false => (<(u128, u128)>::into((0, a)), b), } } -/// Divides the first u128 integer by the second u128 integer, -/// returns the u64 quotient and the u128 remainder. -/// Implements Algorithm D by Donald Knuth. -/// Requires the upper half of the divisor to be non-zero. -pub fn algorithm_d(dividend: u128, divisor: u128) -> (u64, u128) { - let (norm_dividend, norm_divisor): (u256, u128) = normalize_to_threshold(dividend, divisor, true); - - // normalized dividend fits into 192 bits - let ((_, u2), (u1, u0)): ((u64, u64), (u64, u64)) = split_256_into_64(norm_dividend); - let (v1, v0): (u64, u64) = ::into(norm_divisor); +/// Helper function, can be used with jet::div_mod_128_64 or Algorithm D. +/// Normalizes two u128 values by multiplying both by the same factor, +/// ensuring that the most significant non-zero word of `b` is at least 2^63. +/// +/// If `is_b_u128` is true, expects the upper half of `b` to be non-zero. +/// If `is_b_u128` is false, expects `b` to fit into u64. +/// +/// Division algorithms operate in base 2^64, so the normalization threshold is 2^63 +fn normalize_to_threshold_128_63(a: u128, b: u128, is_b_u128: bool) -> (u256, u128, u64) { + let norm: u64 = calculate_normalizer_base_64(b, is_b_u128); + let norm_128: u128 = <(u64, u64)>::into((0, norm)); + + match jet::eq_64(norm, 1) { + true => (<(u128, u128)>::into((0, a)), b, norm), + false => (mul_128(a, norm_128), safe_mul_128(b, norm_128), norm), + } +} +/// Estimates and corrects the next quotient digit (q_hat) for Algorithm D. +/// Returns the quotient digit to use in the subsequent multiply-and-subtract step. +/// Expects result to fit into u64 +pub fn estimate_quotient_digit_base_64(u2: u64, u1: u64, u0: u64, v1: u64, v0: u64) -> u64 { let (q_hat, r_hat): (u64, u64) = jet::div_mod_128_64(<(u64, u64)>::into((u2, u1)), v1); let r_hat_u0: u128 = <(u64, u64)>::into((r_hat, u0)); // correcting estimation: q_hat is off by at most 2. - let q: u64 = match lt_128(r_hat_u0, jet::multiply_64(q_hat, v0)) { + match lt_128(r_hat_u0, jet::multiply_64(q_hat, v0)) { true => { // can not overflow because r_hat_u0 < q_hat * v0, so q_hat is at least 1 let (_, q_hat): (bool, u64) = jet::subtract_64(q_hat, 1); let (carry, r_hat): (bool, u64) = jet::add_64(r_hat, v1); - + match carry { true => q_hat, false => { @@ -327,17 +338,30 @@ pub fn algorithm_d(dividend: u128, divisor: u128) -> (u64, u128) { true => { // can not overflow because r_hat_u0 < q_hat * v0, so q_hat is at least 1 let (_, q_hat): (bool, u64) = jet::subtract_64(q_hat, 1); - + q_hat } false => q_hat, } } - } }, false => q_hat, - }; + } +} + +/// Divides the first u128 integer by the second u128 integer, +/// returns the u64 quotient and the u128 remainder. +/// Implements Algorithm D by Donald Knuth. +/// Requires the upper half of the divisor to be non-zero. +fn algorithm_d_128_128(dividend: u128, divisor: u128) -> (u64, u128) { + let (norm_dividend, norm_divisor, _): (u256, u128, u64) = normalize_to_threshold_128_63(dividend, divisor, true); + + // normalized dividend fits into 192 bits + let (_, u2, u1, u0): (u64, u64, u64, u64) = ::into(norm_dividend); + let (v1, v0): (u64, u64) = ::into(norm_divisor); + + let q: u64 = estimate_quotient_digit_base_64(u2, u1, u0, v1, v0); let remainder: u128 = safe_sub_128(dividend, safe_mul_128(divisor, <(u64, u64)>::into((0, q)))); (q, remainder) @@ -355,16 +379,15 @@ pub fn div_mod_128_64(a: u128, b: u64) -> (u128, u64) { let a_prime: u128 = <(u64, u64)>::into((remainder, a_low)); // we need to normalize here, because jet::div_mod_128_64 only accepts b >= 2^63 - let (a_normalized, b_normalized): (u256, u128) = normalize_to_threshold(a_prime, <(u64, u64)>::into((0, b)), false); + let (a_normalized, b_normalized, norm): (u256, u128, u64) = normalize_to_threshold_128_63(a_prime, <(u64, u64)>::into((0, b)), false); // a_normalized fits into u128, because remainder < b and b_normalized fits into u64 let (_, a_normalized): (u128, u128) = ::into(a_normalized); let (_, b_normalized): (u64, u64) = ::into(b_normalized); // remainder < b, so (remainder * 2^64 + a_low) / b fits into u64 - let (q_low, _): (u64, u64) = jet::div_mod_128_64(a_normalized, b_normalized); // remainder is not valid here due to normalizing - - let (_, remainder): (u64, u64) = ::into(safe_sub_128(a_prime, jet::multiply_64(q_low, b))); + let (q_low, r_normalized): (u64, u64) = jet::div_mod_128_64(a_normalized, b_normalized); + let remainder: u64 = jet::divide_64(r_normalized, norm); (<(u64, u64)>::into((q_high, q_low)), remainder) } @@ -399,7 +422,7 @@ pub fn div_mod_128(a: u128, b: u128) -> (u128, u128) { (q, <(u64, u64)>::into((0, r))) }, false => { - let (q, r): (u64, u128) = algorithm_d(a, b); + let (q, r): (u64, u128) = algorithm_d_128_128(a, b); (<(u64, u64)>::into((0, q)), r) } } diff --git a/simf/lib/u256.simf b/simf/lib/u256.simf new file mode 100644 index 0000000..e3addab --- /dev/null +++ b/simf/lib/u256.simf @@ -0,0 +1,513 @@ +use crate::lib::binary::{not, and}; +use crate::lib::u128::{ + and_128, + or_128, + eq_128, + left_shift_128, + right_shift_128, + is_zero_128, + lt_128, + le_128, + add_128, + full_add_128, + sub_128, + full_sub_128, + mul_128, + calculate_normalizer_base_64, + estimate_quotient_digit_base_64, + div_mod_128, + div_mod_128_64 +}; +use crate::lib::u64::{u64_into_u256}; + +/// Bit logic + +/// Bitwise AND of two 256-bit values +pub fn and_256(a: u256, b: u256) -> u256 { + let (a_high, a_low): (u128, u128) = ::into(a); + let (b_high, b_low): (u128, u128) = ::into(b); + + <(u128, u128)>::into((and_128(a_high, b_high), and_128(a_low, b_low))) +} + +/// Bitwise OR of two 256-bit values +pub fn or_256(a: u256, b: u256) -> u256 { + let (a_high, a_low): (u128, u128) = ::into(a); + let (b_high, b_low): (u128, u128) = ::into(b); + + <(u128, u128)>::into((or_128(a_high, b_high), or_128(a_low, b_low))) +} + +/// Left-shift a 256-bit value by the given amount. Bits are filled with zeroes +pub fn left_shift_256(shift: u8, a: u256) -> u256 { + match jet::is_zero_8(shift) { + true => a, + false => { + let (a_high, a_low): (u128, u128) = ::into(a); + + match jet::lt_8(shift, 128) { + true => { + let (_, low_to_high_amount): (bool, u8) = jet::subtract_8(128, shift); // shift < 128 + let shifted_bits: u128 = right_shift_128(low_to_high_amount, a_low); + + let res_high: u128 = or_128(left_shift_128(shift, a_high), shifted_bits); + + <(u128, u128)>::into((res_high, left_shift_128(shift, a_low))) + }, + false => { + let (_, shift): (bool, u8) = jet::subtract_8(shift, 128); // shift >= 128 + + <(u128, u128)>::into((left_shift_128(shift, a_low), 0)) + } + } + } + } +} + +/// Right-shift a 256-bit value by the given amount. Bits are filled with zeroes +pub fn right_shift_256(shift: u8, a: u256) -> u256 { + match jet::is_zero_8(shift) { + true => a, + false => { + let (a_high, a_low): (u128, u128) = ::into(a); + + match jet::lt_8(shift, 128) { + true => { + let (_, high_to_low_amount): (bool, u8) = jet::subtract_8(128, shift); // shift < 128 + let shifted_bits: u128 = left_shift_128(high_to_low_amount, a_high); + + let res_low: u128 = or_128(right_shift_128(shift, a_low), shifted_bits); + + <(u128, u128)>::into((right_shift_128(shift, a_high), res_low)) + }, + false => { + let (_, shift): (bool, u8) = jet::subtract_8(shift, 128); // shift >= 128 + + <(u128, u128)>::into((0, right_shift_128(shift, a_high))) + } + } + } + } +} + +/// Arithmetic + +/// Checks if an integer is zero +pub fn is_zero_256(a: u256) -> bool { + let (a_high, a_low): (u128, u128) = ::into(a); + + and(is_zero_128(a_high), is_zero_128(a_low)) +} + +/// Checks if an integer is less than another integer +pub fn lt_256(a: u256, b: u256) -> bool { + let (a_high, a_low): (u128, u128) = ::into(a); + let (b_high, b_low): (u128, u128) = ::into(b); + + match lt_128(a_high, b_high) { + true => true, + false => match eq_128(a_high, b_high) { + true => lt_128(a_low, b_low), + false => false, + }, + } +} + +/// Checks if an integer is less than or equal to another integer +pub fn le_256(a: u256, b: u256) -> bool { + let (a_high, a_low): (u128, u128) = ::into(a); + let (b_high, b_low): (u128, u128) = ::into(b); + + match lt_128(a_high, b_high) { + true => true, + false => match eq_128(a_high, b_high) { + true => le_128(a_low, b_low), + false => false, + }, + } +} + +/// Check if an integer is greater than another integer +pub fn gt_256(a: u256, b: u256) -> bool { + lt_256(b, a) +} + +/// Check if an integer is greater than or equal to another integer +pub fn ge_256(a: u256, b: u256) -> bool { + le_256(b, a) +} + +/// Splits the u256 integer into four u64 integers +pub fn split_256_into_64(a: u256) -> (u64, u64, u64, u64) { + ::into(a) +} + +/// Adds two integers and returns the carry +pub fn add_256(a: u256, b: u256) -> (bool, u256) { + let (a_high, a_low): (u128, u128) = ::into(a); + let (b_high, b_low): (u128, u128) = ::into(b); + + let (carry_low, sum_low): (bool, u128) = add_128(a_low, b_low); + let (carry_high, sum_high): (bool, u128) = full_add_128(carry_low, a_high, b_high); + + let res: u256 = <(u128, u128)>::into((sum_high, sum_low)); + (carry_high, res) +} + +/// Adds the 128-bit integer with the 64-bit integer and returns the carry +pub fn add_256_128(a: u256, b: u128) -> (bool, u256) { + let b: u256 = <(u128, u128)>::into((0, b)); + + add_256(a, b) +} + +/// Returns the sum of two u256 values wrapped in Some, or None if the result overflows u256 +pub fn checked_add_256(a: u256, b: u256) -> Option { + let (carry, sum): (bool, u256) = add_256(a, b); + + match carry { + true => None, + false => Some(sum), + } +} + +/// Returns the sum of two u256 values, panics if the result overflows u256 +pub fn safe_add_256(a: u256, b: u256) -> u256 { + unwrap(checked_add_256(a, b)) +} + +/// Subtracts the second integer from the first integer, and returns the borrow bit +pub fn sub_256(a: u256, b: u256) -> (bool, u256) { + let (a_high, a_low): (u128, u128) = ::into(a); + let (b_high, b_low): (u128, u128) = ::into(b); + + let (borrow_low, diff_low): (bool, u128) = sub_128(a_low, b_low); + let (borrow_high, diff_high): (bool, u128) = full_sub_128(borrow_low, a_high, b_high); + + let res: u256 = <(u128, u128)>::into((diff_high, diff_low)); + (borrow_high, res) +} + +/// Returns the difference of two u256 values wrapped in Some, or None if the result overflows u256 +pub fn checked_sub_256(a: u256, b: u256) -> Option { + let (borrow, diff): (bool, u256) = sub_256(a, b); + + match borrow { + true => None, + false => Some(diff), + } +} + +/// Returns the difference of two u256 values, panics if the result overflows u256 +pub fn safe_sub_256(a: u256, b: u256) -> u256 { + unwrap(checked_sub_256(a, b)) +} + +/// Multiply two integers. The output is two 256-bit integers +/// The idea is that u256-bit `a` divides into 128-bit `a_high` and `a_low`, +/// so a = a_high * 2^128 + a_low. +/// In the same way, b = b_high * 2^128 + b_low. +/// Therefore, a * b = 2^256 * a_high * b_high + 2^128(a_high * b_low + a_low * b_high) + a_low * b_low. +pub fn mul_256(a: u256, b: u256) -> (u256, u256) { + let (a_high, a_low): (u128, u128) = ::into(a); + let (b_high, b_low): (u128, u128) = ::into(b); + + let highest: u256 = mul_128(a_high, b_high); + let lowest: u256 = mul_128(a_low, b_low); + let (word_1, word_0): (u128, u128) = ::into(lowest); + let (word_3, word_2): (u128, u128) = ::into(highest); + + let product_1: u256 = mul_128(a_high, b_low); + let product_2: u256 = mul_128(b_high, a_low); + let (carry_3a, middle): (bool, u256) = add_256(product_1, product_2); + + let (middle_2, middle_1): (u128, u128) = ::into(middle); + + // fold the low-side carry directly into the word_2 addition via full_add_128, + // then propagate any resulting carry into word_3 + let (carry_2, res_1): (bool, u128) = add_128(word_1, middle_1); + let (carry_3b, res_2): (bool, u128) = full_add_128(carry_2, word_2, middle_2); + + // `word_3` is the upper half of a_high * b_high. It is at most `u128::MAX - 1` when + // either factor is `u128::MAX`, and even in the extreme case where + // a == b == u256::MAX, the total product still fits into two u256. + // Therefore, word_3 + carry_3a + carry_3b can not overflow, and `add_128` + // is used instead of `safe_add_128` to avoid the unnecessary overflow check + let (_, res_3a): (bool, u128) = full_add_128(carry_3a, word_3, 0); + let (_, res_3): (bool, u128) = full_add_128(carry_3b, res_3a, 0); + + let res_1_0: u256 = <(u128, u128)>::into((res_1, word_0)); + let res_3_2: u256 = <(u128, u128)>::into((res_3, res_2)); + + (res_3_2, res_1_0) +} + +/// Returns the product of two u256 values wrapped in Some, or None if the result overflows u256 +pub fn checked_mul_256(a: u256, b: u256) -> Option { + let (result_high, result_low): (u256, u256) = mul_256(a, b); + + match is_zero_256(result_high) { + true => Some(result_low), + false => None, + } +} + +/// Returns the product of two u256 values, panics if the result overflows u256 +pub fn safe_mul_256(a: u256, b: u256) -> u256 { + unwrap(checked_mul_256(a, b)) +} + +/// Normalizes the dividend and divisor for Algorithm D by multiplying +/// both u256 and u128 by the same factor, ensuring that +/// the most significant non-zero word of `b` is at least 2^63. +fn normalize_to_threshold_256_63(a: u256, b: u128, is_b_u128: bool) -> (u256, u256, u128, u64) { + let norm: u64 = calculate_normalizer_base_64(b, is_b_u128); + let norm_128: u128 = <(u64, u64)>::into((0, norm)); + + match jet::eq_64(norm, 1) { + true => (0, a, b, norm), + false => { + let (high, low): (u256, u256) = mul_256(a, <(u128, u128)>::into((0, norm_128))); + let b_norm: u256 = mul_128(b, norm_128); + let (_, b_norm): (u128, u128) = ::into(b_norm); + + (high, low, b_norm, norm) + }, + } +} + +/// Normalizes the dividend and divisor for Algorithm D by multiplying +/// both u256 values by the same factor, ensuring that +/// the most significant non-zero word of `b` is at least 2^127. +fn normalize_to_threshold_256_127(a: u256, b: u256, is_b_u256: bool) -> (u256, u256, u256) { + // Compile-time constant: 2^127 + let threshold: u128 = 0x80000000000000000000000000000000; + + let (b_high, b_low): (u128, u128) = ::into(b); + + let b_highest_word: u128 = match is_b_u256 { + true => b_high, + false => { + assert!(is_zero_128(b_high)); + b_low + } + }; + assert!(not(is_zero_128(b_highest_word))); + + let (norm, remainder): (u128, u128) = div_mod_128(threshold, b_highest_word); + + let norm: u128 = match is_zero_128(remainder) { + true => norm, + false => { + let (_, norm): (bool, u128) = add_128(norm, 1); // norm <= 2^127, so norm + 1 can not overflow + norm + } + }; + let norm: u256 = <(u128, u128)>::into((0, norm)); + + match lt_128(b_highest_word, threshold) { + true => { + let (high, low): (u256, u256) = mul_256(a, norm); + + (high, low, safe_mul_256(b, norm)) + }, + false => (0, a, b), + } +} + +/// Divides the 256-bit integer by the 64-bit integer, +/// returns a tuple of the u256 quotient and the u64 remainder +pub fn div_mod_256_64(dividend: u256, divisor: u64) -> (u256, u64) { + let (u3, u2, u1, u0): (u64, u64, u64, u64) = split_256_into_64(dividend); + + // calculate the upper part of the quotient + let (q3, remainder): (u64, u64) = jet::div_mod_64(u3, divisor); + + let divisor: u128 = <(u64, u64)>::into((0, divisor)); + let dividend: u256 = <(u64, u64, u64, u64)>::into((remainder, u2, u1, u0)); + + let (norm_dividend_high, norm_dividend_low, norm_divisor, norm): (u256, u256, u128, u64) = + normalize_to_threshold_256_63(dividend, divisor, false); + + // normalized dividend fits into 256 bits + let (u3, u2, u1, u0): (u64, u64, u64, u64) = split_256_into_64(norm_dividend_low); + let (_, v0): (u64, u64) = ::into(norm_divisor); + + let (q2, remainder): (u64, u64) = jet::div_mod_128_64( <(u64, u64)>::into((u3, u2)), v0); + let (q1, remainder): (u64, u64) = jet::div_mod_128_64( <(u64, u64)>::into((remainder, u1)), v0); + let (q0, remainder): (u64, u64) = jet::div_mod_128_64( <(u64, u64)>::into((remainder, u0)), v0); + + let remainder: u64 = jet::divide_64(remainder, norm); + let q: u256 = <(u64, u64, u64, u64)>::into(( q3, q2, q1, q0)); + + (q, remainder) +} + +/// Multiplies the divisor by the quotient digit and subtracts the result from +/// the corresponding dividend limbs. Returns the updated dividend segment, +/// which is used by Algorithm D +fn mul_and_sub(q: u64, u2: u64, u1: u64, u0: u64, v: u128) -> (u64, u64) { + let u: u256 = <(u64, u64, u64, u64)>::into((0, u2, u1, u0)); + + let q_v: u256 = mul_128(<(u64, u64)>::into((0, q)), v); + + let u_updated: u256 = safe_sub_256(u, q_v); + let (_, _, u1, u0): (u64, u64, u64, u64) = split_256_into_64(u_updated); + + (u1, u0) +} + +/// Divides the 256-bit integer by the 128-bit integer, +/// returns a tuple of the u256 quotient and the u128 remainder. +/// Implements Algorithm D by Donald Knuth. +/// Requires the upper half of the divisor to be non-zero (divisor >= 2^64) +fn algorithm_d_256_128(dividend: u256, divisor: u128) -> (u256, u128) { + let (norm_dividend_high, norm_dividend_low, norm_divisor, norm): (u256, u256, u128, u64) = + normalize_to_threshold_256_63(dividend, divisor, true); + + // normalized dividend fits into 320 bits + let (_, _, _, u4): (u64, u64, u64, u64) = split_256_into_64(norm_dividend_high); + let (u3, u2, u1, u0): (u64, u64, u64, u64) = split_256_into_64(norm_dividend_low); + let (v1, v0): (u64, u64) = ::into(norm_divisor); + + let q2: u64 = estimate_quotient_digit_base_64(u4, u3, u2, v1, v0); + let (u3, u2): (u64, u64) = mul_and_sub(q2, u4, u3, u2, norm_divisor); + + let q1: u64 = estimate_quotient_digit_base_64(u3, u2, u1, v1, v0); + let (u2, u1): (u64, u64) = mul_and_sub(q1, u3, u2, u1, norm_divisor); + + let q0: u64 = estimate_quotient_digit_base_64(u2, u1, u0, v1, v0); + let (u1, u0): (u64, u64) = mul_and_sub(q0, u2, u1, u0, norm_divisor); + + let q: u256 = <(u64, u64, u64, u64)>::into((0, q2, q1, q0)); + let (remainder, _): (u128, u64) = div_mod_128_64(<(u64, u64)>::into((u1, u0)), norm); + + (q, remainder) +} + +/// Divides the 256-bit integer by the 128-bit integer, +/// returns a tuple of the u256 quotient and the u128 remainder +pub fn div_mod_256_128(dividend: u256, divisor: u128) -> (u256, u128) { + let (divisor_high, divisor_low): (u64, u64) = ::into(divisor); + + match jet::is_zero_64(divisor_high) { + true => { + let (q, r): (u256, u64) = div_mod_256_64(dividend, divisor_low); + + (q, <(u64, u64)>::into((0, r))) + }, + false => algorithm_d_256_128(dividend, divisor), + } +} + +/// Divides the first u256 integer by the second u256 integer, +/// returns the u128 quotient and the u256 remainder. +/// Implements Algorithm D by Donald Knuth. +/// Requires the upper half of the divisor to be non-zero (divisor >= 2^128). +fn algorithm_d_256_256(dividend: u256, divisor: u256) -> (u128, u256) { + let (norm_dividend_high, norm_dividend_low, norm_divisor): (u256, u256, u256) = normalize_to_threshold_256_127(dividend, divisor, true); + + // normalized dividend fits into 384 bits + let (_, u2): (u128, u128) = ::into(norm_dividend_high); + let (u1, u0): (u128, u128) = ::into(norm_dividend_low); + let (v1, v0): (u128, u128) = ::into(norm_divisor); + + let (q_hat, r_hat): (u256, u128) = algorithm_d_256_128(<(u128, u128)>::into((u2, u1)), v1); + let( _, q_hat): (u128, u128) = ::into(q_hat); + + let r_hat_u0: u256 = <(u128, u128)>::into((r_hat, u0)); + let u_hat: u256 = mul_128(q_hat, v0); + + // correcting estimation: q_hat is off by at most 2. + let q: u128 = match lt_256(r_hat_u0, u_hat) { + true => { + // can not overflow because r_hat_u0 < q_hat * v0, so q_hat is at least 1 + let (_, q_hat): (bool, u128) = sub_128(q_hat, 1); + let (carry, r_hat): (bool, u128) = add_128(r_hat, v1); + + match carry { + true => q_hat, + false => { + let r_hat_u0: u256 = <(u128, u128)>::into((r_hat, u0)); + let (_, u_hat): (bool, u256) = sub_256(u_hat, <(u128, u128)>::into((0, v0))); + + match lt_256(r_hat_u0, u_hat) { + true => { + // can not overflow because r_hat_u0 < q_hat * v0, so q_hat is at least 1 + let (_, q_hat): (bool, u128) = sub_128(q_hat, 1); + + q_hat + } + false => q_hat, + } + } + } + }, + false => q_hat, + }; + + let remainder: u256 = safe_sub_256(dividend, safe_mul_256(divisor, <(u128, u128)>::into((0, q)))); + (q, remainder) +} + +/// Divides the first integer by the second integer, +/// returns the quotient and the remainder +pub fn div_mod_256(a: u256, b: u256) -> (u256, u256) { + let (a_high, a_low): (u128, u128) = ::into(a); + let (b_high, b_low): (u128, u128) = ::into(b); + + match lt_256(a, b) { + true => (0, a), + false => { + match and(is_zero_128(a_high), is_zero_128(b_high)) { + true => { + // if both a_high and b_high are zero, this narrows down to 128-bit division + let (q, r): (u128, u128) = div_mod_128(a_low, b_low); + (<(u128, u128)>::into((0, q)), <(u128, u128)>::into((0, r))) + }, + false => { + match eq_128(a_high, b_high) { + true => { + // safe: !lt_256(a, b) and a_high == b_high, so a_low >= b_low, + // and the subtraction can not underflow + let (_, diff): (bool, u128) = sub_128(a_low, b_low); + (1, <(u128, u128)>::into((0, diff))) + }, + false => { + match is_zero_128(b_high) { + true => { + let (q, r): (u256, u128) = div_mod_256_128(a, b_low); + (q, <(u128, u128)>::into((0, r))) + }, + false => { + let (q, r): (u128, u256) = algorithm_d_256_256(a, b); + (<(u128, u128)>::into((0, q)), r) + } + } + } + } + } + } + } + } +} + +/// Divide the first integer by the second integer, returns the quotient +pub fn div_256(a: u256, b: u256) -> u256 { + let (q, _): (u256, u256) = div_mod_256(a, b); + + q +} + +/// Returns the quotient of two u256 values wrapped in Some, or None if the result overflows u256 +pub fn checked_div_256(a: u256, b: u256) -> Option { + match is_zero_256(b) { + true => None, + false => Some(div_256(a, b)), + } +} + +/// Returns the quotient of two u256 values, panics if the result overflows u256 +pub fn safe_div_256(a: u256, b: u256) -> u256 { + unwrap(checked_div_256(a, b)) +} diff --git a/simf/u128_test.simf b/simf/u128_test.simf index cbaeedf..15b99fe 100644 --- a/simf/u128_test.simf +++ b/simf/u128_test.simf @@ -1,4 +1,16 @@ -use crate::lib::u128::{ checked_add_128, safe_add_128, checked_sub_128, safe_sub_128, checked_mul_128, safe_mul_128, checked_div_128, safe_div_128, eq_128, gt_128, ge_128 }; +use crate::lib::u128::{ + checked_add_128, + safe_add_128, + checked_sub_128, + safe_sub_128, + checked_mul_128, + safe_mul_128, + checked_div_128, + safe_div_128, + eq_128, + gt_128, + ge_128 +}; use crate::lib::asserts::{assert_none_128, assert_eq_128}; use crate::lib::binary::not; use crate::helper::if_test_this_function; @@ -8,8 +20,8 @@ use crate::helper::if_test_this_function; /// witness value carries both, removing the need for a separate overflow flag. fn assert_eq_opt(result: Option, expected: Option) { match expected { - None => assert_none_128(result), Some(e: u128) => assert_eq_128(unwrap(result), e), + None => assert_none_128(result), } } diff --git a/simf/u128_test_arithmetic.simf b/simf/u128_test_arithmetic.simf index a95ee18..28902c7 100644 --- a/simf/u128_test_arithmetic.simf +++ b/simf/u128_test_arithmetic.simf @@ -1,4 +1,20 @@ -use crate::lib::u128::{ eq_128, is_zero_128, lt_128, le_128, add_128, add_128_64, sub_128, mul_128, split_256_into_64, normalize_to_threshold, algorithm_d, div_mod_128_64, div_mod_128, div_128 }; +use crate::lib::u128::{ + eq_128, + is_zero_128, + lt_128, + le_128, + add_128, + add_128_64, + full_add_128, + sub_128, + full_sub_128, + mul_128, + calculate_normalizer_base_64, + estimate_quotient_digit_base_64, + div_mod_128_64, + div_mod_128, + div_128 +}; use crate::helper::{if_test_this_function, assert_bool}; /// Asserts a result equals expected u128 and bool values. @@ -7,7 +23,7 @@ fn assert_eq_uint_bool(result: (bool, u128), expected: u128, expected_bool: bool let (bool_res, uint_res): (bool, u128) = result; assert_bool(bool_res, expected_bool); - + assert!(eq_128(uint_res, expected)); } @@ -30,62 +46,50 @@ fn main() { match if_test_this_function(3, fn_idx) { true => { assert_eq_uint_bool(add_128(a, b), unwrap(expected), expected_bool); }, false => (), }; match if_test_this_function(4, fn_idx) { true => { let (_, b): (u64, u64) = ::into(b); assert_eq_uint_bool(add_128_64(a, b), unwrap(expected), expected_bool); }, false => (), }; - match if_test_this_function(5, fn_idx) { true => { assert_eq_uint_bool(sub_128(a, b), unwrap(expected), expected_bool); }, false => (), }; + match if_test_this_function(5, fn_idx) { true => { assert_eq_uint_bool(full_add_128(eq_128(second_expected, 1), a, b), unwrap(expected), expected_bool); }, false => (), }; + + match if_test_this_function(6, fn_idx) { true => { assert_eq_uint_bool(sub_128(a, b), unwrap(expected), expected_bool); }, false => (), }; + match if_test_this_function(7, fn_idx) { true => { assert_eq_uint_bool(full_sub_128(eq_128(second_expected, 1), a, b), unwrap(expected), expected_bool); }, false => (), }; - match if_test_this_function(6, fn_idx) { + match if_test_this_function(8, fn_idx) { true => { let result: u256 = mul_128(a, b); let (result_high, result_low): (u128, u128) = ::into(result); - + assert!(eq_128(result_high, unwrap(expected))); assert!(eq_128(result_low, second_expected)); }, false => (), }; - match if_test_this_function(7, fn_idx) { - true => { - let input: u256 = <(u128, u128)>::into((a, b)); - let ((res1, res2), (res3, res4)): ((u64, u64), (u64, u64)) = split_256_into_64(input); - - let (expected1, expected2): (u64, u64) = ::into(unwrap(expected)); - let (expected3, expected4): (u64, u64) = ::into(second_expected); - - assert!(jet::eq_64(res1, expected1)); - assert!(jet::eq_64(res2, expected2)); - assert!(jet::eq_64(res3, expected3)); - assert!(jet::eq_64(res4, expected4)); - }, - false => (), - }; - - match if_test_this_function(8, fn_idx) { + match if_test_this_function(9, fn_idx) { true => { - let (result_a, result_b): (u256, u128) = normalize_to_threshold(a, b, expected_bool); + let norm: u64 = calculate_normalizer_base_64(b, expected_bool); - let (result_a_high, result_a_low): (u128, u128) = ::into(result_a); + let (_, expected_norm): (u64, u64) = ::into(unwrap(expected)); - assert!(eq_128(result_a_high, unwrap(expected))); - assert!(eq_128(result_a_low, second_expected)); - assert!(eq_128(result_b, third_expected)); + assert!(jet::eq_64(norm, expected_norm)); }, false => (), }; - match if_test_this_function(9, fn_idx) { + match if_test_this_function(10, fn_idx) { true => { - let (_, expected_q): (u64, u64) = ::into(unwrap(expected)); + let (_, u2): (u64, u64) = ::into(a); + let (u1, u0): (u64, u64) = ::into(b); + let (v1, v0): (u64, u64) = ::into(second_expected); + + let q: u64 = estimate_quotient_digit_base_64(u2, u1, u0, v1, v0); - let (q, r): (u64, u128) = algorithm_d(a, b); + let (_, expected_q): (u64, u64) = ::into(unwrap(expected)); assert!(jet::eq_64(q, expected_q)); - assert!(eq_128(r, second_expected)); }, false => (), }; - match if_test_this_function(10, fn_idx) { + match if_test_this_function(11, fn_idx) { true => { let (_, b): (u64, u64) = ::into(b); let (_, expected_r): (u64, u64) = ::into(second_expected); @@ -98,7 +102,7 @@ fn main() { false => (), }; - match if_test_this_function(11, fn_idx) { + match if_test_this_function(12, fn_idx) { true => { let (q, r): (u128, u128) = div_mod_128(a, b); @@ -108,5 +112,5 @@ fn main() { false => (), }; - match if_test_this_function(12, fn_idx) { true => { assert!(eq_128(div_128(a, b), unwrap(expected))); }, false => (), }; + match if_test_this_function(13, fn_idx) { true => { assert!(eq_128(div_128(a, b), unwrap(expected))); }, false => (), }; } diff --git a/simf/u128_test_bits.simf b/simf/u128_test_bits.simf index 725cad9..d492b02 100644 --- a/simf/u128_test_bits.simf +++ b/simf/u128_test_bits.simf @@ -1,4 +1,4 @@ -use crate::lib::u128::{ and_128, or_128, eq_128, left_shift_128, right_shift_128 }; +use crate::lib::u128::{and_128, or_128, eq_128, left_shift_128, right_shift_128}; use crate::helper::{if_test_this_function, assert_bool}; fn main() { @@ -9,7 +9,7 @@ fn main() { let expected: Option = witness::EXPECTED; let expected_bool: bool = witness::EXPECTED_BOOL; - + /// Bit logic match if_test_this_function(0, fn_idx) { true => { assert!(eq_128(and_128(a, b), unwrap(expected))); }, false => (), }; diff --git a/simf/u1_convert_test.simf b/simf/u1_convert_test.simf index a432dd9..fff7a92 100644 --- a/simf/u1_convert_test.simf +++ b/simf/u1_convert_test.simf @@ -26,7 +26,7 @@ fn main() { assert!(jet::eq_8(u1_to_u8(a), expected)); }, - false => {}, + false => (), }; match if_test_this_function(1, fn_idx) { @@ -36,7 +36,7 @@ fn main() { assert!(jet::eq_16(u1_to_u16(a), expected)); }, - false => {}, + false => (), }; match if_test_this_function(2, fn_idx) { @@ -46,7 +46,7 @@ fn main() { assert!(jet::eq_32(u1_to_u32(a), expected)); }, - false => {}, + false => (), }; match if_test_this_function(3, fn_idx) { @@ -55,7 +55,7 @@ fn main() { assert!(jet::eq_64(u1_to_u64(a), expected)); }, - false => {}, + false => (), }; match if_test_this_function(4, fn_idx) { @@ -64,14 +64,14 @@ fn main() { assert!(eq_128(u1_to_u128(a), expected)); }, - false => {}, + false => (), }; match if_test_this_function(5, fn_idx) { true => { assert!(jet::eq_256(u1_to_u256(a), expected)); }, - false => {}, + false => (), }; match if_test_this_function(6, fn_idx) { @@ -79,6 +79,6 @@ fn main() { let expected_bool: bool = jet::eq_256(expected, 1); assert_eq_bool(u1_to_bool(a), expected_bool); }, - false => {}, + false => (), }; } diff --git a/simf/u256_test.simf b/simf/u256_test.simf new file mode 100644 index 0000000..3cb186d --- /dev/null +++ b/simf/u256_test.simf @@ -0,0 +1,64 @@ +use crate::lib::u256::{ + checked_add_256, + safe_add_256, + checked_sub_256, + safe_sub_256, + checked_mul_256, + safe_mul_256, + checked_div_256, + safe_div_256, + gt_256, + ge_256 +}; +use crate::lib::asserts::{assert_none_256, assert_eq_256}; +use crate::lib::binary::not; +use crate::helper::if_test_this_function; + +/// Asserts a `checked_*` result equals the expected Option. +/// `None` encodes the overflow case, `Some(e)` the fitting case, so a single +/// witness value carries both, removing the need for a separate overflow flag. +fn assert_eq_opt(result: Option, expected: Option) { + match expected { + Some(e: u256) => assert_eq_256(unwrap(result), e), + None => assert_none_256(result), + } +} + +/// Asserts a result equals the expected bool value. +/// `None` encodes `false`, and `Some(_)` encodes `true`. +fn assert_bool_by_opt(result: bool, expected: Option) { + match expected { + Some(_: u256) => assert!(result), + None => assert!(not(result)), + } +} + +fn main() { + let fn_idx: u8 = witness::FUNCTION_INDEX; + + let a: u256 = witness::FIRST_ARG; + let b: u256 = witness::SECOND_ARG; + let expected: Option = witness::EXPECTED; + + /// Safe functions + + // add + match if_test_this_function(0, fn_idx) { true => { assert_eq_opt(checked_add_256(a, b), expected); }, false => (), }; + match if_test_this_function(1, fn_idx) { true => {assert_eq_256(safe_add_256(a, b), unwrap(expected)); }, false => (), }; + + // sub + match if_test_this_function(2, fn_idx) { true => { assert_eq_opt(checked_sub_256(a, b), expected); }, false => (), }; + match if_test_this_function(3, fn_idx) { true => {assert_eq_256(safe_sub_256(a, b), unwrap(expected)); }, false => (), }; + + // mul + match if_test_this_function(4, fn_idx) { true => { assert_eq_opt(checked_mul_256(a, b), expected); }, false => (), }; + match if_test_this_function(5, fn_idx) { true => {assert_eq_256(safe_mul_256(a, b), unwrap(expected)); }, false => (), }; + + // div + match if_test_this_function(6, fn_idx) { true => { assert_eq_opt(checked_div_256(a, b), expected); }, false => (), }; + match if_test_this_function(7, fn_idx) { true => {assert_eq_256(safe_div_256(a, b), unwrap(expected)); }, false => (), }; + + // gt, ge + match if_test_this_function(8, fn_idx) { true => { assert_bool_by_opt(gt_256(a, b), expected); }, false => (), }; + match if_test_this_function(9, fn_idx) { true => { assert_bool_by_opt(ge_256(a, b), expected); }, false => (), }; +} diff --git a/simf/u256_test_bits.simf b/simf/u256_test_bits.simf new file mode 100644 index 0000000..cc8e004 --- /dev/null +++ b/simf/u256_test_bits.simf @@ -0,0 +1,36 @@ +use crate::lib::u256::{and_256, or_256, left_shift_256, right_shift_256, split_256_into_64}; +use crate::lib::asserts::assert_eq_256; +use crate::helper::{if_test_this_function, assert_bool}; + +fn main() { + let fn_idx: u8 = witness::FUNCTION_INDEX; + + let a: u256 = witness::FIRST_ARG; + let b: u256 = witness::SECOND_ARG; + let expected: Option = witness::EXPECTED; + + let expected_bool: bool = witness::EXPECTED_BOOL; + + /// Bit logic + + match if_test_this_function(0, fn_idx) { true => { assert_eq_256(and_256(a, b), unwrap(expected)); }, false => (), }; + match if_test_this_function(1, fn_idx) { true => { assert_eq_256(or_256(a, b), unwrap(expected)); }, false => (), }; + + match if_test_this_function(2, fn_idx) { + true => { + let (_, _, _, a): (u64, u64, u64, u64) = split_256_into_64(a); + let shift: u8 = jet::rightmost_64_8(a); + + assert_eq_256(left_shift_256(shift, b), unwrap(expected)); + }, false => (), + }; + + match if_test_this_function(3, fn_idx) { + true => { + let (_, _, _, a): (u64, u64, u64, u64) = split_256_into_64(a); + let shift: u8 = jet::rightmost_64_8(a); + + assert_eq_256(right_shift_256(shift, b), unwrap(expected)); + }, false => (), + }; +} diff --git a/simf/u256_test_compare.simf b/simf/u256_test_compare.simf new file mode 100644 index 0000000..7deedb5 --- /dev/null +++ b/simf/u256_test_compare.simf @@ -0,0 +1,16 @@ +use crate::lib::u256::{is_zero_256, lt_256, le_256}; +use crate::helper::{if_test_this_function, assert_bool}; + +fn main() { + let fn_idx: u8 = witness::FUNCTION_INDEX; + + let a: u256 = witness::FIRST_ARG; + let b: u256 = witness::SECOND_ARG; + let expected_bool: bool = witness::EXPECTED_BOOL; + + /// Arithmetic + + match if_test_this_function(0, fn_idx) { true => { assert_bool(is_zero_256(a), expected_bool); }, false => (), }; + match if_test_this_function(1, fn_idx) { true => { assert_bool(lt_256(a, b), expected_bool); }, false => (), }; + match if_test_this_function(2, fn_idx) { true => { assert_bool(le_256(a, b), expected_bool); }, false => (), }; +} diff --git a/simf/u256_test_div.simf b/simf/u256_test_div.simf new file mode 100644 index 0000000..50576b1 --- /dev/null +++ b/simf/u256_test_div.simf @@ -0,0 +1,53 @@ +use crate::lib::u256::{div_mod_256_64, div_mod_256_128, div_mod_256, div_256, split_256_into_64}; +use crate::lib::asserts::{assert_eq_128, assert_eq_256}; +use crate::helper::if_test_this_function; + +fn main() { + let fn_idx: u8 = witness::FUNCTION_INDEX; + + let a: u256 = witness::FIRST_ARG; + let b: u256 = witness::SECOND_ARG; + let expected: Option = witness::EXPECTED; + + let second_expected: u256 = witness::SECOND_EXPECTED; + + /// Arithmetic + + match if_test_this_function(0, fn_idx) { + true => { + let (_, _, _, b): (u64, u64, u64, u64) = split_256_into_64(b); + let (_, _, _, expected_r): (u64, u64, u64, u64) = split_256_into_64(second_expected); + + let (q, r): (u256, u64) = div_mod_256_64(a, b); + + assert_eq_256(q, unwrap(expected)); + assert!(jet::eq_64(r, expected_r)); + }, + false => (), + }; + + match if_test_this_function(1, fn_idx) { + true => { + let (_, b): (u128, u128) = ::into(b); + let (_, expected_r): (u128, u128) = ::into(second_expected); + + let (q, r): (u256, u128) = div_mod_256_128(a, b); + + assert_eq_256(q, unwrap(expected)); + assert_eq_128(r, expected_r); + }, + false => (), + }; + + match if_test_this_function(2, fn_idx) { + true => { + let (q, r): (u256, u256) = div_mod_256(a, b); + + assert_eq_256(q, unwrap(expected)); + assert_eq_256(r, second_expected); + }, + false => (), + }; + + match if_test_this_function(3, fn_idx) { true => { assert_eq_256(div_256(a, b), unwrap(expected)); }, false => (), }; +} diff --git a/simf/u256_test_split_add.simf b/simf/u256_test_split_add.simf new file mode 100644 index 0000000..da49048 --- /dev/null +++ b/simf/u256_test_split_add.simf @@ -0,0 +1,44 @@ +use crate::lib::u256::{split_256_into_64, add_256, add_256_128}; +use crate::lib::asserts::assert_eq_256; +use crate::helper::{if_test_this_function, assert_bool}; + +/// Asserts a result equals expected u256 and bool values. +/// Used for functions that return carry or borrow bool value. +fn assert_eq_uint_bool(result: (bool, u256), expected: u256, expected_bool: bool) { + let (bool_res, uint_res): (bool, u256) = result; + + assert_bool(bool_res, expected_bool); + + assert_eq_256(uint_res, expected); +} + +fn main() { + let fn_idx: u8 = witness::FUNCTION_INDEX; + + let a: u256 = witness::FIRST_ARG; + let b: u256 = witness::SECOND_ARG; + let expected: Option = witness::EXPECTED; + let expected_bool: bool = witness::EXPECTED_BOOL; + + /// Arithmetic + + match if_test_this_function(0, fn_idx) { + true => { + let (res1, res2, res3, res4): (u64, u64, u64, u64) = split_256_into_64(a); + + let (high, low): (u128, u128) = ::into(unwrap(expected)); + + let (expected1, expected2): (u64, u64) = ::into(high); + let (expected3, expected4): (u64, u64) = ::into(low); + + assert!(jet::eq_64(res1, expected1)); + assert!(jet::eq_64(res2, expected2)); + assert!(jet::eq_64(res3, expected3)); + assert!(jet::eq_64(res4, expected4)); + }, + false => (), + }; + + match if_test_this_function(1, fn_idx) { true => { assert_eq_uint_bool(add_256(a, b), unwrap(expected), expected_bool); }, false => (), }; + match if_test_this_function(2, fn_idx) { true => { let (_, b): (u128, u128) = ::into(b); assert_eq_uint_bool(add_256_128(a, b), unwrap(expected), expected_bool); }, false => (), }; +} diff --git a/simf/u256_test_sub_mul.simf b/simf/u256_test_sub_mul.simf new file mode 100644 index 0000000..a078585 --- /dev/null +++ b/simf/u256_test_sub_mul.simf @@ -0,0 +1,38 @@ +use crate::lib::u256::{sub_256, mul_256}; +use crate::lib::asserts::assert_eq_256; +use crate::helper::{if_test_this_function, assert_bool}; + +/// Asserts a result equals expected u256 and bool values. +/// Used for functions that return carry or borrow bool value. +fn assert_eq_uint_bool(result: (bool, u256), expected: u256, expected_bool: bool) { + let (bool_res, uint_res): (bool, u256) = result; + + assert_bool(bool_res, expected_bool); + + assert_eq_256(uint_res, expected); +} + +fn main() { + let fn_idx: u8 = witness::FUNCTION_INDEX; + + let a: u256 = witness::FIRST_ARG; + let b: u256 = witness::SECOND_ARG; + let expected: Option = witness::EXPECTED; + + let expected_bool: bool = witness::EXPECTED_BOOL; + let second_expected: u256 = witness::SECOND_EXPECTED; + + /// Arithmetic + + match if_test_this_function(0, fn_idx) { true => { assert_eq_uint_bool(sub_256(a, b), unwrap(expected), expected_bool); }, false => (), }; + + match if_test_this_function(1, fn_idx) { + true => { + let (result_high, result_low): (u256,u256) = mul_256(a, b); + + assert_eq_256(result_high, unwrap(expected)); + assert_eq_256(result_low, second_expected); + }, + false => (), + }; +} diff --git a/simf/u8_convert_test.simf b/simf/u8_convert_test.simf index 7589160..928baf8 100644 --- a/simf/u8_convert_test.simf +++ b/simf/u8_convert_test.simf @@ -26,7 +26,7 @@ fn main() { assert!(jet::eq_16(u8_to_u16(a), expected)); }, - false => {}, + false => (), }; match if_test_this_function(1, fn_idx) { @@ -36,7 +36,7 @@ fn main() { assert!(jet::eq_32(u8_to_u32(a), expected)); }, - false => {}, + false => (), }; match if_test_this_function(2, fn_idx) { @@ -45,7 +45,7 @@ fn main() { assert!(jet::eq_64(u8_to_u64(a), expected)); }, - false => {}, + false => (), }; match if_test_this_function(3, fn_idx) { @@ -54,14 +54,14 @@ fn main() { assert!(eq_128(u8_to_u128(a), expected)); }, - false => {}, + false => (), }; match if_test_this_function(4, fn_idx) { true => { assert!(jet::eq_256(u8_to_u256(a), expected)); }, - false => {}, + false => (), }; match if_test_this_function(5, fn_idx) { @@ -99,7 +99,7 @@ fn main() { assert_eq_1(a1, expected1); assert_eq_1(a0, expected0); }, - false => {}, + false => (), }; match if_test_this_function(6, fn_idx) { @@ -109,6 +109,6 @@ fn main() { assert_eq_1(safe_u8_to_u1(a), expected); }, - false => {}, + false => (), }; } diff --git a/simf/u8_math_test.simf b/simf/u8_math_test.simf index 3bd2eef..d8c4f44 100644 --- a/simf/u8_math_test.simf +++ b/simf/u8_math_test.simf @@ -41,22 +41,22 @@ fn main() { let expected: Option = witness::EXPECTED; // add - match if_test_this_function(0, fn_idx) { true => { assert_eq_opt(checked_add_8(a, b), expected); }, false => {}, }; - match if_test_this_function(1, fn_idx) { true => { assert!(jet::eq_8(safe_add_8(a, b), unwrap(expected))); }, false => {}, }; + match if_test_this_function(0, fn_idx) { true => { assert_eq_opt(checked_add_8(a, b), expected); }, false => (), }; + match if_test_this_function(1, fn_idx) { true => { assert!(jet::eq_8(safe_add_8(a, b), unwrap(expected))); }, false => (), }; // sub - match if_test_this_function(2, fn_idx) { true => { assert_eq_opt(checked_sub_8(a, b), expected); }, false => {}, }; - match if_test_this_function(3, fn_idx) { true => { assert!(jet::eq_8(safe_sub_8(a, b), unwrap(expected))); }, false => {}, }; + match if_test_this_function(2, fn_idx) { true => { assert_eq_opt(checked_sub_8(a, b), expected); }, false => (), }; + match if_test_this_function(3, fn_idx) { true => { assert!(jet::eq_8(safe_sub_8(a, b), unwrap(expected))); }, false => (), }; // mul - match if_test_this_function(4, fn_idx) { true => { assert_eq_opt(checked_mul_8(a, b), expected); }, false => {}, }; - match if_test_this_function(5, fn_idx) { true => { assert!(jet::eq_8(safe_mul_8(a, b), unwrap(expected))); }, false => {}, }; + match if_test_this_function(4, fn_idx) { true => { assert_eq_opt(checked_mul_8(a, b), expected); }, false => (), }; + match if_test_this_function(5, fn_idx) { true => { assert!(jet::eq_8(safe_mul_8(a, b), unwrap(expected))); }, false => (), }; // div - match if_test_this_function(6, fn_idx) { true => { assert_eq_opt(checked_div_8(a, b), expected); }, false => {}, }; - match if_test_this_function(7, fn_idx) { true => { assert!(jet::eq_8(safe_div_8(a, b), unwrap(expected))); }, false => {}, }; + match if_test_this_function(6, fn_idx) { true => { assert_eq_opt(checked_div_8(a, b), expected); }, false => (), }; + match if_test_this_function(7, fn_idx) { true => { assert!(jet::eq_8(safe_div_8(a, b), unwrap(expected))); }, false => (), }; // gt, ge - match if_test_this_function(8, fn_idx) { true => { assert_bool_by_opt(gt_8(a, b), expected); }, false => {}, }; - match if_test_this_function(9, fn_idx) { true => { assert_bool_by_opt(ge_8(a, b), expected); }, false => {}, }; + match if_test_this_function(8, fn_idx) { true => { assert_bool_by_opt(gt_8(a, b), expected); }, false => (), }; + match if_test_this_function(9, fn_idx) { true => { assert_bool_by_opt(ge_8(a, b), expected); }, false => (), }; } diff --git a/tests/common/helper.rs b/tests/common/helper.rs new file mode 100644 index 0000000..6b5a568 --- /dev/null +++ b/tests/common/helper.rs @@ -0,0 +1,22 @@ +// Each `tests/*.rs` is a separate crate that mounts this module but uses only +// part of it, so per-crate dead-code analysis would warn about the rest. +#![allow(dead_code)] + +use primitive_types::U256; +use rand::Rng; + +use crate::common::u256_wrapper::U256Wrapper; + +// Shared constants and helper functions used across integration tests + +pub const DEFAULT_BOOL: bool = false; + +pub fn generate_u256(lower_bound: U256, upper_bound: U256) -> U256 { + assert!( + lower_bound <= upper_bound, + "Error: lower bound is greater than upper bound" + ); + rand::thread_rng() + .gen_range(U256Wrapper(lower_bound)..=U256Wrapper(upper_bound)) + .0 +} diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 21e7049..e8669e2 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,2 +1,4 @@ pub mod core; +pub mod helper; +pub mod u256_wrapper; pub mod uint; diff --git a/tests/common/u256_wrapper.rs b/tests/common/u256_wrapper.rs new file mode 100644 index 0000000..97b2679 --- /dev/null +++ b/tests/common/u256_wrapper.rs @@ -0,0 +1,149 @@ +use primitive_types::U256; +use rand::Rng; +use rand::distributions::uniform::{SampleBorrow, SampleUniform, UniformSampler}; +use std::ops::{Add, Deref, DerefMut, Div, Mul, Sub}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct U256Wrapper(pub U256); + +impl U256Wrapper { + pub fn to_be_bytes(&self) -> [u8; 32] { + self.0.to_big_endian() + } +} + +impl Deref for U256Wrapper { + type Target = U256; + fn deref(&self) -> &U256 { + &self.0 + } +} + +impl DerefMut for U256Wrapper { + fn deref_mut(&mut self) -> &mut U256 { + &mut self.0 + } +} + +impl From for U256Wrapper { + fn from(v: U256) -> Self { + U256Wrapper(v) + } +} + +impl From for U256 { + fn from(v: U256Wrapper) -> Self { + v.0 + } +} + +impl Add for U256Wrapper { + type Output = U256Wrapper; + fn add(self, rhs: Self) -> Self::Output { + U256Wrapper(self.0 + rhs.0) + } +} + +impl Sub for U256Wrapper { + type Output = U256Wrapper; + fn sub(self, rhs: Self) -> Self::Output { + U256Wrapper(self.0 - rhs.0) + } +} + +impl Mul for U256Wrapper { + type Output = U256Wrapper; + fn mul(self, rhs: Self) -> Self::Output { + U256Wrapper(self.0 * rhs.0) + } +} + +impl Div for U256Wrapper { + type Output = U256Wrapper; + fn div(self, rhs: Self) -> Self::Output { + U256Wrapper(self.0 / rhs.0) + } +} + +#[derive(Clone, Copy, Debug)] +pub struct UniformU256 { + lower: U256, + range: U256, + inclusive_max: bool, +} + +impl UniformSampler for UniformU256 { + type X = U256Wrapper; + + fn new(lower: B1, upper: B2) -> Self + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let lower = lower.borrow().0; + let upper = upper.borrow().0; + + assert!(lower < upper, "Lower bound must be less than upper bound"); + + UniformU256 { + lower, + range: upper - lower, + inclusive_max: false, + } + } + + fn new_inclusive(lower: B1, upper: B2) -> Self + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let lower = lower.borrow().0; + let upper = upper.borrow().0; + + assert!( + lower <= upper, + "Lower bound must be less than or equal to upper bound" + ); + + if upper == U256::MAX && lower == U256::zero() { + UniformU256 { + lower, + range: U256::zero(), + inclusive_max: true, + } + } else { + UniformU256 { + lower, + range: upper - lower + U256::one(), + inclusive_max: false, + } + } + } + + fn sample(&self, rng: &mut R) -> Self::X { + if self.inclusive_max { + return U256Wrapper(random_u256(rng)); + } + loop { + let candidate = random_u256(rng); + let result = candidate % self.range; + let limit = U256::MAX - (U256::MAX % self.range); + + if candidate < limit { + return U256Wrapper(self.lower + result); + } + } + } +} + +impl SampleUniform for U256Wrapper { + type Sampler = UniformU256; +} + +fn random_u256(rng: &mut R) -> U256 { + let mut bytes = [0u8; 32]; + + rng.fill(&mut bytes); + + U256::from_big_endian(&bytes) +} diff --git a/tests/helper_test.rs b/tests/helper_test.rs new file mode 100644 index 0000000..8843942 --- /dev/null +++ b/tests/helper_test.rs @@ -0,0 +1,23 @@ +mod common; + +use primitive_types::U256; + +use crate::common::helper::generate_u256; + +#[test] +fn generate_u256_respects_bounds() { + let cases = [ + (U256::zero(), U256::MAX), + (U256::one(), U256::from(u128::MAX)), + (U256::from(u128::MAX) + 1, U256::MAX), + (U256::from(u64::MAX) + 1, U256::from(u128::MAX)), + (U256::MAX, U256::MAX), + ]; + + for (lo, hi) in cases { + for _ in 0..10_000 { + let v = generate_u256(lo, hi); + assert!(lo <= v && v <= hi, "{v:#x} outside [{lo:#x}, {hi:#x}]"); + } + } +} diff --git a/tests/op_return_test.rs b/tests/op_return_test.rs index 917bd49..e61268e 100644 --- a/tests/op_return_test.rs +++ b/tests/op_return_test.rs @@ -3,6 +3,7 @@ mod common; use rand::Rng; use crate::common::core::{run, run_with_op_return}; +use crate::common::helper::DEFAULT_BOOL; use common::core::Expect; use simplicityhl_std::artifacts::op_return_test::OpReturnTestProgram; @@ -20,7 +21,6 @@ fn op(o: FunctionToTest) -> u8 { o as u8 } -const DEFAULT_BOOL: bool = false; const DEFAULT_DATA: &[u8; 1] = &[1]; fn program() -> OpReturnTestProgram { diff --git a/tests/u128_test.rs b/tests/u128_test.rs index 5791165..f2411bb 100644 --- a/tests/u128_test.rs +++ b/tests/u128_test.rs @@ -35,6 +35,6 @@ impl TestUint for u128 { mod u128_tests { use super::*; - // Stamps the 16 `#[simplex::test]` entry points for u128. Logic lives in common::uint. + // Stamps the 22 `#[simplex::test]` entry points for u128. Logic lives in common::uint. uint_tests!(u128); } diff --git a/tests/u128_test_arithmetic.rs b/tests/u128_test_arithmetic.rs index 7b72ea9..e3efe9b 100644 --- a/tests/u128_test_arithmetic.rs +++ b/tests/u128_test_arithmetic.rs @@ -3,6 +3,7 @@ mod common; use primitive_types::U256; use rand::Rng; +use crate::common::helper::DEFAULT_BOOL; use common::core::{Expect, run}; use simplicityhl_std::artifacts::u128_test_arithmetic::U128TestArithmeticProgram; @@ -16,11 +17,12 @@ enum FunctionToTest { Le128, Add128, Add128_64, + FullAdd128, Sub128, + FullSub128, Mul128, - Split256Into64, - NormalizeToThreshold, - AlgorithmD, + CalculateNormalizerBase64, + EstimateQuotientDigitBase64, DivMod128_64, DivMod128, Div128, @@ -31,7 +33,6 @@ fn op(o: FunctionToTest) -> u8 { o as u8 } -const DEFAULT_BOOL: bool = false; const DEFAULT_EXPECTED: u128 = 0; fn program() -> U128TestArithmeticProgram { @@ -322,6 +323,110 @@ mod u128_tests_arithmetic { ) } + #[simplex::test] + fn u128_test_full_add_128_not_overflow_carry_low_false( + context: simplex::TestContext, + ) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=u128::MAX / 2); + let b = rand::thread_rng().gen_range(0..=u128::MAX / 2); + let result = a + b; + let result_carry = false; + let carry_low = 0_u128; + + run( + &context, + program(), + build_witness( + op(FunctionToTest::FullAdd128), + a, + b, + Some(result), + result_carry, + carry_low, + DEFAULT_EXPECTED, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u128_test_full_add_128_overflow_carry_low_false( + context: simplex::TestContext, + ) -> anyhow::Result<()> { + let a = u128::MAX; + let b = rand::thread_rng().gen_range(1..=u128::MAX); + let result = b - 1; + let result_carry = true; + let carry_low = 0_u128; + + run( + &context, + program(), + build_witness( + op(FunctionToTest::FullAdd128), + a, + b, + Some(result), + result_carry, + carry_low, + DEFAULT_EXPECTED, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u128_test_full_add_128_not_overflow_carry_low_true( + context: simplex::TestContext, + ) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=u128::MAX / 2); + let b = rand::thread_rng().gen_range(0..=u128::MAX / 2); + let result = a + b + 1; + let result_carry = false; + let carry_low = 1_u128; + + run( + &context, + program(), + build_witness( + op(FunctionToTest::FullAdd128), + a, + b, + Some(result), + result_carry, + carry_low, + DEFAULT_EXPECTED, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u128_test_full_add_128_overflow_carry_low_true( + context: simplex::TestContext, + ) -> anyhow::Result<()> { + let a = u128::MAX; + let b = rand::thread_rng().gen_range(1..=u128::MAX); + let result = b; + let result_carry = true; + let carry_low = 1_u128; + + run( + &context, + program(), + build_witness( + op(FunctionToTest::FullAdd128), + a, + b, + Some(result), + result_carry, + carry_low, + DEFAULT_EXPECTED, + ), + Expect::Ok, + ) + } + #[simplex::test] fn u128_test_sub_128_not_overflow(context: simplex::TestContext) -> anyhow::Result<()> { let a = rand::thread_rng().gen_range(0..=u128::MAX); @@ -464,23 +569,51 @@ mod u128_tests_arithmetic { } #[simplex::test] - fn u128_test_mul_128(context: simplex::TestContext) -> anyhow::Result<()> { - let a = rand::thread_rng().gen_range(0..u128::MAX); - let b = rand::thread_rng().gen_range(0..u128::MAX); - let result = U256::from(a) * U256::from(b); + fn u128_test_full_sub_128_borrow_low_false( + context: simplex::TestContext, + ) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..=u128::MAX); + let b = rand::thread_rng().gen_range(0..=a); + let result = a - b; + let result_borrow = false; + let borrow_low = 0_u128; - let (result_high, result_low) = split_helper(result); + run( + &context, + program(), + build_witness( + op(FunctionToTest::FullSub128), + a, + b, + Some(result), + result_borrow, + borrow_low, + DEFAULT_EXPECTED, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u128_test_full_sub_128_overflow_borrow_low_false( + context: simplex::TestContext, + ) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..u128::MAX); + let b = u128::MAX; + let result = a + 1; + let result_borrow = true; + let borrow_low = 0_u128; run( &context, program(), build_witness( - op(FunctionToTest::Mul128), + op(FunctionToTest::FullSub128), a, b, - Some(result_high), - DEFAULT_BOOL, - result_low, + Some(result), + result_borrow, + borrow_low, DEFAULT_EXPECTED, ), Expect::Ok, @@ -488,20 +621,49 @@ mod u128_tests_arithmetic { } #[simplex::test] - fn u128_test_split_256_into_64(context: simplex::TestContext) -> anyhow::Result<()> { + fn u128_test_full_sub_128_borrow_low_true(context: simplex::TestContext) -> anyhow::Result<()> { let a = rand::thread_rng().gen_range(0..=u128::MAX); - let b = rand::thread_rng().gen_range(0..=u128::MAX); + let b = rand::thread_rng().gen_range(0..a); + let result = a - b - 1; + let result_borrow = false; + let borrow_low = 1_u128; run( &context, program(), build_witness( - op(FunctionToTest::Split256Into64), + op(FunctionToTest::FullSub128), a, b, - Some(a), - DEFAULT_BOOL, + Some(result), + result_borrow, + borrow_low, + DEFAULT_EXPECTED, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u128_test_full_sub_128_overflow_borrow_low_true( + context: simplex::TestContext, + ) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..u128::MAX); + let b = u128::MAX; + let (result, result_borrow) = a.overflowing_sub(b); + + let borrow_low = 1_u128; + + run( + &context, + program(), + build_witness( + op(FunctionToTest::FullSub128), + a, b, + Some(result - 1), + result_borrow, + borrow_low, DEFAULT_EXPECTED, ), Expect::Ok, @@ -509,107 +671,118 @@ mod u128_tests_arithmetic { } #[simplex::test] - fn u128_test_normalize_to_threshold_b_is_u64( + fn u128_test_mul_128(context: simplex::TestContext) -> anyhow::Result<()> { + let a = rand::thread_rng().gen_range(0..u128::MAX); + let b = rand::thread_rng().gen_range(0..u128::MAX); + let result = U256::from(a) * U256::from(b); + + let (result_high, result_low) = split_helper(result); + + run( + &context, + program(), + build_witness( + op(FunctionToTest::Mul128), + a, + b, + Some(result_high), + DEFAULT_BOOL, + result_low, + DEFAULT_EXPECTED, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u128_test_calculate_normalizer_base_64_b_is_u64( context: simplex::TestContext, ) -> anyhow::Result<()> { let threshold = 1u128 << 63; - let a = rand::thread_rng().gen_range(0..=u128::MAX); let b = rand::thread_rng().gen_range(1..threshold); let norm: u128 = threshold.div_ceil(b); - let result_a = U256::from(a) * U256::from(norm); - let result_b = b * norm; - let (result_a_high, result_a_low) = split_helper(result_a); - run( &context, program(), build_witness( - op(FunctionToTest::NormalizeToThreshold), - a, + op(FunctionToTest::CalculateNormalizerBase64), + DEFAULT_EXPECTED, b, - Some(result_a_high), + Some(norm), false, - result_a_low, - result_b, + DEFAULT_EXPECTED, + DEFAULT_EXPECTED, ), Expect::Ok, ) } #[simplex::test] - fn u128_test_normalize_to_threshold_b_is_big_enough_not_normalize( + fn u128_test_calculate_normalizer_base_64_b_is_big_enough_not_normalize( context: simplex::TestContext, ) -> anyhow::Result<()> { let threshold = 1u128 << 63; - let a = rand::thread_rng().gen_range(0..=u128::MAX); let b = rand::thread_rng().gen_range(threshold..=u64::MAX as u128); run( &context, program(), build_witness( - op(FunctionToTest::NormalizeToThreshold), - a, + op(FunctionToTest::CalculateNormalizerBase64), + DEFAULT_EXPECTED, b, - Some(0), + Some(1), false, - a, - b, + DEFAULT_EXPECTED, + DEFAULT_EXPECTED, ), Expect::Ok, ) } #[simplex::test] - fn u128_test_normalize_to_threshold_b_is_u128( + fn u128_test_calculate_normalizer_base_64_b_is_u128( context: simplex::TestContext, ) -> anyhow::Result<()> { let threshold = 1u128 << 63; - let a = rand::thread_rng().gen_range(0..=u128::MAX); let b = rand::thread_rng().gen_range((u64::MAX as u128) + 1..=u128::MAX); - let b_high = b >> 64; let norm: u128 = threshold.div_ceil(b_high); - let result_a = U256::from(a) * U256::from(norm); - let result_b = b * norm; - let (result_a_high, result_a_low) = split_helper(result_a); - run( &context, program(), build_witness( - op(FunctionToTest::NormalizeToThreshold), - a, + op(FunctionToTest::CalculateNormalizerBase64), + DEFAULT_EXPECTED, b, - Some(result_a_high), + Some(norm), true, - result_a_low, - result_b, + DEFAULT_EXPECTED, + DEFAULT_EXPECTED, ), Expect::Ok, ) } #[simplex::test] - fn u128_test_normalize_to_threshold_b_is_u64_fail( + fn u128_test_calculate_normalizer_base_64_b_is_u64_fail( context: simplex::TestContext, ) -> anyhow::Result<()> { - let a = rand::thread_rng().gen_range(0..=u128::MAX); let b = rand::thread_rng().gen_range((u64::MAX as u128) + 1..=u128::MAX); run( &context, program(), build_witness( - op(FunctionToTest::NormalizeToThreshold), - a, + op(FunctionToTest::CalculateNormalizerBase64), + DEFAULT_EXPECTED, b, Some(DEFAULT_EXPECTED), false, @@ -621,18 +794,17 @@ mod u128_tests_arithmetic { } #[simplex::test] - fn u128_test_normalize_to_threshold_b_is_u128_fail( + fn u128_test_calculate_normalizer_base_64_b_is_u128_fail( context: simplex::TestContext, ) -> anyhow::Result<()> { - let a = rand::thread_rng().gen_range(0..=u128::MAX); let b = rand::thread_rng().gen_range(1..=u64::MAX as u128); run( &context, program(), build_witness( - op(FunctionToTest::NormalizeToThreshold), - a, + op(FunctionToTest::CalculateNormalizerBase64), + DEFAULT_EXPECTED, b, Some(DEFAULT_EXPECTED), true, @@ -644,18 +816,17 @@ mod u128_tests_arithmetic { } #[simplex::test] - fn u128_test_normalize_to_threshold_b_is_zero_fail( + fn u128_test_calculate_normalizer_base_64_b_is_zero_fail( context: simplex::TestContext, ) -> anyhow::Result<()> { - let a = rand::thread_rng().gen_range(0..=u128::MAX); let b = 0; run( &context, program(), build_witness( - op(FunctionToTest::NormalizeToThreshold), - a, + op(FunctionToTest::CalculateNormalizerBase64), + DEFAULT_EXPECTED, b, Some(DEFAULT_EXPECTED), false, @@ -667,24 +838,32 @@ mod u128_tests_arithmetic { } #[simplex::test] - fn u128_test_algorithm_d(context: simplex::TestContext) -> anyhow::Result<()> { - // divisor is expected to be greater than or equal to 2^64 - let b = rand::thread_rng().gen_range((u64::MAX as u128) + 1..u128::MAX); - let a = rand::thread_rng().gen_range(b..=u128::MAX); + fn u128_test_estimate_quotient_digit_base_64( + context: simplex::TestContext, + ) -> anyhow::Result<()> { + let threshold = 1u64 << 63; - let q = a / b; - let r = a % b; + let b_high = rand::thread_rng().gen_range(threshold..=u64::MAX); + let b_low = rand::thread_rng().gen_range(0..=u64::MAX); + + let a_high = rand::thread_rng().gen_range(0..b_high); + let a_low = rand::thread_rng().gen_range(0..=u128::MAX); + + let a = ((U256::from(a_high)) << 128) | (U256::from(a_low)); + let b = ((b_high as u128) << 64) | (b_low as u128); + + let q = (a / b).as_u128(); run( &context, program(), build_witness( - op(FunctionToTest::AlgorithmD), - a, - b, + op(FunctionToTest::EstimateQuotientDigitBase64), + a_high as u128, + a_low, Some(q), DEFAULT_BOOL, - r, + b, DEFAULT_EXPECTED, ), Expect::Ok, @@ -692,24 +871,33 @@ mod u128_tests_arithmetic { } #[simplex::test] - fn u128_test_algorithm_d_fail(context: simplex::TestContext) -> anyhow::Result<()> { - // expected to fail because divisor is less than 2^64 - let b = rand::thread_rng().gen_range(1..=u64::MAX as u128); - let a = rand::thread_rng().gen_range(b..=u128::MAX); + fn u128_test_estimate_quotient_digit_base_64_fail( + context: simplex::TestContext, + ) -> anyhow::Result<()> { + // expected to fail because a is to big for q to fit unto u64 + let threshold = 1u64 << 63; - let q = a / b; - let r = a % b; + let b_high = rand::thread_rng().gen_range(threshold..u64::MAX); + let b_low = rand::thread_rng().gen_range(0..=u64::MAX); + + let a_high = rand::thread_rng().gen_range(b_high..=u64::MAX); + let a_low = rand::thread_rng().gen_range(0..=u128::MAX); + + let a = ((U256::from(a_high)) << 128) | (U256::from(a_low)); + let b = ((b_high as u128) << 64) | (b_low as u128); + + let q = (a / b).as_u128(); run( &context, program(), build_witness( - op(FunctionToTest::AlgorithmD), - a, - b, + op(FunctionToTest::EstimateQuotientDigitBase64), + a_high as u128, + a_low, Some(q), DEFAULT_BOOL, - r, + b, DEFAULT_EXPECTED, ), Expect::AssertFailed, diff --git a/tests/u128_test_bits.rs b/tests/u128_test_bits.rs index cfc4531..5d04898 100644 --- a/tests/u128_test_bits.rs +++ b/tests/u128_test_bits.rs @@ -2,6 +2,7 @@ mod common; use rand::Rng; +use crate::common::helper::DEFAULT_BOOL; use common::core::{Expect, run}; use simplicityhl_std::artifacts::u128_test_bits::U128TestBitsProgram; @@ -22,7 +23,6 @@ fn op(o: FunctionToTest) -> u8 { o as u8 } -const DEFAULT_BOOL: bool = false; const DEFAULT_EXPECTED: u128 = 0; fn program() -> U128TestBitsProgram { diff --git a/tests/u16_test.rs b/tests/u16_test.rs index 7274e4e..49d64f5 100644 --- a/tests/u16_test.rs +++ b/tests/u16_test.rs @@ -33,6 +33,6 @@ impl TestUint for u16 { mod u16_tests { use super::*; - // Stamps the 16 `#[simplex::test]` entry points for u16. Logic lives in common::uint. + // Stamps the 22 `#[simplex::test]` entry points for u16. Logic lives in common::uint. uint_tests!(u16); } diff --git a/tests/u256_test.rs b/tests/u256_test.rs new file mode 100644 index 0000000..d15c05d --- /dev/null +++ b/tests/u256_test.rs @@ -0,0 +1,48 @@ +mod common; + +use primitive_types::U256; + +use common::u256_wrapper::U256Wrapper; +use common::uint::TestUint; + +use simplicityhl_std::artifacts::u256_test::U256TestProgram; +use simplicityhl_std::artifacts::u256_test::derived_u256_test::{ + U256TestArguments, U256TestWitness, +}; + +// The only per-width code for the common operations. +impl TestUint for U256Wrapper { + type Program = U256TestProgram; + type Witness = U256TestWitness; + + const ZERO: U256Wrapper = U256Wrapper(U256::zero()); + const ONE: U256Wrapper = U256Wrapper(U256::one()); + const MAX: U256Wrapper = U256Wrapper(U256::MAX); + const HALF_MAX: U256Wrapper = U256Wrapper(U256([u64::MAX, u64::MAX, u64::MAX, u64::MAX >> 1])); + const MUL_BOUND: U256Wrapper = U256Wrapper(U256([0, 0, 1, 0])); // 2^(256/2) + + fn program() -> U256TestProgram { + U256TestProgram::new(U256TestArguments {}) + } + + fn witness( + op: u8, + a: U256Wrapper, + b: U256Wrapper, + expected: Option, + ) -> U256TestWitness { + U256TestWitness { + function_index: op, + first_arg: a.to_big_endian(), + second_arg: b.to_big_endian(), + expected: expected.map(|w| w.to_be_bytes()), + } + } +} + +mod u256_tests { + use super::*; + + // Stamps the 22 `#[simplex::test]` entry points for U256Wrapper. Logic lives in common::uint. + uint_tests!(U256Wrapper); +} diff --git a/tests/u256_test_bits.rs b/tests/u256_test_bits.rs new file mode 100644 index 0000000..d874a28 --- /dev/null +++ b/tests/u256_test_bits.rs @@ -0,0 +1,208 @@ +mod common; + +use primitive_types::U256; +use rand::Rng; + +use crate::common::helper::{DEFAULT_BOOL, generate_u256}; +use common::core::{Expect, run}; + +use simplicityhl_std::artifacts::u256_test_bits::U256TestBitsProgram; +use simplicityhl_std::artifacts::u256_test_bits::derived_u256_test_bits::{ + U256TestBitsArguments, U256TestBitsWitness, +}; + +enum FunctionToTest { + And256, + Or256, + LeftShift256, + RightShift256, +} + +#[inline] +fn op(o: FunctionToTest) -> u8 { + o as u8 +} + +fn program() -> U256TestBitsProgram { + U256TestBitsProgram::new(U256TestBitsArguments {}) +} + +fn build_witness( + function: u8, + a: [u8; 32], + b: [u8; 32], + expected: Option<[u8; 32]>, + expected_bool: bool, +) -> U256TestBitsWitness { + U256TestBitsWitness { + function_index: function, + first_arg: a, + second_arg: b, + expected, + expected_bool, + } +} + +mod u256_tests_bits { + use super::*; + + #[simplex::test] + fn u256_test_and_256(context: simplex::TestContext) -> anyhow::Result<()> { + let a = generate_u256(U256::zero(), U256::MAX); + let b = generate_u256(U256::zero(), U256::MAX); + let result = (a & b).to_big_endian(); + + run( + &context, + program(), + build_witness( + op(FunctionToTest::And256), + a.to_big_endian(), + b.to_big_endian(), + Some(result), + DEFAULT_BOOL, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_or_256(context: simplex::TestContext) -> anyhow::Result<()> { + let a = generate_u256(U256::zero(), U256::MAX); + let b = generate_u256(U256::zero(), U256::MAX); + let result = (a | b).to_big_endian(); + + run( + &context, + program(), + build_witness( + op(FunctionToTest::Or256), + a.to_big_endian(), + b.to_big_endian(), + Some(result), + DEFAULT_BOOL, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_left_shift_256(context: simplex::TestContext) -> anyhow::Result<()> { + let shift = rand::thread_rng().gen_range(1..=127_u8); + let val = generate_u256(U256::zero(), U256::MAX); + let result = (val << shift).to_big_endian(); + + run( + &context, + program(), + build_witness( + op(FunctionToTest::LeftShift256), + U256::from(shift).to_big_endian(), + val.to_big_endian(), + Some(result), + DEFAULT_BOOL, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_left_shift_256_by_zero(context: simplex::TestContext) -> anyhow::Result<()> { + let shift = 0; + let val = generate_u256(U256::zero(), U256::MAX).to_big_endian(); + let result = val; + + run( + &context, + program(), + build_witness( + op(FunctionToTest::LeftShift256), + U256::from(shift).to_big_endian(), + val, + Some(result), + DEFAULT_BOOL, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_left_shift_256_max(context: simplex::TestContext) -> anyhow::Result<()> { + let shift = u8::MAX; + let val = generate_u256(U256::zero(), U256::MAX); + let result = (val << shift).to_big_endian(); + + run( + &context, + program(), + build_witness( + op(FunctionToTest::LeftShift256), + U256::from(shift).to_big_endian(), + val.to_big_endian(), + Some(result), + DEFAULT_BOOL, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_right_shift_256(context: simplex::TestContext) -> anyhow::Result<()> { + let shift = rand::thread_rng().gen_range(1..=127_u128); + let val = generate_u256(U256::zero(), U256::MAX); + let result = (val >> shift).to_big_endian(); + + run( + &context, + program(), + build_witness( + op(FunctionToTest::RightShift256), + U256::from(shift).to_big_endian(), + val.to_big_endian(), + Some(result), + DEFAULT_BOOL, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_right_shift_256_by_zero(context: simplex::TestContext) -> anyhow::Result<()> { + let shift = 0; + let val = generate_u256(U256::zero(), U256::MAX).to_big_endian(); + let result = val; + + run( + &context, + program(), + build_witness( + op(FunctionToTest::RightShift256), + U256::from(shift).to_big_endian(), + val, + Some(result), + DEFAULT_BOOL, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_right_shift_256_max(context: simplex::TestContext) -> anyhow::Result<()> { + let shift = u8::MAX; + let val = generate_u256(U256::zero(), U256::MAX); + let result = (val >> shift).to_big_endian(); + + run( + &context, + program(), + build_witness( + op(FunctionToTest::RightShift256), + U256::from(shift).to_big_endian(), + val.to_big_endian(), + Some(result), + DEFAULT_BOOL, + ), + Expect::Ok, + ) + } +} diff --git a/tests/u256_test_compare.rs b/tests/u256_test_compare.rs new file mode 100644 index 0000000..0ed9e83 --- /dev/null +++ b/tests/u256_test_compare.rs @@ -0,0 +1,166 @@ +mod common; + +use primitive_types::U256; + +use crate::common::helper::generate_u256; +use common::core::{Expect, run}; + +use simplicityhl_std::artifacts::u256_test_compare::U256TestCompareProgram; +use simplicityhl_std::artifacts::u256_test_compare::derived_u256_test_compare::{ + U256TestCompareArguments, U256TestCompareWitness, +}; + +enum FunctionToTest { + IsZero256, + Lt256, + Le256, +} + +#[inline] +fn op(o: FunctionToTest) -> u8 { + o as u8 +} + +const DEFAULT_EXPECTED: [u8; 32] = [0; 32]; + +fn program() -> U256TestCompareProgram { + U256TestCompareProgram::new(U256TestCompareArguments {}) +} + +fn build_witness( + function: u8, + a: [u8; 32], + b: [u8; 32], + expected_bool: bool, +) -> U256TestCompareWitness { + U256TestCompareWitness { + function_index: function, + first_arg: a, + second_arg: b, + expected_bool, + } +} + +mod u256_tests_arithmetic { + use super::*; + + #[simplex::test] + fn u256_test_is_zero_256_true(context: simplex::TestContext) -> anyhow::Result<()> { + let a = [0; 32]; + + run( + &context, + program(), + build_witness(op(FunctionToTest::IsZero256), a, DEFAULT_EXPECTED, true), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_is_zero_256_false(context: simplex::TestContext) -> anyhow::Result<()> { + let a = generate_u256(U256::one(), U256::MAX).to_big_endian(); + + run( + &context, + program(), + build_witness(op(FunctionToTest::IsZero256), a, DEFAULT_EXPECTED, false), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_lt_256_less(context: simplex::TestContext) -> anyhow::Result<()> { + let a = generate_u256(U256::zero(), U256::MAX - 1); + let b = a + 1; + + run( + &context, + program(), + build_witness( + op(FunctionToTest::Lt256), + a.to_big_endian(), + b.to_big_endian(), + true, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_lt_256_eq(context: simplex::TestContext) -> anyhow::Result<()> { + let a = generate_u256(U256::zero(), U256::MAX).to_big_endian(); + + run( + &context, + program(), + build_witness(op(FunctionToTest::Lt256), a, a, false), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_lt_256_bigger(context: simplex::TestContext) -> anyhow::Result<()> { + let a = generate_u256(U256::one(), U256::MAX); + let b = a - 1; + + run( + &context, + program(), + build_witness( + op(FunctionToTest::Lt256), + a.to_big_endian(), + b.to_big_endian(), + false, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_le_256_less(context: simplex::TestContext) -> anyhow::Result<()> { + let a = generate_u256(U256::zero(), U256::MAX - 1); + let b = a + 1; + + run( + &context, + program(), + build_witness( + op(FunctionToTest::Le256), + a.to_big_endian(), + b.to_big_endian(), + true, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_le_256_eq(context: simplex::TestContext) -> anyhow::Result<()> { + let a = generate_u256(U256::one(), U256::MAX).to_big_endian(); + + run( + &context, + program(), + build_witness(op(FunctionToTest::Le256), a, a, true), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_le_256_bigger(context: simplex::TestContext) -> anyhow::Result<()> { + let a = generate_u256(U256::one(), U256::MAX); + let b = a - 1; + + run( + &context, + program(), + build_witness( + op(FunctionToTest::Le256), + a.to_big_endian(), + b.to_big_endian(), + false, + ), + Expect::Ok, + ) + } +} diff --git a/tests/u256_test_div.rs b/tests/u256_test_div.rs new file mode 100644 index 0000000..68bb70f --- /dev/null +++ b/tests/u256_test_div.rs @@ -0,0 +1,380 @@ +mod common; + +use primitive_types::U256; + +use crate::common::helper::generate_u256; +use common::core::{Expect, run}; + +use simplicityhl_std::artifacts::u256_test_div::U256TestDivProgram; +use simplicityhl_std::artifacts::u256_test_div::derived_u256_test_div::{ + U256TestDivArguments, U256TestDivWitness, +}; + +enum FunctionToTest { + DivMod256_64, + DivMod256_128, + DivMod256, + Div256, +} + +#[inline] +fn op(o: FunctionToTest) -> u8 { + o as u8 +} + +const DEFAULT_EXPECTED: [u8; 32] = [0; 32]; + +fn program() -> U256TestDivProgram { + U256TestDivProgram::new(U256TestDivArguments {}) +} + +fn build_witness( + function: u8, + a: [u8; 32], + b: [u8; 32], + expected: Option<[u8; 32]>, + second_expected: [u8; 32], +) -> U256TestDivWitness { + U256TestDivWitness { + function_index: function, + first_arg: a, + second_arg: b, + expected, + second_expected, + } +} + +mod u256_tests_arithmetic { + use super::*; + + #[simplex::test] + fn test_div_mod_256_64(context: simplex::TestContext) -> anyhow::Result<()> { + let a = generate_u256(U256::zero(), U256::MAX); + let b = generate_u256(U256::one(), U256::from(u64::MAX)); + + let q = (a / b).to_big_endian(); + let r = (a % b).to_big_endian(); + + run( + &context, + program(), + build_witness( + op(FunctionToTest::DivMod256_64), + a.to_big_endian(), + b.to_big_endian(), + Some(q), + r, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn test_div_mod_256_64_overflow(context: simplex::TestContext) -> anyhow::Result<()> { + let a = generate_u256(U256::zero(), U256::MAX); + let b = [0; 32]; + + run( + &context, + program(), + build_witness( + op(FunctionToTest::DivMod256_64), + a.to_big_endian(), + b, + Some(DEFAULT_EXPECTED), + DEFAULT_EXPECTED, + ), + Expect::AssertFailed, + ) + } + + #[simplex::test] + fn test_div_mod_256_128(context: simplex::TestContext) -> anyhow::Result<()> { + let a = generate_u256(U256::zero(), U256::MAX); + let b = generate_u256(U256::from(u64::MAX) + 1, U256::from(u128::MAX)); + + let q = (a / b).to_big_endian(); + let r = (a % b).to_big_endian(); + + run( + &context, + program(), + build_witness( + op(FunctionToTest::DivMod256_128), + a.to_big_endian(), + b.to_big_endian(), + Some(q), + r, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn test_div_mod_256_128_b_fits_into_u64(context: simplex::TestContext) -> anyhow::Result<()> { + let a = generate_u256(U256::zero(), U256::MAX); + let b = generate_u256(U256::one(), U256::from(u64::MAX)); + + let q = (a / b).to_big_endian(); + let r = (a % b).to_big_endian(); + + run( + &context, + program(), + build_witness( + op(FunctionToTest::DivMod256_128), + a.to_big_endian(), + b.to_big_endian(), + Some(q), + r, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn test_div_mod_256_128_overflow(context: simplex::TestContext) -> anyhow::Result<()> { + let a = generate_u256(U256::zero(), U256::MAX); + let b = [0; 32]; + + run( + &context, + program(), + build_witness( + op(FunctionToTest::DivMod256_128), + a.to_big_endian(), + b, + Some(DEFAULT_EXPECTED), + DEFAULT_EXPECTED, + ), + Expect::AssertFailed, + ) + } + + #[simplex::test] + fn test_div_mod_256_128_a_eq_b(context: simplex::TestContext) -> anyhow::Result<()> { + let a = (generate_u256(U256::one(), U256::from(u128::MAX))).to_big_endian(); + + let q = U256::one().to_big_endian(); + let r = U256::zero().to_big_endian(); + + run( + &context, + program(), + build_witness(op(FunctionToTest::DivMod256_128), a, a, Some(q), r), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_div_mod_256_a_less_than_b(context: simplex::TestContext) -> anyhow::Result<()> { + let a = generate_u256(U256::one(), U256::MAX - 1); + let b = generate_u256(a + 1, U256::MAX); + + let q = (a / b).to_big_endian(); + let r = (a % b).to_big_endian(); + + run( + &context, + program(), + build_witness( + op(FunctionToTest::DivMod256), + a.to_big_endian(), + b.to_big_endian(), + Some(q), + r, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_div_mod_256_div_128(context: simplex::TestContext) -> anyhow::Result<()> { + let b = generate_u256(U256::one(), U256::from(u128::MAX)); + let a = generate_u256(b, U256::from(u128::MAX)); + + let q = (a / b).to_big_endian(); + let r = (a % b).to_big_endian(); + + run( + &context, + program(), + build_witness( + op(FunctionToTest::DivMod256), + a.to_big_endian(), + b.to_big_endian(), + Some(q), + r, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_div_mod_256_q_is_1(context: simplex::TestContext) -> anyhow::Result<()> { + // case where a >= b and a_high = b_high != 0 + let b_low = generate_u256(U256::zero(), U256::from(u128::MAX)); + let a_low = generate_u256(b_low, U256::from(u128::MAX)); + let high = generate_u256(U256::one(), U256::from(u128::MAX)); + + let a = ((high as U256) << 128) | (a_low as U256); + let b = ((high as U256) << 128) | (b_low as U256); + + let q = (a / b).to_big_endian(); + let r = (a % b).to_big_endian(); + + run( + &context, + program(), + build_witness( + op(FunctionToTest::DivMod256), + a.to_big_endian(), + b.to_big_endian(), + Some(q), + r, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_div_mod_256_b_fits_into_u128(context: simplex::TestContext) -> anyhow::Result<()> { + let b = generate_u256(U256::one(), U256::from(u128::MAX)); + let a = generate_u256(U256::from(u128::MAX) + 1, U256::MAX); + + let q = (a / b).to_big_endian(); + let r = (a % b).to_big_endian(); + + run( + &context, + program(), + build_witness( + op(FunctionToTest::DivMod256), + a.to_big_endian(), + b.to_big_endian(), + Some(q), + r, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_div_mod_256_b_is_u256(context: simplex::TestContext) -> anyhow::Result<()> { + let b = generate_u256(U256::one(), U256::MAX - 1); + let a = generate_u256(b + 1, U256::MAX); + + let q = (a / b).to_big_endian(); + let r = (a % b).to_big_endian(); + + run( + &context, + program(), + build_witness( + op(FunctionToTest::DivMod256), + a.to_big_endian(), + b.to_big_endian(), + Some(q), + r, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_div_mod_256_a_equal_b(context: simplex::TestContext) -> anyhow::Result<()> { + let a = generate_u256(U256::one(), U256::MAX).to_big_endian(); + + run( + &context, + program(), + build_witness( + op(FunctionToTest::DivMod256), + a, + a, + Some(U256::one().to_big_endian()), + [0; 32], + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_div_mod_256_equal_high_words_max_low_diff( + context: simplex::TestContext, + ) -> anyhow::Result<()> { + let high = generate_u256(U256::one(), U256::from(u128::MAX)); + + let a = ((high << 128) | (U256::from(u128::MAX))).to_big_endian(); + let b = (high << 128).to_big_endian(); + + run( + &context, + program(), + build_witness( + op(FunctionToTest::DivMod256), + a, + b, + Some(U256::one().to_big_endian()), + U256::from(u128::MAX).to_big_endian(), + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_div_mod_256_eq_high_words_a_less_than_b( + context: simplex::TestContext, + ) -> anyhow::Result<()> { + let high = generate_u256(U256::one(), U256::from(u128::MAX)); + + let a = ((high as U256) << 128).to_big_endian(); + let b = (((high as U256) << 128) | (U256::from(u128::MAX))).to_big_endian(); + + run( + &context, + program(), + build_witness(op(FunctionToTest::DivMod256), a, b, Some([0; 32]), a), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_div_256(context: simplex::TestContext) -> anyhow::Result<()> { + let a = generate_u256(U256::zero(), U256::MAX); + let b = generate_u256(U256::one(), U256::MAX); + let result = (a / b).to_big_endian(); + + run( + &context, + program(), + build_witness( + op(FunctionToTest::Div256), + a.to_big_endian(), + b.to_big_endian(), + Some(result), + DEFAULT_EXPECTED, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_div_256_overflow(context: simplex::TestContext) -> anyhow::Result<()> { + let a = generate_u256(U256::zero(), U256::MAX); + let b = [0; 32]; + + run( + &context, + program(), + build_witness( + op(FunctionToTest::Div256), + a.to_big_endian(), + b, + Some(DEFAULT_EXPECTED), + DEFAULT_EXPECTED, + ), + Expect::AssertFailed, + ) + } +} diff --git a/tests/u256_test_split_add.rs b/tests/u256_test_split_add.rs new file mode 100644 index 0000000..7620b73 --- /dev/null +++ b/tests/u256_test_split_add.rs @@ -0,0 +1,146 @@ +mod common; + +use primitive_types::U256; + +use crate::common::helper::{DEFAULT_BOOL, generate_u256}; +use common::core::{Expect, run}; + +use simplicityhl_std::artifacts::u256_test_split_add::U256TestSplitAddProgram; +use simplicityhl_std::artifacts::u256_test_split_add::derived_u256_test_split_add::{ + U256TestSplitAddArguments, U256TestSplitAddWitness, +}; + +enum FunctionToTest { + Split256Into64, + Add256, + Add256_128, +} + +#[inline] +fn op(o: FunctionToTest) -> u8 { + o as u8 +} + +const DEFAULT_EXPECTED: [u8; 32] = [0; 32]; + +fn program() -> U256TestSplitAddProgram { + U256TestSplitAddProgram::new(U256TestSplitAddArguments {}) +} + +fn build_witness( + function: u8, + a: [u8; 32], + b: [u8; 32], + expected: Option<[u8; 32]>, + expected_bool: bool, +) -> U256TestSplitAddWitness { + U256TestSplitAddWitness { + function_index: function, + first_arg: a, + second_arg: b, + expected, + expected_bool, + } +} + +mod u256_tests_arithmetic { + use super::*; + + #[simplex::test] + fn u256_test_split_256_into_64(context: simplex::TestContext) -> anyhow::Result<()> { + let a = generate_u256(U256::one(), U256::MAX).to_big_endian(); + + run( + &context, + program(), + build_witness( + op(FunctionToTest::Split256Into64), + a, + DEFAULT_EXPECTED, + Some(a), + DEFAULT_BOOL, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_add_256_not_overflow(context: simplex::TestContext) -> anyhow::Result<()> { + let a = generate_u256(U256::zero(), U256::MAX / 2); + let b = generate_u256(U256::zero(), U256::MAX / 2); + let result = (a + b).to_big_endian(); + + run( + &context, + program(), + build_witness( + op(FunctionToTest::Add256), + a.to_big_endian(), + b.to_big_endian(), + Some(result), + false, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_add_256_overflow(context: simplex::TestContext) -> anyhow::Result<()> { + let a = U256::MAX; + let b = generate_u256(U256::one(), U256::MAX); + let result = (b - 1).to_big_endian(); + + run( + &context, + program(), + build_witness( + op(FunctionToTest::Add256), + a.to_big_endian(), + b.to_big_endian(), + Some(result), + true, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_add_256_128_not_overflow(context: simplex::TestContext) -> anyhow::Result<()> { + let a = generate_u256(U256::zero(), U256::MAX / 2); + let b = generate_u256(U256::one(), U256::from(u128::MAX)) as U256; + let result = (a + b).to_big_endian(); + + run( + &context, + program(), + build_witness( + op(FunctionToTest::Add256_128), + a.to_big_endian(), + b.to_big_endian(), + Some(result), + false, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_add_256_128_overflow(context: simplex::TestContext) -> anyhow::Result<()> { + let a = U256::MAX; + let b = generate_u256(U256::one(), U256::from(u128::MAX)) as U256; + let result = (b - 1).to_big_endian(); + + run( + &context, + program(), + build_witness( + op(FunctionToTest::Add256_128), + a.to_big_endian(), + b.to_big_endian(), + Some(result), + true, + ), + Expect::Ok, + ) + } +} diff --git a/tests/u256_test_sub_mul.rs b/tests/u256_test_sub_mul.rs new file mode 100644 index 0000000..5d97b44 --- /dev/null +++ b/tests/u256_test_sub_mul.rs @@ -0,0 +1,213 @@ +mod common; + +use primitive_types::U256; +use rand::Rng; + +use crate::common::helper::{DEFAULT_BOOL, generate_u256}; +use common::core::{Expect, run}; + +use simplicityhl_std::artifacts::u256_test_sub_mul::U256TestSubMulProgram; +use simplicityhl_std::artifacts::u256_test_sub_mul::derived_u256_test_sub_mul::{ + U256TestSubMulArguments, U256TestSubMulWitness, +}; + +enum FunctionToTest { + Sub256, + Mul256, +} + +#[inline] +fn op(o: FunctionToTest) -> u8 { + o as u8 +} + +const DEFAULT_EXPECTED: [u8; 32] = [0; 32]; + +fn program() -> U256TestSubMulProgram { + U256TestSubMulProgram::new(U256TestSubMulArguments {}) +} + +fn build_witness( + function: u8, + a: [u8; 32], + b: [u8; 32], + expected: Option<[u8; 32]>, + expected_bool: bool, + second_expected: [u8; 32], +) -> U256TestSubMulWitness { + U256TestSubMulWitness { + function_index: function, + first_arg: a, + second_arg: b, + expected, + expected_bool, + second_expected, + } +} + +fn split_u512(a: [u8; 64]) -> ([u8; 32], [u8; 32]) { + let high = U256::from_big_endian(&a[0..32]); + let low = U256::from_big_endian(&a[32..64]); + + (high.to_big_endian(), low.to_big_endian()) +} + +mod u256_tests_arithmetic { + use super::*; + + #[simplex::test] + fn u256_test_sub_256_not_overflow(context: simplex::TestContext) -> anyhow::Result<()> { + let a = generate_u256(U256::zero(), U256::MAX); + let b = generate_u256(U256::zero(), a); + let result = (a - b).to_big_endian(); + + run( + &context, + program(), + build_witness( + op(FunctionToTest::Sub256), + a.to_big_endian(), + b.to_big_endian(), + Some(result), + false, + DEFAULT_EXPECTED, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_sub_256_a_eq_b(context: simplex::TestContext) -> anyhow::Result<()> { + let a = generate_u256(U256::zero(), U256::MAX).to_big_endian(); + + run( + &context, + program(), + build_witness( + op(FunctionToTest::Sub256), + a, + a, + Some([0; 32]), + false, + DEFAULT_EXPECTED, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_sub_256_a_low_eq_b_low(context: simplex::TestContext) -> anyhow::Result<()> { + let a = generate_u256(U256::zero(), U256::MAX); + let b_high = rand::thread_rng().gen_range(0..=u128::MAX); + + let low: u128 = a.low_u128(); + let b = (U256::from(b_high) << 128) | U256::from(low); + + let (result, carry) = a.overflowing_sub(b); + + run( + &context, + program(), + build_witness( + op(FunctionToTest::Sub256), + a.to_big_endian(), + b.to_big_endian(), + Some(result.to_big_endian()), + carry, + DEFAULT_EXPECTED, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_sub_256_diff_is_u128_max(context: simplex::TestContext) -> anyhow::Result<()> { + let a_low: u128 = u128::MAX; + + let a_high = rand::thread_rng().gen_range(0..=u128::MAX); + let b_high = rand::thread_rng().gen_range(0..=u128::MAX); + + let a = (U256::from(a_high) << 128) | U256::from(a_low); + let b = (U256::from(b_high)) << 128; // b_low is 0 + + let (result, carry) = a.overflowing_sub(b); + + run( + &context, + program(), + build_witness( + op(FunctionToTest::Sub256), + a.to_big_endian(), + b.to_big_endian(), + Some(result.to_big_endian()), + carry, + DEFAULT_EXPECTED, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_sub_256_diff_is_u256_max(context: simplex::TestContext) -> anyhow::Result<()> { + let a = U256::MAX.to_big_endian(); + let b = U256::zero(); + + run( + &context, + program(), + build_witness( + op(FunctionToTest::Sub256), + a, + b.to_big_endian(), + Some(a), + false, + DEFAULT_EXPECTED, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_sub_256_overflow(context: simplex::TestContext) -> anyhow::Result<()> { + let a = generate_u256(U256::one(), U256::MAX - 1); + let b = U256::MAX; + let result = a + 1; + + run( + &context, + program(), + build_witness( + op(FunctionToTest::Sub256), + a.to_big_endian(), + b.to_big_endian(), + Some(result.to_big_endian()), + true, + DEFAULT_EXPECTED, + ), + Expect::Ok, + ) + } + + #[simplex::test] + fn u256_test_mul_256(context: simplex::TestContext) -> anyhow::Result<()> { + let a = generate_u256(U256::one(), U256::MAX); + let b = generate_u256(U256::one(), U256::MAX); + let result = a.full_mul(b).to_big_endian(); + + let (result_high, result_low) = split_u512(result); + + run( + &context, + program(), + build_witness( + op(FunctionToTest::Mul256), + a.to_big_endian(), + b.to_big_endian(), + Some(result_high), + DEFAULT_BOOL, + result_low, + ), + Expect::Ok, + ) + } +} diff --git a/tests/u32_test.rs b/tests/u32_test.rs index 7c76910..bf3fd70 100644 --- a/tests/u32_test.rs +++ b/tests/u32_test.rs @@ -33,6 +33,6 @@ impl TestUint for u32 { mod u32_tests { use super::*; - // Stamps the 16 `#[simplex::test]` entry points for u32. Logic lives in common::uint. + // Stamps the 22 `#[simplex::test]` entry points for u32. Logic lives in common::uint. uint_tests!(u32); } diff --git a/tests/u64_test.rs b/tests/u64_test.rs index 59cbed8..bb30a03 100644 --- a/tests/u64_test.rs +++ b/tests/u64_test.rs @@ -50,7 +50,7 @@ mod u64_tests { use super::*; - // Stamps the 16 `#[simplex::test]` entry points for u64. Logic lives in common::uint. + // Stamps the 22 `#[simplex::test]` entry points for u64. Logic lives in common::uint. uint_tests!(u64); #[simplex::test] diff --git a/tests/u8_math_tests.rs b/tests/u8_math_tests.rs index 2d5c120..cc69367 100644 --- a/tests/u8_math_tests.rs +++ b/tests/u8_math_tests.rs @@ -35,6 +35,6 @@ impl TestUint for u8 { mod u8_math_tests { use super::*; - // Stamps the 16 `#[simplex::test]` entry points for u8. Logic lives in common::uint. + // Stamps the 22 `#[simplex::test]` entry points for u8. Logic lives in common::uint. uint_tests!(u8); }