From 55949f961e9e52a43c57acdc1c6438050772d580 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Fri, 6 Feb 2026 16:50:57 -0700 Subject: [PATCH 1/5] wnaf: support windows of size 64 Followup to #46 which attempted to change an assertion for the window size from `<= 64` to `< 64`, which was in response to the `width` type being `u64` and its value computed as `1 << window`, which would overflow a `u64`. This means `width` needs an extra bit, so this promotes it to a `u128` while keeping any variables that can remain `u64` as they were. --- src/wnaf.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/wnaf.rs b/src/wnaf.rs index 175d676..fa80f74 100644 --- a/src/wnaf.rs +++ b/src/wnaf.rs @@ -104,8 +104,8 @@ pub(crate) fn wnaf_form>(wnaf: &mut Vec, c: S, window: usize // Initialise the current and next limb buffers. let mut limbs = LimbBuffer::new(c.as_ref()); - let width = 1u64 << window; - let window_mask = width - 1; + let width = 1u128 << window; + let window_mask = (width - 1) as u64; // max `width` is `1 << 64 == u64::MAX + 1` let mut pos = 0; let mut carry = 0; @@ -133,12 +133,13 @@ pub(crate) fn wnaf_form>(wnaf: &mut Vec, c: S, window: usize wnaf.push(0); pos += 1; } else { - wnaf.push(if window_val < width / 2 { + // max `width` is `1 << 64`, which becomes `1 << 63` when divided by 2, which fits `u64` + wnaf.push(if window_val < (width / 2) as u64 { carry = 0; window_val as i64 } else { carry = 1; - (window_val as i64).wrapping_sub(width as i64) + (window_val as i128).wrapping_sub(width as i128) as i64 }); wnaf.extend(iter::repeat(0).take(window - 1)); pos += window; From 11fafeb92f8edbfa67863413c1c828221f19d73c Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Fri, 6 Feb 2026 16:57:25 -0700 Subject: [PATCH 2/5] CI: remove libfontconfig install Otherwise CI breaks. See also: https://github.com/zkcrypto/group/pull/71/files/383558c298e2cacc15a7d3151b526b2418b39a17#diff-b803fcb7f17ed9235f1e5cb1fcd2f5d3b2838429d4368ae4c57ce4436577f03f --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 39f9e7b..c966e66 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -81,7 +81,6 @@ jobs: - uses: actions/checkout@v4 - run: cargo fetch # Requires #![deny(rustdoc::broken_intra_doc_links)] in crates. - - run: sudo apt-get -y install libfontconfig1-dev - name: Check intra-doc links run: cargo doc --all-features --document-private-items From 6a7d96ac3884fb7cd0ae2ddfcc36a949d9556094 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Sat, 7 Feb 2026 11:12:48 -0700 Subject: [PATCH 3/5] Avoid u128 by computing width_div_2 instead --- src/wnaf.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/wnaf.rs b/src/wnaf.rs index fa80f74..34e7e2a 100644 --- a/src/wnaf.rs +++ b/src/wnaf.rs @@ -104,8 +104,9 @@ pub(crate) fn wnaf_form>(wnaf: &mut Vec, c: S, window: usize // Initialise the current and next limb buffers. let mut limbs = LimbBuffer::new(c.as_ref()); - let width = 1u128 << window; - let window_mask = (width - 1) as u64; // max `width` is `1 << 64 == u64::MAX + 1` + let width_div_2 = 1u64 << (window - 1); // window is asserted `2..=64` above + let width = (width_div_2 as i128) * 2; // for conditionally subtracting from `window_val` + let window_mask = width_div_2 | (width_div_2 - 1); // fill in 1s for all the lower 0 bits let mut pos = 0; let mut carry = 0; @@ -133,13 +134,12 @@ pub(crate) fn wnaf_form>(wnaf: &mut Vec, c: S, window: usize wnaf.push(0); pos += 1; } else { - // max `width` is `1 << 64`, which becomes `1 << 63` when divided by 2, which fits `u64` - wnaf.push(if window_val < (width / 2) as u64 { + wnaf.push(if window_val < width_div_2 { carry = 0; window_val as i64 } else { carry = 1; - (window_val as i128).wrapping_sub(width as i128) as i64 + ((window_val as i128) - width) as i64 }); wnaf.extend(iter::repeat(0).take(window - 1)); pos += window; From 96b0a2d452bb3cf689d41048203a1e031d4ac0d4 Mon Sep 17 00:00:00 2001 From: kilic Date: Wed, 1 Feb 2023 20:37:23 +0300 Subject: [PATCH 4/5] fix: allow wnaf form to represent dense values --- src/wnaf.rs | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/wnaf.rs b/src/wnaf.rs index 34e7e2a..f7650a1 100644 --- a/src/wnaf.rs +++ b/src/wnaf.rs @@ -94,12 +94,12 @@ pub(crate) fn wnaf_form>(wnaf: &mut Vec, c: S, window: usize // Required by the NAF definition debug_assert!(window >= 2); // Required so that the NAF digits fit in i64 - debug_assert!(window <= 64); + debug_assert!(window < 64); let bit_len = c.as_ref().len() * 8; wnaf.truncate(0); - wnaf.reserve(bit_len); + wnaf.reserve(bit_len + 1); // Initialise the current and next limb buffers. let mut limbs = LimbBuffer::new(c.as_ref()); @@ -110,7 +110,7 @@ pub(crate) fn wnaf_form>(wnaf: &mut Vec, c: S, window: usize let mut pos = 0; let mut carry = 0; - while pos < bit_len { + while pos <= bit_len { // Construct a buffer of bits of the scalar, starting at bit `pos` let u64_idx = pos / 64; let bit_idx = pos % 64; @@ -145,6 +145,7 @@ pub(crate) fn wnaf_form>(wnaf: &mut Vec, c: S, window: usize pos += window; } } + wnaf.truncate(wnaf.len().saturating_sub(window - 1)); } /// Performs w-NAF exponentiation with the provided window table and w-NAF form scalar. @@ -505,3 +506,28 @@ impl Mul<&WnafScalar wnaf_exp(&self.table, &rhs.wnaf) } } + +#[test] +fn test_wnaf_form() { + fn from_wnaf(wnaf: &Vec) -> u128 { + wnaf.iter().rev().fold(0, |acc, next| { + let mut acc = acc * 2; + acc += *next as u128; + acc + }) + } + for w in 2..64 { + for e in 0..=u16::MAX { + let mut wnaf = vec![]; + wnaf_form(&mut wnaf, e.to_le_bytes(), w); + assert_eq!(e as u128, from_wnaf(&wnaf)); + } + } + for w in 2..64 { + for e in u128::MAX - 10000..=u128::MAX { + let mut wnaf = vec![]; + wnaf_form(&mut wnaf, e.to_le_bytes(), w); + assert_eq!(e, from_wnaf(&wnaf)); + } + } +} From d24afd49472d8f80619afd089208025cc1705d79 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Fri, 6 Feb 2026 15:28:59 -0700 Subject: [PATCH 5/5] [WIP] Support dense wNAF --- src/wnaf.rs | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/wnaf.rs b/src/wnaf.rs index f7650a1..336ea0a 100644 --- a/src/wnaf.rs +++ b/src/wnaf.rs @@ -94,7 +94,7 @@ pub(crate) fn wnaf_form>(wnaf: &mut Vec, c: S, window: usize // Required by the NAF definition debug_assert!(window >= 2); // Required so that the NAF digits fit in i64 - debug_assert!(window < 64); + debug_assert!(window <= 64); let bit_len = c.as_ref().len() * 8; @@ -145,6 +145,8 @@ pub(crate) fn wnaf_form>(wnaf: &mut Vec, c: S, window: usize pos += window; } } + + // Remove trailing zeros left by the loop above wnaf.truncate(wnaf.len().saturating_sub(window - 1)); } @@ -511,21 +513,30 @@ impl Mul<&WnafScalar fn test_wnaf_form() { fn from_wnaf(wnaf: &Vec) -> u128 { wnaf.iter().rev().fold(0, |acc, next| { - let mut acc = acc * 2; - acc += *next as u128; - acc + // If one of the least-significant w-NAF limbs is negative, `acc` may be large + // (due to the result being represented as a `u128`). `wrapping_mul` wraps at + // the boundary of the type; in this case, it has the effect of doubling the + // equivalent signed value: + // acc = -x = u128::MAX + 1 - x + // 2 * acc = 2 * (u128::MAX + 1 - x) + // = 2 * (u128::MAX + 1) - 2x + // = -2x as u128 + let acc = acc.wrapping_mul(2); + // Rust signed-to-unsigned casts add or subtract `T::MAX + 1` until the value + // fits into the new type. A wrapping addition of the new type is therefore + // equivalent to a wrapping subtraction of the magnitude of the original type. + acc.wrapping_add(*next as u128) }) } + let mut wnaf = Vec::with_capacity(129); for w in 2..64 { for e in 0..=u16::MAX { - let mut wnaf = vec![]; wnaf_form(&mut wnaf, e.to_le_bytes(), w); assert_eq!(e as u128, from_wnaf(&wnaf)); } } for w in 2..64 { for e in u128::MAX - 10000..=u128::MAX { - let mut wnaf = vec![]; wnaf_form(&mut wnaf, e.to_le_bytes(), w); assert_eq!(e, from_wnaf(&wnaf)); }