Skip to content
Open
Changes from all 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
52 changes: 44 additions & 8 deletions src/wnaf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,23 +150,45 @@ pub(crate) fn wnaf_form<S: AsRef<[u8]>>(wnaf: &mut Vec<i64>, c: S, window: usize
///
/// This function must be provided a `table` and `wnaf` that were constructed with
/// the same window size; otherwise, it may panic or produce invalid results.
#[inline]
pub(crate) fn wnaf_exp<G: Group>(table: &[G], wnaf: &[i64]) -> G {
let mut result = G::identity();
wnaf_multi_exp(&[table], &[wnaf])
}

/// Performs w-NAF multi-exponentiation using the interleaved window method, also known as
/// Straus' method.
///
/// The key insight is that when computing this sum by means of additions and doublings, the
/// doublings can be shared by performing the additions within an inner loop.
///
/// This function must be provided with `tables` and `wnafs` that were constructed with
/// the same window size; otherwise, it may panic or produce invalid results.
pub(crate) fn wnaf_multi_exp<G: Group, T: AsRef<[G]>, W: AsRef<[i64]>>(
tables: &[T],
wnafs: &[W],
) -> G {
debug_assert_eq!(tables.len(), wnafs.len());
let window_size = wnafs.iter().map(|w| w.as_ref().len()).max().unwrap_or(0);

let mut result = G::identity();
let mut found_one = false;

for n in wnaf.iter().rev() {
for i in (0..window_size).rev() {
// Only double once per iteration of the loop
if found_one {
result = result.double();
}

if *n != 0 {
found_one = true;
for (table, wnaf) in tables.iter().zip(wnafs.iter()) {
let n = wnaf.as_ref().get(i).copied().unwrap_or(0);
if n != 0 {
found_one = true;

if *n > 0 {
result += &table[(n / 2) as usize];
} else {
result -= &table[((-n) / 2) as usize];
if n > 0 {
result += table.as_ref()[(n / 2) as usize];
} else {
result -= table.as_ref()[((-n) / 2) as usize];
}
}
}
}
Expand Down Expand Up @@ -499,6 +521,20 @@ impl<G: Group, const WINDOW_SIZE: usize> WnafBase<G, WINDOW_SIZE> {

WnafBase { table }
}

/// Perform a multiscalar multiplication.
///
/// Computes a sum-of-products `aA + bB + ...` in variable time with w-NAF multi-exponentiation
/// using the interleaved window method, also known as Straus' method.
pub fn multiscalar_mul<I, J>(scalars: I, bases: J) -> G
where
I: IntoIterator<Item = WnafScalar<G::Scalar, WINDOW_SIZE>>,
J: IntoIterator<Item = Self>,
{
Comment on lines +525 to +533

@tarcieri tarcieri Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

In exploring how to adapt the w-NAF implementation in this crate to support no_alloc usage patterns, I ended up going with an API closer to the LinearCombination traits we have in the @RustCrypto elliptic-curve crate which are based on clonable iterators over 2-tuples instead of multiple iterators, which would look more like:

pub fn multiscalar_mul<'a, I>(pairs: I) -> G
where
    I: Clone + Iterator<Item = (&'a Self, &'a WnafScalar<G::Scalar, WINDOW_SIZE>)>,

(The Clone bound is needed to support multiple passes over the iterator)

Notably this style of API makes it much easier to feed the iterators all the way through the implementation into the newly added wnaf_multi_exp such that no intermediate Vec is required, like so:

pub fn multiscalar_mul<'a, I>(pairs: I) -> G
where
    I: Clone + Iterator<Item = (&'a Self, &'a WnafScalar<G::Scalar, WINDOW_SIZE>)>,
{
    wnaf_multi_exp(pairs.map(|(b, s)| (b.table.as_slice(), s.wnaf.as_slice())))
}

fn wnaf_multi_exp<'a, G, I>(terms: I) -> G
where
    G: Group,
    I: Clone + IntoIterator<Item = (&'a [G], &'a [i64])>,

I can throw up an additional commit that implements this approach on top of the current one for comparison.

let wnafs = scalars.into_iter().map(|s| s.wnaf).collect::<Vec<_>>();
let tables = bases.into_iter().map(|b| b.table).collect::<Vec<_>>();
wnaf_multi_exp(tables.as_slice(), wnafs.as_slice())
}
}

impl<G: Group, const WINDOW_SIZE: usize> Mul<&WnafScalar<G::Scalar, WINDOW_SIZE>>
Expand Down
Loading