Skip to main content

wnaf/
lib.rs

1#![no_std]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3#![doc(
4    html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg",
5    html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg"
6)]
7#![allow(clippy::int_plus_one, reason = "clearer for our use cases  ")]
8#![forbid(unsafe_code)]
9#![doc = include_str!("../README.md")]
10
11#[cfg(feature = "alloc")]
12#[macro_use]
13extern crate alloc;
14
15mod base;
16mod limb_buffer;
17mod scalar;
18mod traits;
19
20#[cfg(feature = "alloc")]
21mod boxed;
22
23pub use crate::{
24    base::WnafBase,
25    scalar::WnafScalar,
26    traits::{WindowSize, WnafGroup, WnafSize},
27};
28pub use array;
29pub use group::Group;
30
31#[cfg(feature = "alloc")]
32pub use crate::boxed::BoxedWnaf;
33
34use crate::limb_buffer::LimbBuffer;
35use ff::PrimeField;
36
37/// Type used to represent wNAF digits.
38///
39/// For a window of size `w` non-zero wNAF digits are odd and have magnitude at most `2^(w-1) - 1`
40/// and lie within `{-(2^(w-1)-1), 2^(w-1)-1}`.
41pub type Digit = i8;
42
43/// Maximum supported value for `w`.
44///
45/// This ensures `2^(8-1)-1=127`, so digits lie within `{-127,127}`, which fits in `i8`.
46// NOTE: this is also the maximum impl size we support for the `WindowSize` trait
47pub const W_MAX: usize = 8;
48
49/// Computes a wNAF window table for the given base and window size.
50///
51/// For a window of size `w` non-zero wNAF digits are odd and have magnitude at most `2^(w-1) - 1`.
52///
53/// The table is indexed by `|digit| / 2`, so the required size is `(2^(w-1) - 1) / 2 + 1 = 2^(w-2)`
54/// entries.
55fn wnaf_table<G: Group>(table: &mut [G], base: &G, window: usize) {
56    debug_assert_eq!(table.len(), 1 << (window - 2));
57
58    let dbl = base.double();
59    let mut cur = *base;
60
61    for entry in table {
62        *entry = cur;
63        cur.add_assign(&dbl);
64    }
65}
66
67/// Fills `wnaf` with the wNAF representation of a little-endian scalar, and returns the
68/// number of digits written.
69#[allow(clippy::cast_possible_wrap)]
70fn wnaf_form<S: AsRef<[u8]>>(wnaf: &mut [Digit], c: S, bit_len: usize, window: usize) -> usize {
71    fn digit(n: u64) -> Digit {
72        #[cfg(debug_assertions)]
73        {
74            Digit::try_from(n).expect("overflow")
75        }
76        #[cfg(not(debug_assertions))]
77        {
78            n as Digit
79        }
80    }
81
82    debug_assert!(window >= 2);
83    debug_assert!(window <= W_MAX);
84    debug_assert!(bit_len < wnaf.len(), "wnaf storage too small");
85    debug_assert!(c.as_ref().len() <= bit_len.div_ceil(8), "input too large");
86
87    let width = 1u64 << window;
88    let window_mask = width - 1;
89
90    let mut limbs = LimbBuffer::new(c.as_ref());
91    let mut pos = 0;
92    let mut carry = 0;
93    let mut cursor = 0;
94
95    while pos < bit_len {
96        // Construct a buffer of bits of the scalar, starting at bit `pos`
97        let u64_idx = pos / 64;
98        let bit_idx = pos % 64;
99        let (cur_u64, next_u64) = limbs.get(u64_idx);
100        let bit_buf = if bit_idx + window < 64 {
101            // This window's bits are contained in a single u64
102            cur_u64 >> bit_idx
103        } else {
104            // Combine the current u64's bits with the bits from the next u64
105            (cur_u64 >> bit_idx) | (next_u64 << (64 - bit_idx))
106        };
107
108        // Add the carry into the current window
109        let window_val = carry + (bit_buf & window_mask);
110
111        if window_val & 1 == 0 {
112            // If the window value is even, preserve the carry and emit 0.
113            // If carry == 0 and window_val & 1 == 0, then the next carry should be 0.
114            // If carry == 1 and window_val & 1 == 0, then bit_buf & 1 == 1 so the next carry should
115            // be 1.
116            wnaf[cursor] = 0;
117            cursor += 1;
118            pos += 1;
119        } else {
120            wnaf[cursor] = digit(window_val);
121
122            if window_val < width / 2 {
123                carry = 0;
124            } else {
125                carry = 1;
126                wnaf[cursor] -= digit(width);
127            };
128
129            cursor += 1;
130
131            let max_pos = bit_len.saturating_sub(usize::try_from(carry).expect("overflow"));
132            let skip = window.min(max_pos - pos);
133
134            for _ in 1..skip {
135                wnaf[cursor] = 0;
136                cursor += 1;
137            }
138            pos += skip;
139        }
140    }
141
142    // If there is a remaining carry (the scalar used all `bit_len` bits and the last wNAF digit
143    // was negative), emit it so the representation is exact.
144    if carry != 0 {
145        wnaf[cursor] = digit(carry);
146        cursor += 1;
147    }
148
149    cursor
150}
151
152/// Performs wNAF multi-exponentiation using the interleaved window method, also known as
153/// Straus's method.
154///
155/// The key insight is that when computing this sum by means of additions and doublings, the
156/// doublings can be shared by performing the additions within an inner loop.
157fn wnaf_multi_exp<'a, G, I>(terms: I) -> G
158where
159    G: Group,
160    I: Clone + IntoIterator<Item = (&'a [G], &'a [Digit], usize)>,
161{
162    let window_size = terms
163        .clone()
164        .into_iter()
165        .map(|(_, _, len)| len)
166        .max()
167        .unwrap_or(0);
168
169    let mut result = G::identity();
170    let mut found_one = false;
171
172    for i in (0..window_size).rev() {
173        if found_one {
174            result = result.double();
175        }
176
177        for (table, wnaf, _) in terms.clone() {
178            let n = wnaf.get(i).copied().unwrap_or(0);
179
180            if n != 0 {
181                found_one = true;
182
183                #[allow(clippy::cast_sign_loss)]
184                if n > 0 {
185                    result += table[(n / 2) as usize];
186                } else {
187                    result -= table[((-n) / 2) as usize];
188                }
189            }
190        }
191    }
192
193    result
194}
195
196/// Get the little endian representation of a field, namely a scalar.
197fn le_repr<F: PrimeField>(fe: &F) -> F::Repr {
198    let mut ret = fe.to_repr();
199    // TODO(tarcieri): determine endianness via `PrimeField` trait. See zkcrypto/rfcs#4
200    ret.as_mut().reverse();
201    ret
202}