Skip to main content

rhdl_ed25519_scalable_core/
host.rs

1//! Host-side reference arithmetic for the scalable datapath.
2//!
3//! These functions run on the build/test host, never in hardware. They
4//! generate ROM contents (the fixed-base table and constants) and give the
5//! microcode interpreter bit-exact semantics: every field value is canonical,
6//! strictly below `p = 2^255 - 19`, exactly as the RHDL multiplier and
7//! add/subtract units produce.
8//!
9//! The module also holds the reference curve model ([`Ext`], [`Niels`]),
10//! point encoding and strict decoding, the small-order encoding list, and the
11//! mixed-radix scalar recoding ([`recode`]) with its digit layout
12//! ([`DIGITS`], [`digit_offset`], [`table_row`]). None of it is constant
13//! time; it only computes reference values.
14
15use std::sync::OnceLock;
16
17/// Canonical field element modulo `2^255 - 19`, four little-endian words.
18///
19/// Every operation returns a fully reduced value, so `==` on the words is
20/// field equality and the low bit is the Ed25519 sign.
21#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
22pub struct Fp(pub [u64; 4]);
23
24/// The field prime `p = 2^255 - 19` as four little-endian words.
25const P: [u64; 4] = [
26    0xffff_ffff_ffff_ffed,
27    0xffff_ffff_ffff_ffff,
28    0xffff_ffff_ffff_ffff,
29    0x7fff_ffff_ffff_ffff,
30];
31
32/// `a >= b` for four-word little-endian integers.
33fn ge(a: &[u64; 4], b: &[u64; 4]) -> bool {
34    // Compare from the most significant word down.
35    for i in (0..4).rev() {
36        if a[i] != b[i] {
37            return a[i] > b[i];
38        }
39    }
40    true
41}
42
43/// `a - b mod 2^256` for four-word little-endian integers.
44fn sub_raw(a: &[u64; 4], b: &[u64; 4]) -> [u64; 4] {
45    let mut out = [0u64; 4];
46    let mut borrow = 0u64;
47    for i in 0..4 {
48        // Borrow out of this word if either subtraction wrapped.
49        let (d, b1) = a[i].overflowing_sub(b[i]);
50        let (d, b2) = d.overflowing_sub(borrow);
51        out[i] = d;
52        borrow = u64::from(b1 || b2);
53    }
54    out
55}
56
57impl Fp {
58    /// Zero.
59    pub const ZERO: Fp = Fp([0; 4]);
60    /// One.
61    pub const ONE: Fp = Fp([1, 0, 0, 0]);
62
63    /// Reduce an arbitrary 512-bit little-endian value.
64    fn reduce512(w: [u64; 8]) -> Fp {
65        // 2^256 = 38 (mod p).
66        // Fold the high 256 bits into the low ones: acc[i] = w[i] + 38 w[i+4]
67        // (acc[4] stays zero).
68        let mut acc = [0u128; 5];
69        for i in 0..4 {
70            acc[i] += u128::from(w[i]) + u128::from(w[i + 4]) * 38;
71        }
72        // Propagate carries into 64-bit limbs; limbs[4] is small (below 40).
73        let mut limbs = [0u64; 5];
74        let mut carry = 0u128;
75        for i in 0..4 {
76            let v = acc[i] + carry;
77            limbs[i] = v as u64;
78            carry = v >> 64;
79        }
80        limbs[4] = carry as u64;
81        // Fold bit 255 and above: value = lo255 + top * 2^255, 2^255 = 19.
82        let mut v = [limbs[0], limbs[1], limbs[2], limbs[3]];
83        let top = (u128::from(limbs[4]) << 1) | u128::from(v[3] >> 63);
84        v[3] &= 0x7fff_ffff_ffff_ffff;
85        let mut c = top * 19;
86        for limb in &mut v {
87            let s = u128::from(*limb) + c;
88            *limb = s as u64;
89            c = s >> 64;
90        }
91        // The value is now below 2^255 + a small amount; subtract p until
92        // canonical.
93        let mut out = Fp(v);
94        while ge(&out.0, &P) {
95            out = Fp(sub_raw(&out.0, &P));
96        }
97        out
98    }
99
100    /// Canonical value from 32 little-endian bytes (reduced modulo `p`).
101    ///
102    /// Bit 255 is included in the value, so callers decoding a point must
103    /// clear the sign bit first.
104    pub fn from_bytes(bytes: &[u8; 32]) -> Fp {
105        let mut w = [0u64; 8];
106        for i in 0..4 {
107            w[i] = u64::from_le_bytes(bytes[i * 8..i * 8 + 8].try_into().unwrap());
108        }
109        Fp::reduce512(w)
110    }
111
112    /// Canonical little-endian encoding.
113    pub fn to_bytes(self) -> [u8; 32] {
114        let mut out = [0u8; 32];
115        for i in 0..4 {
116            out[i * 8..i * 8 + 8].copy_from_slice(&self.0[i].to_le_bytes());
117        }
118        out
119    }
120
121    /// Sum modulo `p`.
122    pub fn add(self, o: Fp) -> Fp {
123        // 257-bit sum, reduced by the general path.
124        let mut w = [0u64; 8];
125        let mut carry = 0u128;
126        for i in 0..4 {
127            let s = u128::from(self.0[i]) + u128::from(o.0[i]) + carry;
128            w[i] = s as u64;
129            carry = s >> 64;
130        }
131        w[4] = carry as u64;
132        Fp::reduce512(w)
133    }
134
135    /// Difference modulo `p`.
136    pub fn sub(self, o: Fp) -> Fp {
137        // -o as p - o, except that -0 is 0 (p itself is not canonical).
138        let neg = if o == Fp::ZERO {
139            Fp::ZERO
140        } else {
141            Fp(sub_raw(&P, &o.0))
142        };
143        self.add(neg)
144    }
145
146    /// Product modulo `p`.
147    pub fn mul(self, o: Fp) -> Fp {
148        // Schoolbook 256 x 256 -> 512-bit product, one 64-bit word per entry
149        // of `w` (u128 entries hold intermediate sums).
150        let mut w = [0u128; 8];
151        for i in 0..4 {
152            let mut carry = 0u128;
153            for j in 0..4 {
154                let t = w[i + j] + u128::from(self.0[i]) * u128::from(o.0[j]) + carry;
155                w[i + j] = t & u128::from(u64::MAX);
156                carry = t >> 64;
157            }
158            w[i + 4] += carry;
159        }
160        Fp::reduce512(w.map(|x| x as u64))
161    }
162
163    /// Square modulo `p`.
164    pub fn square(self) -> Fp {
165        self.mul(self)
166    }
167
168    /// `self^(2^k)`.
169    pub fn pow2k(self, k: u32) -> Fp {
170        let mut x = self;
171        for _ in 0..k {
172            x = x.square();
173        }
174        x
175    }
176
177    /// Multiplicative inverse (zero maps to zero).
178    ///
179    /// Fermat's little theorem, `self^(p-2)`, by plain square-and-multiply.
180    pub fn invert(self) -> Fp {
181        // p - 2 = 2^255 - 21.
182        let mut result = Fp::ONE;
183        let e: [u64; 4] = sub_raw(&P, &[2, 0, 0, 0]);
184        // Exponent bits from the top (bit 254) down.
185        for i in (0..255).rev() {
186            result = result.square();
187            if (e[i / 64] >> (i % 64)) & 1 == 1 {
188                result = result.mul(self);
189            }
190        }
191        result
192    }
193
194    /// Negation modulo `p`.
195    pub fn neg(self) -> Fp {
196        Fp::ZERO.sub(self)
197    }
198
199    /// Least-significant bit of the canonical value (Ed25519 "negative").
200    pub fn is_negative(self) -> bool {
201        self.0[0] & 1 == 1
202    }
203
204    /// Fifteen 17-bit digits of the canonical value, least significant first.
205    ///
206    /// This is the digit layout of the RHDL field units (15 x 17 = 255 bits).
207    pub fn digits17(self) -> [u32; 15] {
208        let mut out = [0u32; 15];
209        for (k, digit) in out.iter_mut().enumerate() {
210            let mut v = 0u32;
211            for b in 0..17 {
212                // Absolute bit index of bit `b` of digit `k`.
213                let bit = 17 * k + b;
214                v |= (((self.0[bit / 64] >> (bit % 64)) & 1) as u32) << b;
215            }
216            *digit = v;
217        }
218        out
219    }
220
221    /// Inverse of [`Self::digits17`]; the digits must encode a value below `p`.
222    ///
223    /// The value is not reduced, so out-of-range digits give a non-canonical
224    /// `Fp`.
225    pub fn from_digits17(d: &[u32; 15]) -> Fp {
226        let mut w = [0u64; 4];
227        for (k, digit) in d.iter().enumerate() {
228            for b in 0..17 {
229                if (digit >> b) & 1 == 1 {
230                    let bit = 17 * k + b;
231                    w[bit / 64] |= 1 << (bit % 64);
232                }
233            }
234        }
235        Fp(w)
236    }
237
238    /// A small integer as a field element (canonical because `v < p`).
239    fn from_u64(v: u64) -> Fp {
240        Fp([v, 0, 0, 0])
241    }
242}
243
244/// Edwards curve constant `d = -121665/121666`.
245pub fn edwards_d() -> Fp {
246    Fp::from_u64(121665)
247        .neg()
248        .mul(Fp::from_u64(121666).invert())
249}
250
251/// `2d`, the constant the addition formulas and the Niels form use.
252pub fn edwards_d2() -> Fp {
253    let d = edwards_d();
254    d.add(d)
255}
256
257/// `sqrt(-1)`, i.e. `2^((p-1)/4)`.
258///
259/// 2 is a non-square modulo `p`, so `2^((p-1)/2) = -1` and its square root
260/// `2^((p-1)/4)` is a square root of `-1`.
261pub fn sqrt_m1() -> Fp {
262    // (p - 1)/4 = 2^253 - 5.
263    let mut result = Fp::ONE;
264    let two = Fp::from_u64(2);
265    let e: [u64; 4] = [
266        0xffff_ffff_ffff_fffb,
267        0xffff_ffff_ffff_ffff,
268        0xffff_ffff_ffff_ffff,
269        0x1fff_ffff_ffff_ffff,
270    ];
271    // Square-and-multiply from bit 252 down.
272    for i in (0..253).rev() {
273        result = result.square();
274        if (e[i / 64] >> (i % 64)) & 1 == 1 {
275            result = result.mul(two);
276        }
277    }
278    result
279}
280
281/// Extended twisted-Edwards point `(X:Y:Z:T)`.
282///
283/// Affine `x = X/Z`, `y = Y/Z`, on `-x^2 + y^2 = 1 + d x^2 y^2`.
284#[derive(Copy, Clone, Debug, PartialEq, Eq)]
285pub struct Ext {
286    /// X.
287    pub x: Fp,
288    /// Y.
289    pub y: Fp,
290    /// Z.
291    pub z: Fp,
292    /// T = XY/Z.
293    pub t: Fp,
294}
295
296/// Affine Niels coordinates `(y+x, y-x, 2dxy)`.
297///
298/// The form the tables store: a mixed addition with it needs no `Z` and no
299/// multiply by `2d`, and negation only swaps two coordinates and negates the
300/// third.
301#[derive(Copy, Clone, Debug, PartialEq, Eq)]
302pub struct Niels {
303    /// `y + x`.
304    pub ypx: Fp,
305    /// `y - x`.
306    pub ymx: Fp,
307    /// `2dxy`.
308    pub xy2d: Fp,
309}
310
311impl Niels {
312    /// Niels form of the identity `(0, 1)`: `(1, 1, 0)`.
313    pub fn identity() -> Self {
314        Niels {
315            ypx: Fp::ONE,
316            ymx: Fp::ONE,
317            xy2d: Fp::ZERO,
318        }
319    }
320    /// Negated point: `-(x, y) = (-x, y)` swaps `y+x` and `y-x` and negates
321    /// `2dxy`.
322    pub fn neg(self) -> Self {
323        Niels {
324            ypx: self.ymx,
325            ymx: self.ypx,
326            xy2d: self.xy2d.neg(),
327        }
328    }
329}
330
331impl Ext {
332    /// Negated point `(-X:Y:Z:-T)`.
333    pub fn neg(self) -> Ext {
334        Ext {
335            x: self.x.neg(),
336            y: self.y,
337            z: self.z,
338            t: self.t.neg(),
339        }
340    }
341
342    /// Identity `(0:1:1:0)`.
343    pub fn identity() -> Self {
344        Ext {
345            x: Fp::ZERO,
346            y: Fp::ONE,
347            z: Fp::ONE,
348            t: Fp::ZERO,
349        }
350    }
351
352    /// Ed25519 basepoint: `y = 4/5`, `x` non-negative (even).
353    pub fn basepoint() -> Self {
354        let y = Fp::from_u64(4).mul(Fp::from_u64(5).invert());
355        let x = recover_x(y, false).expect("basepoint");
356        Ext {
357            x,
358            y,
359            z: Fp::ONE,
360            t: x.mul(y),
361        }
362    }
363
364    /// Complete unified addition (Hisil et al., a = -1).
365    ///
366    /// Complete means it is correct for every pair of inputs, including
367    /// doubling and the identity, which keeps this reference model simple.
368    pub fn add(self, o: Ext) -> Ext {
369        let d2 = edwards_d2();
370        let a = self.y.sub(self.x).mul(o.y.sub(o.x));
371        let b = self.y.add(self.x).mul(o.y.add(o.x));
372        let c = self.t.mul(d2).mul(o.t);
373        let d = self.z.add(self.z).mul(o.z);
374        let e = b.sub(a);
375        let f = d.sub(c);
376        let g = d.add(c);
377        let h = b.add(a);
378        Ext {
379            x: e.mul(f),
380            y: g.mul(h),
381            z: f.mul(g),
382            t: e.mul(h),
383        }
384    }
385
386    /// Doubling, through the complete addition.
387    pub fn double(self) -> Ext {
388        self.add(self)
389    }
390
391    /// Affine coordinates `(X/Z, Y/Z)`.
392    pub fn affine(self) -> (Fp, Fp) {
393        let zi = self.z.invert();
394        (self.x.mul(zi), self.y.mul(zi))
395    }
396
397    /// Standard 32-byte compressed encoding: `y` little endian with the sign
398    /// of `x` in bit 255.
399    pub fn compress(self) -> [u8; 32] {
400        let (x, y) = self.affine();
401        let mut s = y.to_bytes();
402        // Bit 255 of a canonical y is zero, so XOR sets it.
403        s[31] ^= u8::from(x.is_negative()) << 7;
404        s
405    }
406
407    /// Affine Niels form.
408    pub fn niels(self) -> Niels {
409        let (x, y) = self.affine();
410        Niels {
411            ypx: y.add(x),
412            ymx: y.sub(x),
413            xy2d: edwards_d2().mul(x).mul(y),
414        }
415    }
416
417    /// Scalar multiplication by a little-endian 256-bit integer (host only).
418    ///
419    /// Double-and-add from the top bit, not constant time.
420    pub fn mul_bytes(self, k: &[u8; 32]) -> Ext {
421        let mut acc = Ext::identity();
422        for i in (0..256).rev() {
423            acc = acc.double();
424            if (k[i / 8] >> (i % 8)) & 1 == 1 {
425                acc = acc.add(self);
426            }
427        }
428        acc
429    }
430}
431
432/// The `x` with the given sign for which `(x, y)` is on the curve, or
433/// `None` if `y` has no such `x`.
434///
435/// From the curve equation, `x^2 = (y^2 - 1) / (d y^2 + 1)`. Returns
436/// `Some(0)` for `x = 0` even when `negative` is set; [`decode_point`]
437/// rejects that encoding.
438fn recover_x(y: Fp, negative: bool) -> Option<Fp> {
439    let d = edwards_d();
440    let yy = y.square();
441    // x^2 = u / v.
442    let u = yy.sub(Fp::ONE);
443    let v = d.mul(yy).add(Fp::ONE);
444    let x2 = u.mul(v.invert());
445    // x = x2^((p+3)/8), (p+3)/8 = 2^252 - 2.
446    // Because p = 5 (mod 8), this is a square root of x2 or of -x2.
447    let mut x = Fp::ONE;
448    let e: [u64; 4] = [
449        0xffff_ffff_ffff_fffe,
450        0xffff_ffff_ffff_ffff,
451        0xffff_ffff_ffff_ffff,
452        0x0fff_ffff_ffff_ffff,
453    ];
454    for i in (0..252).rev() {
455        x = x.square();
456        if (e[i / 64] >> (i % 64)) & 1 == 1 {
457            x = x.mul(x2);
458        }
459    }
460    // A root of -x2: multiply by sqrt(-1) to get a root of x2.
461    if x.square() != x2 {
462        x = x.mul(sqrt_m1());
463    }
464    // Still not a root: x2 is not a square, so no point has this y.
465    if x.square() != x2 {
466        return None;
467    }
468    // Pick the root whose low bit matches the requested sign.
469    if x.is_negative() != negative {
470        x = x.neg();
471    }
472    Some(x)
473}
474
475/// Digits of the fixed-base recoding (one table per digit).
476pub const DIGITS: usize = 54;
477/// The low `WIDE_DIGITS` digits are signed radix 32, the rest radix 16.
478///
479/// 39 * 5 + 15 * 4 = 255 bits. The split balances the machine's slots: a
480/// radix-32 lookup scans 8 entry pairs and a radix-16 one 4, every digit
481/// costs one 7-multiply mixed addition, and with this split scans and
482/// multiplies both total about 375 per fixed-base multiplication.
483pub const WIDE_DIGITS: usize = 39;
484/// Table rows (entry pairs) over all digits: 39 * 8 + 15 * 4 = 372.
485pub const TABLE_ROWS: usize = WIDE_DIGITS * 8 + (DIGITS - WIDE_DIGITS) * 4;
486
487/// Bit width of digit `i`: 5 for radix 32, 4 for radix 16.
488pub const fn digit_width(i: usize) -> u32 {
489    if i < WIDE_DIGITS { 5 } else { 4 }
490}
491
492/// Bit offset of digit `i`: the sum of the widths of digits `0..i`.
493pub const fn digit_offset(i: usize) -> u32 {
494    if i < WIDE_DIGITS {
495        5 * i as u32
496    } else {
497        5 * WIDE_DIGITS as u32 + 4 * (i - WIDE_DIGITS) as u32
498    }
499}
500
501/// Table entries of digit `i` (magnitudes `1..=entries`).
502///
503/// A signed width-`w` digit has magnitude at most `2^(w-1)`: 16 entries for
504/// radix 32, 8 for radix 16. Zero needs no entry (it selects the identity).
505pub const fn digit_entries(i: usize) -> usize {
506    1 << (digit_width(i) - 1)
507}
508
509/// First table row (entry pair) of digit `i`.
510///
511/// Rows are packed digit after digit: 8 per radix-32 digit, 4 per radix-16
512/// digit.
513pub const fn table_row(i: usize) -> usize {
514    if i < WIDE_DIGITS {
515        8 * i
516    } else {
517        8 * WIDE_DIGITS + 4 * (i - WIDE_DIGITS)
518    }
519}
520
521/// `table[i][e-1] = e * 2^offset(i) * B` in affine Niels form.
522///
523/// Built once on first use (it takes many host-side inversions) and shared.
524pub fn basepoint_table() -> &'static [Vec<Niels>] {
525    static TABLE: OnceLock<Vec<Vec<Niels>>> = OnceLock::new();
526    TABLE.get_or_init(|| point_table(Ext::basepoint()))
527}
528
529/// `table[i][e-1] = e * 2^offset(i) * P` in affine Niels form: the
530/// fixed-base table of any point (the base point, or a verification key).
531pub fn point_table(p: Ext) -> Vec<Vec<Niels>> {
532    // 2^offset(i) * P for the current digit.
533    let mut base = p;
534    let mut table = Vec::with_capacity(DIGITS);
535    for i in 0..DIGITS {
536        let mut row = Vec::with_capacity(digit_entries(i));
537        // e * base, for e = 1, 2, ...
538        let mut multiple = base;
539        for _ in 0..digit_entries(i) {
540            row.push(multiple.niels());
541            multiple = multiple.add(base);
542        }
543        table.push(row);
544        // Advance to the next digit's base.
545        for _ in 0..digit_width(i) {
546            base = base.double();
547        }
548    }
549    table
550}
551
552/// Whether a 32-byte encoding has a canonical `y` (below `p`, sign bit
553/// ignored).
554pub fn canonical_y(bytes: &[u8; 32]) -> bool {
555    let mut y = *bytes;
556    y[31] &= 0x7f;
557    // Compare with p = 2^255 - 19, most significant byte first.
558    let mut p = [0xffu8; 32];
559    p[0] = 0xed;
560    p[31] = 0x7f;
561    for i in (0..32).rev() {
562        if y[i] != p[i] {
563            return y[i] < p[i];
564        }
565    }
566    // y == p is not canonical.
567    false
568}
569
570/// Strict point decoding: canonical `y`, a square root exists, and not the
571/// `x = 0` encoding with the sign bit set. Same rules as the strict
572/// verifier of `rhdl_ed25519_fast_core`.
573pub fn decode_point(bytes: &[u8; 32]) -> Option<Ext> {
574    if !canonical_y(bytes) {
575        return None;
576    }
577    // Bit 255 is the sign of x; the rest is y.
578    let negative = bytes[31] >> 7 == 1;
579    let mut yb = *bytes;
580    yb[31] &= 0x7f;
581    let y = Fp::from_bytes(&yb);
582    let x = recover_x(y, negative)?;
583    // "Negative zero" has no point of its own; reject it.
584    if x == Fp::ZERO && negative {
585        return None;
586    }
587    Some(Ext {
588        x,
589        y,
590        z: Fp::ONE,
591        t: x.mul(y),
592    })
593}
594
595/// The eight encodings of low-order points that strict verification
596/// rejects (the list `rhdl_ed25519_fast_core` checks).
597///
598/// These are the canonical encodings of the eight points of the torsion
599/// subgroup (order dividing 8), compared as four little-endian 64-bit
600/// words. With a small-order key, `[k]A` takes at most eight values, so a
601/// signature can be forged without the secret key; strict verifiers refuse
602/// these encodings.
603pub fn small_order_encoding(bytes: &[u8; 32]) -> bool {
604    const WORDS: [[u64; 4]; 8] = [
605        // y = 1: the identity (order 1).
606        [1, 0, 0, 0],
607        // An order-8 point, x non-negative.
608        [0x4fd84d3d706a17c7, 0x0f67100d760b3cba, 0xc6cc392cfa53202a, 0x7a03ac9277fdc74e],
609        // y = 0 with the sign bit set: the order-4 point whose x is odd.
610        [0, 0, 0, 0x8000_0000_0000_0000],
611        // An order-8 point, x non-negative.
612        [0xb027b2c28f95e826, 0xf098eff289f4c345, 0x3933c6d305acdfd5, 0x05fc536d880238b1],
613        // y = p - 1 = -1: the point (0, -1) (order 2).
614        [0xffff_ffff_ffff_ffec, 0xffff_ffff_ffff_ffff, 0xffff_ffff_ffff_ffff, 0x7fff_ffff_ffff_ffff],
615        // The previous order-8 y with the sign bit set.
616        [0xb027b2c28f95e826, 0xf098eff289f4c345, 0x3933c6d305acdfd5, 0x85fc536d880238b1],
617        // y = 0 with the sign bit clear: the order-4 point whose x is even.
618        [0, 0, 0, 0],
619        // The first order-8 y with the sign bit set.
620        [0x4fd84d3d706a17c7, 0x0f67100d760b3cba, 0xc6cc392cfa53202a, 0xfa03ac9277fdc74e],
621    ];
622    let words: [u64; 4] = std::array::from_fn(|k| u64::from_le_bytes(bytes[8 * k..8 * k + 8].try_into().unwrap()));
623    WORDS.contains(&words)
624}
625
626/// Signed mixed-radix recoding into [`DIGITS`] digits (the rest of the
627/// array is zero): digit `i` lies in `[-2^(w-1), 2^(w-1)]` for its width `w`.
628///
629/// The scalar must be below `2^253` (every scalar the signer multiplies is
630/// reduced modulo `l`), which keeps the top digit in `[0, 4]`.
631///
632/// Signed digits halve the table: magnitudes run to `2^(w-1)` instead of
633/// `2^w - 1`, and the sign is applied by negating the selected entry. The
634/// array has 64 slots to match the machine's per-scalar digit slots.
635pub fn recode(scalar: &[u8; 32]) -> [i8; 64] {
636    assert!(scalar[31] < 32, "scalar must be below 2^253");
637    // Bit `k` of the scalar.
638    let bit = |k: u32| -> i32 { ((scalar[(k / 8) as usize] >> (k % 8)) & 1) as i32 };
639    let mut d = [0i8; 64];
640    // Carry out of the previous digit (0 or 1).
641    let mut carry = 0;
642    for i in 0..DIGITS {
643        let w = digit_width(i);
644        // The digit's w bits plus the incoming carry, in 0..=2^w.
645        let mut v = carry;
646        for b in 0..w {
647            v += bit(digit_offset(i) + b) << b;
648        }
649        // Values from 2^(w-1) up become v - 2^w (in [-2^(w-1), 0]) and carry
650        // 1 into the next digit. The top digit keeps its value: the scalar
651        // bound keeps it at most 4.
652        if i + 1 < DIGITS && v >= 1 << (w - 1) {
653            d[i] = (v - (1 << w)) as i8;
654            carry = 1;
655        } else {
656            d[i] = v as i8;
657            carry = 0;
658        }
659    }
660    d
661}
662
663/// Ed25519 group order `l = 2^252 + 27742317777372353535851937790883648493`,
664/// little endian.
665pub const L_BYTES: [u8; 32] = [
666    0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14,
667    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x10,
668];