Skip to main content

rhdl_ed25519_scalable_core/
programs.rs

1//! The elliptic-curve programs the field machines run.
2//!
3//! | Program | Bundles | Multiplies | Use |
4//! |---|---:|---:|---|
5//! | [`fixed_base`] | 392 | 378 | `[r]B` for signing and key expansion |
6//! | [`fixed_base_affine`] | 657 | 645 | `[r]B` plus its own inversion (fused mode) |
7//! | [`batch_compress`]`(32)` | 423 | 422 | affine `x, y` of 32 points with one inversion |
8//!
9//! Each is plain Rust against [`Builder`],
10//! compiled to bundles, and checked against `curve25519-dalek` through the
11//! interpreter in this module's tests. The verification and key-loading
12//! programs ([`verify_known_key`], [`decode_candidate`], [`table_bases`],
13//! [`digit_table`]) are built the same way.
14//!
15//! # The fixed-base walk
16//!
17//! `[r]B = Σ dᵢ · (2^offsetᵢ · B)`: one table per digit, so no doublings. Each
18//! digit costs one 7-multiply mixed addition, and each lookup costs one table
19//! scan step per entry pair. Choosing the digit sizes balances the two:
20//!
21#![doc = concat!("<div style=\"overflow-x:auto\">", include_str!("../docs/images/slot-balance.svg"), "</div>")]
22//!
23//! The chosen recoding ([`host::recode`](crate::host::recode)) has 39 signed
24//! radix-32 digits and 15 signed radix-16 digits (255 bits; every scalar here
25//! is reduced mod `l` first, so the top digit stays small). How the program
26//! went from 492 to 392 bundles:
27//!
28//! | Change | Bundles |
29//! |---|---:|
30//! | Dalek's radix-16 walk: 64 additions, 4 doublings, two interleaved chains | 492 |
31//! | one table per digit (no doublings); chains start from their first lookup (3 multiplies, not 7) | 458 |
32//! | mixed radix 32/16, digits in order | 435 |
33//! | radix-16 lookups interleaved among radix-32 ones | 399 |
34//! | lookup order from a search with the real scheduler (`LOOKUP_ORDER`) | **392** |
35//!
36//! Additions commute, so the order is free. In digit order the long scans
37//! come first and starve the multiplier; interleaving keeps both busy. The
38//! result fills the multiply, add and scan slots 96 %, 97 % and 95 %.
39//!
40//! # Batch compression
41//!
42//! Encoding needs `1/Z` (265 multiplies by Fermat's little theorem).
43//! Montgomery's trick inverts the product of 32 `Z`s once and recovers each
44//! inverse with three more multiplies: 13.2 per point instead of 265.
45//!
46//! # Key loading
47//!
48//! A verification key `A` arrives as a 32-byte encoding, and verification
49//! needs the fixed-base table of `-A`. Three programs build it, none of them
50//! branching: [`decode_candidate`] computes both
51//! square-root candidates (the controller picks one), [`table_bases`] walks
52//! the 54 bases `2^offset(i) · (-A)` by doubling, and [`digit_table`] turns
53//! each base into its 8 or 16 affine Niels entries with one shared inversion.
54
55use super::host::Fp;
56use super::host::DIGITS;
57use super::microcode::{Builder, Program, V};
58
59/// An extended twisted-Edwards point `(X:Y:Z:T)`, `T = XY/Z`, as virtual
60/// values.
61#[derive(Clone, Copy)]
62struct Point {
63    /// `X`.
64    x: V,
65    /// `Y`.
66    y: V,
67    /// `Z`.
68    z: V,
69    /// `T`; some helpers leave a placeholder here when the caller never
70    /// reads it.
71    t: V,
72}
73
74/// Extended point plus affine Niels point from bank L (Dalek `+ AffineNielsPoint`).
75///
76/// 7 multiplies: 3 against the table entry, then 4 to turn the completed
77/// point back into extended coordinates. `l` is `(y+x, y-x, 2dxy)` of the
78/// table entry, whose `Z` is 1.
79fn madd(b: &mut Builder, p: &Point, l: [V; 3]) -> Point {
80    let ypx = b.add(p.y, p.x);
81    let ymx = b.sub(p.y, p.x);
82    // (Y+X)(y+x), (Y-X)(y-x), and T * 2dxy.
83    let pp = b.mul(ypx, l[0]);
84    let mm = b.mul(ymx, l[1]);
85    let txy = b.mul(p.t, l[2]);
86    // 2Z (the entry's Z is 1).
87    let z2 = b.add(p.z, p.z);
88    // Completed point: affine x = cx / cz, y = cy / ct.
89    let cx = b.sub(pp, mm);
90    let cy = b.add(pp, mm);
91    let cz = b.add(z2, txy);
92    let ct = b.sub(z2, txy);
93    // Completed to extended.
94    Point {
95        x: b.mul(cx, ct),
96        y: b.mul(cy, cz),
97        z: b.mul(cz, ct),
98        t: b.mul(cx, cy),
99    }
100}
101
102/// Projective doubling (a = -1); `T` of the result only when requested.
103///
104/// Dalek's `ProjectivePoint::double` followed by the completed-to-extended
105/// conversion. Doubling does not read `T`, so a chain of doublings computes
106/// `T` only on its last step; when `want_t` is false the input's stale `T`
107/// is passed through.
108fn double(b: &mut Builder, p: &Point, want_t: bool) -> Point {
109    let xx = b.sqr(p.x);
110    let yy = b.sqr(p.y);
111    let zz = b.sqr(p.z);
112    // (X + Y)^2.
113    let xpy = b.add(p.x, p.y);
114    let xpy2 = b.sqr(xpy);
115    let zz2 = b.add(zz, zz);
116    let yypxx = b.add(yy, xx);
117    let yymxx = b.sub(yy, xx);
118    // Completed point: X = (X+Y)^2 - (Y^2+X^2) = 2XY, Y = Y^2+X^2,
119    // Z = Y^2-X^2, T = 2Z^2 - (Y^2-X^2).
120    let cx = b.sub(xpy2, yypxx);
121    let ct = b.sub(zz2, yymxx);
122    Point {
123        x: b.mul(cx, ct),
124        y: b.mul(yypxx, yymxx),
125        z: b.mul(yymxx, ct),
126        t: if want_t { b.mul(cx, yypxx) } else { p.t },
127    }
128}
129
130/// Affine Niels point from bank L as an extended point with `Z = 1`
131/// (bank L may only feed a multiplier's second factor).
132///
133/// `x = ((y+x) - (y-x)) / 2`, `y = ((y+x) + (y-x)) / 2` and
134/// `t = xy = 2dxy / 2d`. Starting a chain this way costs 3 multiplies
135/// instead of a 7-multiply addition to the identity. `half` and `inv_2d`
136/// are the constants `1/2` and `1/(2d)`.
137fn niels_to_ext(b: &mut Builder, l: [V; 3], half: V, inv_2d: V) -> Point {
138    // The halving happens on the multiplier because L cannot feed the adder.
139    let ypx_2 = b.mul(half, l[0]);
140    let ymx_2 = b.mul(half, l[1]);
141    let t = b.mul(inv_2d, l[2]);
142    let one = b.constant(Fp::ONE);
143    Point {
144        x: b.sub(ypx_2, ymx_2),
145        y: b.add(ypx_2, ymx_2),
146        z: one,
147        t,
148    }
149}
150
151/// Full extended addition `P + Q` (Dalek `+ ProjectiveNielsPoint`); `T` of
152/// the sum only when requested.
153///
154/// `d2` is the constant `2d`. When `want_t` is false the result's `t` holds
155/// `Z1 * Z2`, a placeholder that saves a multiply where only `X, Y, Z` are
156/// output.
157fn add_ext(b: &mut Builder, p: &Point, q: &Point, d2: V, want_t: bool) -> Point {
158    // Q in projective Niels form: (Y+X, Y-X, Z, 2dT).
159    let q_ypx = b.add(q.y, q.x);
160    let q_ymx = b.sub(q.y, q.x);
161    let q_t2d = b.mul(q.t, d2);
162    let ypx = b.add(p.y, p.x);
163    let ymx = b.sub(p.y, p.x);
164    let pp = b.mul(ypx, q_ypx);
165    let mm = b.mul(ymx, q_ymx);
166    let tt = b.mul(p.t, q_t2d);
167    let zz = b.mul(p.z, q.z);
168    let zz2 = b.add(zz, zz);
169    // Completed point, then completed to extended as in `madd`.
170    let cx = b.sub(pp, mm);
171    let cy = b.add(pp, mm);
172    let cz = b.add(zz2, tt);
173    let ct = b.sub(zz2, tt);
174    Point {
175        x: b.mul(cx, ct),
176        y: b.mul(cy, cz),
177        z: b.mul(cz, ct),
178        t: if want_t { b.mul(cx, cy) } else { zz },
179    }
180}
181
182/// Lookup order of the fixed-base walk (see the module docs).
183///
184/// Radix-16 digits (39..54, 4 scans each) lead, so the multiplier starts
185/// sooner, and are then spread among the radix-32 digits (8 scans each) so
186/// scans and multiplies stay balanced. Found by searching orders with the
187/// real scheduler (`tools/hostmodel`): 392 bundles, against 435 in digit
188/// order. In [`fixed_base`], positions alternate between its two chains;
189/// [`verify_known_key`] runs the whole order as one chain.
190pub const LOOKUP_ORDER: [u8; DIGITS] = [
191    39, 40, 41, 42, 43, 44, 0, 1, 2, 3, 45, 4, 5, 6, 46, 7, 8, 9, 10, 47, 11, 12, 13, 48, 14, 15,
192    16, 17, 49, 18, 19, 20, 50, 21, 22, 23, 24, 51, 25, 26, 27, 52, 28, 29, 30, 31, 53, 32, 33, 34,
193    35, 36, 37, 38,
194];
195
196/// The fixed-base walk `[s]B` over the digits in `order`, as an extended
197/// point. `order` must have even length; even positions feed one chain and
198/// odd positions the other.
199fn fixed_base_point(b: &mut Builder, order: &[u8]) -> Point {
200    let d2 = b.constant(super::host::edwards_d2());
201    // 1/2 and 1/(2d), for `niels_to_ext`.
202    let half = b.constant(Fp::ONE.add(Fp::ONE).invert());
203    let inv_2d = b.constant(super::host::edwards_d2().invert());
204    // [s]B = sum_i d_i (2^offset(i) B). Lookups alternate between two
205    // independent chains (so the scheduler can fill both slots), each started
206    // from its first lookup instead of adding to the identity. Radix-16
207    // lookups (4 scans) are spread among the radix-32 ones (8 scans) so scans
208    // and multiplies stay balanced along the whole program.
209    //
210    // `odd` takes positions 0, 2, 4, ... of `order` (odd when counted from
211    // 1) and `even` positions 1, 3, 5, ...
212    let l = b.lookup(order[0]);
213    let mut odd = niels_to_ext(b, l, half, inv_2d);
214    let l = b.lookup(order[1]);
215    let mut even = niels_to_ext(b, l, half, inv_2d);
216    for pair in order[2..].chunks(2) {
217        let l = b.lookup(pair[0]);
218        odd = madd(b, &odd, l);
219        let l = b.lookup(pair[1]);
220        even = madd(b, &even, l);
221    }
222    // Join the chains; only X, Y, Z are needed.
223    add_ext(b, &odd, &even, d2, false)
224}
225
226/// [`fixed_base`] with an explicit lookup order (for schedule exploration).
227#[doc(hidden)]
228pub fn fixed_base_ordered(order: &[u8]) -> Program {
229    let mut b = Builder::new();
230    let p = fixed_base_point(&mut b, order);
231    b.out(p.x, 0);
232    b.out(p.y, 1);
233    b.out(p.z, 2);
234    b.compile("fixed_base")
235}
236
237/// Known-key verification core: `[S]B + [k](-A)` as two fixed-base walks.
238///
239/// Digit slots `0..54` hold `S`, slots `64..118` hold `k = H(R ‖ A ‖ M) mod l`,
240/// and the key table holds [`point_table`](super::host::point_table)`(-A)`.
241/// Outputs projective `X, Y, Z` (indices 0..3); the controller encodes the
242/// point and compares it with `R`. `key_order` is the lookup order of the key
243/// chain (the base chain uses [`LOOKUP_ORDER`]).
244///
245/// The two scalars give two independent chains, so this program does not
246/// split each walk into odd and even chains as [`fixed_base`] does.
247pub fn verify_known_key_ordered(key_order: &[u8]) -> Program {
248    let mut b = Builder::new();
249    let d2 = b.constant(super::host::edwards_d2());
250    let half = b.constant(Fp::ONE.add(Fp::ONE).invert());
251    let inv_2d = b.constant(super::host::edwards_d2().invert());
252    // Each chain starts from its first lookup.
253    let l = b.lookup(LOOKUP_ORDER[0]);
254    let mut base = niels_to_ext(&mut b, l, half, inv_2d);
255    let l = b.lookup_key(key_order[0]);
256    let mut key = niels_to_ext(&mut b, l, half, inv_2d);
257    // Base and key lookups alternate, keeping both chains in flight.
258    for k in 1..DIGITS {
259        let l = b.lookup(LOOKUP_ORDER[k]);
260        base = madd(&mut b, &base, l);
261        let l = b.lookup_key(key_order[k]);
262        key = madd(&mut b, &key, l);
263    }
264    let p = add_ext(&mut b, &base, &key, d2, false);
265    b.out(p.x, 0);
266    b.out(p.y, 1);
267    b.out(p.z, 2);
268    b.compile("verify_known_key")
269}
270
271/// [`verify_known_key_ordered`] with the key chain in [`LOOKUP_ORDER`].
272pub fn verify_known_key() -> Program {
273    verify_known_key_ordered(&LOOKUP_ORDER)
274}
275
276/// Key loading, step 1: square-root candidate for a point with `y` in
277/// input 0. Outputs `x` (0), `x * sqrt(-1)` (1), `v * x^2` (2) and `u` (3)
278/// with `u = y^2 - 1`, `v = d y^2 + 1`. The controller takes `x` if
279/// `v x^2 = u`, `x sqrt(-1)` if `v x^2 = -u`, and rejects the point
280/// otherwise, then applies the sign bit; the program never branches.
281///
282/// The candidate is the RFC 8032 one, `x = u v^3 (u v^7)^((p-5)/8)`, which
283/// computes a root of `u / v` without a separate inversion.
284pub fn decode_candidate() -> Program {
285    let mut b = Builder::new();
286    let y = V::Input(0);
287    let one = b.constant(Fp::ONE);
288    let d = b.constant(super::host::edwards_d());
289    let sqrt_m1 = b.constant(super::host::sqrt_m1());
290    let y2 = b.sqr(y);
291    let u = b.sub(y2, one);
292    let dy2 = b.mul(y2, d);
293    let v = b.add(dy2, one);
294    // v^3 and v^7.
295    let v2 = b.sqr(v);
296    let v3 = b.mul(v2, v);
297    let v6 = b.sqr(v3);
298    let v7 = b.mul(v6, v);
299    let uv3 = b.mul(u, v3);
300    let uv7 = b.mul(u, v7);
301    let t = b.pow_p58(uv7);
302    let x = b.mul(uv3, t);
303    // The other candidate, for when x^2 = -u/v.
304    let xi = b.mul(x, sqrt_m1);
305    // v x^2, which the controller compares with u and -u.
306    let x2 = b.sqr(x);
307    let vx2 = b.mul(v, x2);
308    b.out(x, 0);
309    b.out(xi, 1);
310    b.out(vx2, 2);
311    b.out(u, 3);
312    b.compile("decode_candidate")
313}
314
315/// Key loading, step 2: the 54 table bases `2^offset(i) * P` of an affine
316/// point `P = (x, y)` (inputs 0, 1), as projective `X, Y, Z, T`, in digit
317/// order (output `4i + c`, counted by the controller).
318///
319/// Every output uses indices 0..4; the controller tells bases apart by
320/// counting outputs, which arrive in program order.
321pub fn table_bases() -> Program {
322    let mut b = Builder::new();
323    let one = b.constant(Fp::ONE);
324    let x = V::Input(0);
325    let y = V::Input(1);
326    // P in extended coordinates with Z = 1, T = xy.
327    let mut p = Point { x, y, z: one, t: b.mul(x, y) };
328    for i in 0..DIGITS {
329        b.out(p.x, 0);
330        b.out(p.y, 1);
331        b.out(p.z, 2);
332        b.out(p.t, 3);
333        if i + 1 < DIGITS {
334            // Next base: digit i's width in doublings, computing T only on
335            // the last one because the output needs it.
336            let w = super::host::digit_width(i);
337            for step in 0..w {
338                p = double(&mut b, &p, step + 1 == w);
339            }
340        }
341    }
342    b.compile("table_bases")
343}
344
345/// Key loading, step 3: the `entries` table entries `e * Q` (`e = 1..`) of
346/// a projective base `Q` (inputs 0..4 = `X, Y, Z, T`) in affine Niels form,
347/// normalized with one shared inversion. Output `3(e-1) + c` is coordinate
348/// `c` (`y+x`, `y-x`, `2dxy`) of entry `e`.
349///
350/// `entries` is 16 for a radix-32 digit and 8 for a radix-16 one. The
351/// multiples are `Q`, `2Q` (one doubling), then repeated additions of `Q`;
352/// their `Z`s are inverted together with Montgomery's trick as in
353/// [`batch_compress`].
354pub fn digit_table(entries: usize) -> Program {
355    // The program always builds Q and 2Q before the rest.
356    assert!(entries >= 2, "a digit table has at least two entries");
357    let mut b = Builder::new();
358    let d2 = b.constant(super::host::edwards_d2());
359    let q = Point { x: V::Input(0), y: V::Input(1), z: V::Input(2), t: V::Input(3) };
360    // m[e - 1] = e * Q, with T (the next addition reads it).
361    let mut m = vec![q, double(&mut b, &q, true)];
362    while m.len() < entries {
363        let next = add_ext(&mut b, m.last().unwrap(), &q, d2, true);
364        m.push(next);
365    }
366    // prefix[e] = Z_0 * ... * Z_e.
367    let mut prefix = vec![m[0].z];
368    for e in 1..entries {
369        let p = b.mul(prefix[e - 1], m[e].z);
370        prefix.push(p);
371    }
372    // inv = 1 / (Z_0 * ... * Z_e) for the current e, walking down.
373    let mut inv = b.invert(prefix[entries - 1]);
374    // Placeholders, all overwritten below.
375    let mut zinv = vec![inv; entries];
376    for e in (0..entries).rev() {
377        if e == 0 {
378            // inv is now 1 / Z_0.
379            zinv[0] = inv;
380        } else {
381            // 1/Z_e = (Z_0 ... Z_{e-1}) / (Z_0 ... Z_e); then drop Z_e from
382            // inv.
383            zinv[e] = b.mul(inv, prefix[e - 1]);
384            inv = b.mul(inv, m[e].z);
385        }
386    }
387    for e in 0..entries {
388        // Affine x, y, then the Niels coordinates.
389        let x = b.mul(m[e].x, zinv[e]);
390        let y = b.mul(m[e].y, zinv[e]);
391        let ypx = b.add(y, x);
392        let ymx = b.sub(y, x);
393        let xy = b.mul(x, y);
394        let xy2d = b.mul(xy, d2);
395        b.out(ypx, (3 * e) as u8);
396        b.out(ymx, (3 * e + 1) as u8);
397        b.out(xy2d, (3 * e + 2) as u8);
398    }
399    b.compile("digit_table")
400}
401
402/// Fixed-base multiplication followed by its own inversion: outputs affine
403/// `x` (index 0) and `y` (index 1). Used when no batch-compress machine exists.
404pub fn fixed_base_affine() -> Program {
405    let mut b = Builder::new();
406    let p = fixed_base_point(&mut b, &LOOKUP_ORDER);
407    let zi = b.invert(p.z);
408    let x = b.mul(p.x, zi);
409    let y = b.mul(p.y, zi);
410    b.out(x, 0);
411    b.out(y, 1);
412    b.compile("fixed_base_affine")
413}
414
415/// Fixed-base scalar multiplication. Outputs projective `X, Y, Z` (indices 0..3).
416///
417/// `[s]B = sum_i d_i T_i` with `T_i = 2^offset(i) B`, split into two sums by
418/// position in [`LOOKUP_ORDER`]: digits at even positions form one chain and
419/// digits at odd positions the other. The two chains are independent and
420/// interleaved, so the scheduler can fill the bundle slots that one chain's
421/// add/multiply recurrence would leave empty; one final addition joins them.
422pub fn fixed_base() -> Program {
423    let mut b = Builder::new();
424    let p = fixed_base_point(&mut b, &LOOKUP_ORDER);
425    b.out(p.x, 0);
426    b.out(p.y, 1);
427    b.out(p.z, 2);
428    b.compile("fixed_base")
429}
430
431/// Batch affine conversion of `g` points held in bank I as `X, Y, Z` triples.
432///
433/// Output `2p` is affine `x` and `2p + 1` is affine `y` of point `p`. All
434/// values are canonical, so `x`'s least-significant bit is its Ed25519 sign.
435/// A zero `Z` would zero the whole batch; valid curve points never have one.
436///
437/// Montgomery's trick: `g - 1` multiplies build the prefix products, one
438/// inversion inverts the last, and a backward walk recovers each `1/Z` with
439/// two multiplies.
440pub fn batch_compress(g: u8) -> Program {
441    assert!(g >= 1);
442    let mut b = Builder::new();
443    // Point p's coordinates in bank I.
444    let x = |p: u8| V::Input(3 * p);
445    let y = |p: u8| V::Input(3 * p + 1);
446    let z = |p: u8| V::Input(3 * p + 2);
447    // prefix[p] = Z_0 * ... * Z_p.
448    let mut prefix = vec![z(0)];
449    for p in 1..g {
450        let next = b.mul(prefix[p as usize - 1], z(p));
451        prefix.push(next);
452    }
453    // inv = 1 / (Z_0 * ... * Z_p) for the current p, walking down.
454    let mut inv = b.invert(prefix[g as usize - 1]);
455    for p in (0..g).rev() {
456        let zi = if p == 0 {
457            inv
458        } else {
459            // 1/Z_p, then drop Z_p from inv.
460            let zi = b.mul(inv, prefix[p as usize - 1]);
461            inv = b.mul(inv, z(p));
462            zi
463        };
464        let ax = b.mul(x(p), zi);
465        let ay = b.mul(y(p), zi);
466        b.out(ax, 2 * p);
467        b.out(ay, 2 * p + 1);
468    }
469    // Adds zero as C[0], the `Out` dummy operand. The program uses no other
470    // constant, and `compile` would add the same zero to an empty bank, so
471    // this only makes the entry explicit.
472    let _ = b.constant(Fp::ZERO);
473    b.compile("batch_compress")
474}
475
476/// Interpreter tests of every program against Dalek and the host model.
477#[cfg(test)]
478mod tests {
479    use super::super::host::{Ext, Fp, L_BYTES, recode};
480    use super::super::microcode::{Context, interpret};
481    use super::*;
482
483    /// Reduce a 64-byte little-endian value modulo `l`.
484    fn reduce_l(bytes: [u8; 64]) -> [u8; 32] {
485        use num_bigint::BigUint;
486        let l = BigUint::from_bytes_le(&L_BYTES);
487        let v = BigUint::from_bytes_le(&bytes) % l;
488        let mut out = [0u8; 32];
489        let b = v.to_bytes_le();
490        out[..b.len()].copy_from_slice(&b);
491        out
492    }
493
494    /// `n` deterministic pseudo-random scalars below `l` (xorshift64 bytes,
495    /// reduced from 64 bytes like a hash).
496    fn scalars(n: usize) -> Vec<[u8; 32]> {
497        let mut state = 0x1234_5678_9abc_def0u64;
498        (0..n)
499            .map(|_| {
500                let mut wide = [0u8; 64];
501                for byte in &mut wide {
502                    state ^= state << 13;
503                    state ^= state >> 7;
504                    state ^= state << 17;
505                    *byte = state as u8;
506                }
507                reduce_l(wide)
508            })
509            .collect()
510    }
511
512    /// `[S]B + [k](-A)` equals `R` exactly when `R = [S]B - [k]A`; checked
513    /// for random keys and scalars, plus one corrupted case.
514    #[test]
515    fn verify_known_key_computes_the_equation() {
516        use super::super::host::point_table;
517        let p = verify_known_key();
518        println!(
519            "verify_known_key: {} bundles, {} mul, {} add, {} scan, regs M{} A{}",
520            p.bundles.len(), p.mul_ops, p.add_ops, p.scan_ops, p.regs_m, p.regs_a
521        );
522        let all = scalars(12);
523        for t in 0..4 {
524            // Secret key scalar a, signature scalar s, challenge k.
525            let (a, s, k) = (all[3 * t], all[3 * t + 1], all[3 * t + 2]);
526            let public = Ext::basepoint().mul_bytes(&a);
527            // The R that makes (R, s) verify for challenge k.
528            let r = Ext::basepoint().mul_bytes(&s).add(public.mul_bytes(&k).neg());
529            let table = point_table(public.neg());
530            let mut ctx = Context::with_key(&p, &[], recode(&s), recode(&k), table.clone());
531            let out = interpret(&p, &mut ctx);
532            // `compress` does not read T.
533            let point = Ext { x: out[0].1, y: out[1].1, z: out[2].1, t: Fp::ZERO };
534            assert_eq!(point.compress(), r.compress(), "case {t}");
535            // A different k must not verify.
536            let mut ctx = Context::with_key(&p, &[], recode(&s), recode(&a), table);
537            let out = interpret(&p, &mut ctx);
538            let point = Ext { x: out[0].1, y: out[1].1, z: out[2].1, t: Fp::ZERO };
539            assert_ne!(point.compress(), r.compress(), "case {t} with the wrong k");
540        }
541    }
542
543    /// Key loading end to end on the interpreter (with the controller's
544    /// root choice done here): the built table equals `point_table(-A)`.
545    #[test]
546    fn key_load_programs_build_the_key_table() {
547        use super::super::host::{decode_point, digit_entries, point_table};
548        let dec = decode_candidate();
549        let bases = table_bases();
550        let wide = digit_table(16);
551        let narrow = digit_table(8);
552        for (name, p) in [("decode_candidate", &dec), ("table_bases", &bases), ("digit_table(16)", &wide), ("digit_table(8)", &narrow)] {
553            println!("{name}: {} bundles, {} mul, regs M{} A{} I{}", p.bundles.len(), p.mul_ops, p.regs_m, p.regs_a, p.regs_i);
554        }
555        for a in scalars(2) {
556            let public = Ext::basepoint().mul_bytes(&a);
557            let enc = public.compress();
558            // Split the encoding into the sign of x and y.
559            let sign = enc[31] >> 7 == 1;
560            let mut yb = enc;
561            yb[31] &= 0x7f;
562            let y = Fp::from_bytes(&yb);
563            // Step 1 + controller root choice.
564            let mut ctx = Context::new(&dec, &[y], [0; 64]);
565            let out = interpret(&dec, &mut ctx);
566            let (x, xi, vx2, u) = (out[0].1, out[1].1, out[2].1, out[3].1);
567            let mut root = if vx2 == u { x } else if vx2 == u.neg() { xi } else { panic!("not on the curve") };
568            if root.is_negative() != sign {
569                root = root.neg();
570            }
571            assert_eq!(Some(root), decode_point(&enc).map(|p| p.x));
572            // -A = (-x, y).
573            let neg_x = root.neg();
574            let mut ctx = Context::new(&bases, &[neg_x, y], [0; 64]);
575            let out = interpret(&bases, &mut ctx);
576            let want = point_table(public.neg());
577            for i in 0..DIGITS {
578                // Base i is outputs 4i..4i+4 (X, Y, Z, T).
579                let q: Vec<Fp> = (0..4).map(|c| out[4 * i + c].1).collect();
580                let prog = if digit_entries(i) == 16 { &wide } else { &narrow };
581                let mut ctx = Context::new(prog, &q, [0; 64]);
582                let entries = interpret(prog, &mut ctx);
583                for e in 0..digit_entries(i) {
584                    let n = super::super::host::Niels { ypx: entries[3 * e].1, ymx: entries[3 * e + 1].1, xy2d: entries[3 * e + 2].1 };
585                    assert_eq!(n, want[i][e], "digit {i} entry {e}");
586                }
587            }
588        }
589    }
590
591    /// The fused program's affine output encodes to Dalek's `[s]B`.
592    #[test]
593    fn fused_fixed_base_matches_dalek() {
594        use curve25519_dalek::{EdwardsPoint, Scalar};
595        let p = fixed_base_affine();
596        println!(
597            "fixed_base_affine: {} bundles, {} mul, regs M{} A{}",
598            p.bundles.len(),
599            p.mul_ops,
600            p.regs_m,
601            p.regs_a
602        );
603        for s in scalars(6) {
604            let mut ctx = Context::new(&p, &[], recode(&s));
605            let out = interpret(&p, &mut ctx);
606            let (x, y) = (out[0].1, out[1].1);
607            // Ed25519 encoding: y with x's sign in bit 255.
608            let mut enc = y.to_bytes();
609            enc[31] |= u8::from(x.is_negative()) << 7;
610            let expect = EdwardsPoint::mul_base(&Scalar::from_bytes_mod_order(s)).compress();
611            assert_eq!(enc, expect.to_bytes());
612        }
613    }
614
615    /// `fixed_base` followed by `batch_compress(4)` encodes to Dalek's
616    /// `[s]B`, including the scalars 0, 1 and `l - 1`.
617    #[test]
618    fn fixed_base_and_batch_compress_match_dalek() {
619        use curve25519_dalek::{EdwardsPoint, Scalar};
620        let fb = fixed_base();
621        let g = 4;
622        let cp = batch_compress(g);
623        println!(
624            "fixed_base: {} bundles, {} mul, {} add, {} scan, regs M{} A{}",
625            fb.bundles.len(),
626            fb.mul_ops,
627            fb.add_ops,
628            fb.scan_ops,
629            fb.regs_m,
630            fb.regs_a
631        );
632        println!(
633            "batch_compress({g}): {} bundles, {} mul, regs M{} I{}",
634            cp.bundles.len(),
635            cp.mul_ops,
636            cp.regs_m,
637            cp.regs_i
638        );
639        // Edge-case scalars: 0, 1 and l - 1.
640        let mut special = vec![[0u8; 32], {
641            let mut one = [0u8; 32];
642            one[0] = 1;
643            one
644        }];
645        let mut lm1 = L_BYTES;
646        lm1[0] -= 1;
647        special.push(lm1);
648        // 11 scalars: two full batches of 4 and a short batch of 3, so the
649        // identity padding below runs too.
650        let all: Vec<[u8; 32]> = special.into_iter().chain(scalars(8)).collect();
651        for chunk in all.chunks(g as usize) {
652            let mut inputs = Vec::new();
653            for s in chunk {
654                let mut ctx = Context::new(&fb, &[], recode(s));
655                let out = interpret(&fb, &mut ctx);
656                assert_eq!(out.len(), 3);
657                inputs.extend(out.iter().map(|(_, v)| *v));
658            }
659            // Pad a short batch with the identity.
660            while inputs.len() < 3 * g as usize {
661                inputs.extend([Fp::ZERO, Fp::ONE, Fp::ONE]);
662            }
663            let mut ctx = Context::new(&cp, &inputs, [0; 64]);
664            let out = interpret(&cp, &mut ctx);
665            for (p, s) in chunk.iter().enumerate() {
666                let x = out.iter().find(|(i, _)| *i as usize == 2 * p).unwrap().1;
667                let y = out.iter().find(|(i, _)| *i as usize == 2 * p + 1).unwrap().1;
668                let mut enc = y.to_bytes();
669                enc[31] |= u8::from(x.is_negative()) << 7;
670                let expect = EdwardsPoint::mul_base(&Scalar::from_bytes_mod_order(*s)).compress();
671                assert_eq!(enc, expect.to_bytes());
672                assert_eq!(enc, Ext::basepoint().mul_bytes(s).compress());
673            }
674        }
675    }
676}