Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

ff: refactor find_naf and add unit tests #896

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 62 additions & 43 deletions ff/src/biginteger/arithmetic.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use ark_std::Zero;
use ark_std::{vec, vec::*};

macro_rules! adc {
Expand Down Expand Up @@ -153,53 +152,30 @@ pub fn mac_with_carry(a: u64, b: u64, c: u64, carry: &mut u64) -> u64 {

/// Compute the NAF (non-adjacent form) of num
pub fn find_naf(num: &[u64]) -> Vec<i8> {
let is_zero = |num: &[u64]| num.iter().all(Zero::is_zero);
let is_odd = |num: &[u64]| num[0] & 1 == 1;
let sub_noborrow = |num: &mut [u64], z: u64| {
let mut other = vec![0u64; num.len()];
other[0] = z;
let mut borrow = 0;

for (a, b) in num.iter_mut().zip(other) {
borrow = sbb(a, b, borrow);
}
};
let add_nocarry = |num: &mut [u64], z: u64| {
let mut other = vec![0u64; num.len()];
other[0] = z;
let mut carry = 0;

for (a, b) in num.iter_mut().zip(other) {
carry = adc(a, b, carry);
}
};
let div2 = |num: &mut [u64]| {
let mut t = 0;
for i in num.iter_mut().rev() {
let t2 = *i << 63;
*i >>= 1;
*i |= t;
t = t2;
}
};

let mut num = num.to_vec();
let mut res = vec![];

while !is_zero(&num) {
let z: i8;
if is_odd(&num) {
z = 2 - (num[0] % 4) as i8;
if z >= 0 {
sub_noborrow(&mut num, z as u64)
} else {
add_nocarry(&mut num, (-z) as u64)
}
while num.iter().any(|&x| x != 0) {
let z = if num[0] & 1 == 1 {
let z = 2 - (num[0] % 4) as i8;
// Choose the appropriate operation: subtract (sbb) if z >= 0, otherwise add (adc).
let op = if z >= 0 { sbb } else { adc };
num.iter_mut()
.zip(ark_std::iter::once(&(z.abs() as u64)).chain(ark_std::iter::repeat(&0)))
.fold(0, |carry, (a, b)| op(a, *b, carry));
z
} else {
z = 0;
}
0
};

res.push(z);
div2(&mut num);

// Perform an in-place division of `num` by 2, using bit-shifting.
num.iter_mut().rev().fold(0, |carry, x| {
let next_carry = *x << 63;
*x = (*x >> 1) | carry;
next_carry
});
}
Copy link
Member

Choose a reason for hiding this comment

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

Thanks for the PR! I think I like the old code structure slightly better since it separates out the arithmetic logic from the algorithm logic. I do like the new implementations of the arithmetic better, so maybe we can go back to the old code structure but with the new arithmetic impls?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed, I've also added a more complete documentation inside the function for the reader to understand the various operations that are performed.


res
Expand Down Expand Up @@ -454,4 +430,47 @@ mod tests {
assert_eq!(test, test_expected);
}
}

#[test]
fn test_find_naf_zero() {
// Test for zero input
let naf = find_naf(&[0]);
assert!(naf.is_empty());
}

#[test]
fn test_find_naf_single_digit() {
// Test for small numbers
assert_eq!(find_naf(&[1]), vec![1]);
assert_eq!(find_naf(&[2]), vec![0, 1]);
assert_eq!(find_naf(&[3]), vec![-1, 0, 1]);
assert_eq!(find_naf(&[4]), vec![0, 0, 1]);
}

#[test]
fn test_find_naf_large_number() {
// Test for a larger number
assert_eq!(find_naf(&[13]), vec![1, 0, -1, 0, 1]);
}

#[test]
fn test_find_naf_multiple_blocks() {
// Test multi-block number (simulate large numbers split across blocks)
let num = [0, 1];
assert_eq!(
find_naf(&num),
vec![
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1
]
);
}

#[test]
fn test_find_naf_edge_cases() {
// Test edge cases
let naf = find_naf(&[u64::MAX]);
assert!(naf.len() > 0);
}
}
Loading