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}