Skip to main content

rhdl_ed25519_fast_core/
equation.rs

1//! Projective Ed25519 verification equation.
2//!
3//! This stage adds affine `R` to projective `[k]A`, then compares the result
4//! with projective `[S]B` using cross-products.  No inversion or compression is
5//! required.  Twenty contexts share four field multipliers and each request
6//! occupies exactly three multiplier issue cycles.
7
8use rhdl::prelude::*;
9use rhdl_fpga::core::dff::DFF;
10
11use crate::{
12    field_parallel::{
13        FieldElement51, FieldMulIn, FieldMulPipe, field_add_kernel, field_eq_kernel,
14        field_sub_kernel,
15    },
16    multicomb::ExtendedPoint,
17};
18
19const CONTEXTS: usize = 20;
20const FREE: u128 = 0;
21const READY: u128 = 1;
22const WAIT: u128 = 2;
23
24#[derive(Copy, Clone, Debug, Default, PartialEq, Digital)]
25#[doc(hidden)]
26pub enum EquationPhase {
27    #[default]
28    AddFirst,
29    AddSecond,
30    Compare,
31}
32
33/// One strict projective-equation request.
34#[derive(Copy, Clone, Debug, PartialEq, Digital)]
35pub struct EquationIn {
36    /// Marks this request valid when [`EquationOut::ready`] is high.
37    pub valid: bool,
38    /// Variable-base product `[k]A`.
39    pub variable: ExtendedPoint,
40    /// Decoded affine signature point `R`.
41    pub r: ExtendedPoint,
42    /// Precomputed affine `2*d*T_R`.
43    pub r_t2d: FieldElement51,
44    /// Fixed-base product `[S]B`.
45    pub fixed: ExtendedPoint,
46    /// Caller-defined transaction tag.
47    pub tag: b16,
48}
49
50/// Flow control and equality result from [`EquationPipe`].
51#[derive(Copy, Clone, Debug, PartialEq, Digital)]
52pub struct EquationOut {
53    /// High when an equation context can be accepted.
54    pub ready: bool,
55    /// High for one cycle when [`Self::equal`] is valid.
56    pub valid: bool,
57    /// Projective equality decision.
58    pub equal: bool,
59    /// Delayed caller tag.
60    pub tag: b16,
61    /// Number of occupied contexts.
62    pub occupancy: b5,
63}
64
65#[derive(Copy, Clone, Debug, PartialEq, Digital)]
66#[doc(hidden)]
67pub struct EquationContext {
68    state: b2,
69    phase: EquationPhase,
70    tag: b16,
71    variable: ExtendedPoint,
72    r: ExtendedPoint,
73    r_t2d: FieldElement51,
74    fixed: ExtendedPoint,
75    e: FieldElement51,
76    f: FieldElement51,
77    g: FieldElement51,
78    h: FieldElement51,
79    sum: ExtendedPoint,
80}
81
82fn empty_context() -> EquationContext {
83    EquationContext {
84        state: b2(FREE),
85        phase: EquationPhase::AddFirst,
86        tag: b16(0),
87        variable: ExtendedPoint::identity(),
88        r: ExtendedPoint::identity(),
89        r_t2d: FieldElement51::zero(),
90        fixed: ExtendedPoint::identity(),
91        e: FieldElement51::zero(),
92        f: FieldElement51::zero(),
93        g: FieldElement51::zero(),
94        h: FieldElement51::zero(),
95        sum: ExtendedPoint::identity(),
96    }
97}
98
99#[derive(Copy, Clone, Debug, PartialEq, Digital)]
100#[doc(hidden)]
101pub struct EquationControl {
102    contexts: [EquationContext; CONTEXTS],
103    issue_valid: [bool; 17],
104    issue_context: [b5; 17],
105    round_robin: b5,
106}
107
108fn reset_control() -> EquationControl {
109    EquationControl {
110        contexts: [empty_context(); CONTEXTS],
111        issue_valid: [false; 17],
112        issue_context: [b5(0); 17],
113        round_robin: b5(0),
114    }
115}
116
117/// Twenty-context strict projective equation pipeline.
118#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
119pub struct EquationPipe {
120    control: DFF<EquationControl>,
121    mul0: FieldMulPipe,
122    mul1: FieldMulPipe,
123    mul2: FieldMulPipe,
124    mul3: FieldMulPipe,
125}
126
127impl Default for EquationPipe {
128    fn default() -> Self {
129        Self {
130            control: DFF::new(reset_control()),
131            mul0: FieldMulPipe::default(),
132            mul1: FieldMulPipe::default(),
133            mul2: FieldMulPipe::default(),
134            mul3: FieldMulPipe::default(),
135        }
136    }
137}
138
139impl SynchronousIO for EquationPipe {
140    type I = EquationIn;
141    type O = EquationOut;
142    type Kernel = equation_kernel;
143}
144
145#[kernel]
146fn zero() -> FieldElement51 {
147    FieldElement51 { limbs: [b51(0); 5] }
148}
149
150#[kernel]
151fn one() -> FieldElement51 {
152    FieldElement51 {
153        limbs: [b51(1), b51(0), b51(0), b51(0), b51(0)],
154    }
155}
156
157#[kernel]
158fn identity() -> ExtendedPoint {
159    ExtendedPoint {
160        x: zero(),
161        y: one(),
162        z: one(),
163        t: zero(),
164    }
165}
166
167#[kernel]
168fn empty_context_kernel(_unused: bool) -> EquationContext {
169    EquationContext {
170        state: b2(FREE),
171        phase: EquationPhase::AddFirst,
172        tag: b16(0),
173        variable: identity(),
174        r: identity(),
175        r_t2d: zero(),
176        fixed: identity(),
177        e: zero(),
178        f: zero(),
179        g: zero(),
180        h: zero(),
181        sum: identity(),
182    }
183}
184
185#[kernel]
186fn wrap20(value: b5) -> b5 {
187    if value >= b5(20) {
188        value - b5(20)
189    } else {
190        value
191    }
192}
193
194/// State transition for [`EquationPipe`].
195#[kernel]
196pub fn equation_kernel(
197    _cr: ClockReset,
198    i: EquationIn,
199    q: EquationPipeQ,
200) -> (EquationOut, EquationPipeD) {
201    let r = q.control;
202    let mut n = r;
203    let zero = zero();
204    let mut output = EquationOut {
205        ready: false,
206        valid: false,
207        equal: false,
208        tag: b16(0),
209        occupancy: b5(0),
210    };
211
212    let mut free_found = false;
213    let mut free_context = b5(0);
214    for index in 0..CONTEXTS {
215        if r.contexts[index].state != b2(FREE) {
216            output.occupancy += b5(1);
217        } else if !free_found {
218            free_found = true;
219            free_context = b5(index as u128);
220        }
221    }
222    output.ready = free_found;
223
224    let mut issue_found = false;
225    let mut issue_context = b5(0);
226    for offset in 0..CONTEXTS {
227        let candidate = wrap20(r.round_robin + b5(offset as u128));
228        if !issue_found && r.contexts[candidate].state == b2(READY) {
229            issue_found = true;
230            issue_context = candidate;
231        }
232    }
233    let context = r.contexts[issue_context];
234    let mut a = [zero; 4];
235    let mut b = [zero; 4];
236    if issue_found {
237        match context.phase {
238            EquationPhase::AddFirst => {
239                a[0] = field_sub_kernel(context.variable.y, context.variable.x);
240                b[0] = field_sub_kernel(context.r.y, context.r.x);
241                a[1] = field_add_kernel(context.variable.y, context.variable.x);
242                b[1] = field_add_kernel(context.r.y, context.r.x);
243                a[2] = context.variable.t;
244                b[2] = context.r_t2d;
245            }
246            EquationPhase::AddSecond => {
247                a = [context.e, context.g, context.e, context.f];
248                b = [context.f, context.h, context.h, context.g];
249            }
250            EquationPhase::Compare => {
251                a = [
252                    context.sum.x,
253                    context.fixed.x,
254                    context.sum.y,
255                    context.fixed.y,
256                ];
257                b = [
258                    context.fixed.z,
259                    context.sum.z,
260                    context.fixed.z,
261                    context.sum.z,
262                ];
263            }
264        }
265        n.contexts[issue_context].state = b2(WAIT);
266        n.round_robin = wrap20(issue_context + b5(1));
267    }
268
269    n.issue_valid[0] = issue_found;
270    n.issue_context[0] = issue_context;
271    for stage in 1..17 {
272        n.issue_valid[stage] = r.issue_valid[stage - 1];
273        n.issue_context[stage] = r.issue_context[stage - 1];
274    }
275
276    if r.issue_valid[16] && q.mul0.valid && q.mul1.valid && q.mul2.valid && q.mul3.valid {
277        let result_context = r.issue_context[16];
278        let current = r.contexts[result_context];
279        let mut updated = current;
280        updated.state = b2(READY);
281        match current.phase {
282            EquationPhase::AddFirst => {
283                let d = field_add_kernel(current.variable.z, current.variable.z);
284                updated.e = field_sub_kernel(q.mul1.result, q.mul0.result);
285                updated.f = field_sub_kernel(d, q.mul2.result);
286                updated.g = field_add_kernel(d, q.mul2.result);
287                updated.h = field_add_kernel(q.mul1.result, q.mul0.result);
288                updated.phase = EquationPhase::AddSecond;
289            }
290            EquationPhase::AddSecond => {
291                updated.sum = ExtendedPoint {
292                    x: q.mul0.result,
293                    y: q.mul1.result,
294                    z: q.mul3.result,
295                    t: q.mul2.result,
296                };
297                updated.phase = EquationPhase::Compare;
298            }
299            EquationPhase::Compare => {
300                output.valid = true;
301                output.equal = field_eq_kernel(q.mul0.result, q.mul1.result)
302                    && field_eq_kernel(q.mul2.result, q.mul3.result);
303                output.tag = current.tag;
304                updated = empty_context_kernel(false);
305            }
306        }
307        n.contexts[result_context] = updated;
308    }
309
310    if i.valid && free_found {
311        n.contexts[free_context] = EquationContext {
312            state: b2(READY),
313            phase: EquationPhase::AddFirst,
314            tag: i.tag,
315            variable: i.variable,
316            r: i.r,
317            r_t2d: i.r_t2d,
318            fixed: i.fixed,
319            e: zero,
320            f: zero,
321            g: zero,
322            h: zero,
323            sum: identity(),
324        };
325    }
326
327    let mut d = EquationPipeD::dont_care();
328    d.control = n;
329    d.mul0 = FieldMulIn {
330        valid: issue_found,
331        a: a[0],
332        b: b[0],
333    };
334    d.mul1 = FieldMulIn {
335        valid: issue_found,
336        a: a[1],
337        b: b[1],
338    };
339    d.mul2 = FieldMulIn {
340        valid: issue_found,
341        a: a[2],
342        b: b[2],
343    };
344    d.mul3 = FieldMulIn {
345        valid: issue_found,
346        a: a[3],
347        b: b[3],
348    };
349    (output, d)
350}
351
352#[cfg(test)]
353mod tests {
354    use super::*;
355    use crate::field_parallel::field_from_words_kernel;
356    use std::iter;
357
358    fn field(words: [u64; 4]) -> FieldElement51 {
359        field_from_words_kernel(words.map(|word| b64(word as u128)))
360    }
361
362    fn basepoint() -> (ExtendedPoint, FieldElement51) {
363        (
364            ExtendedPoint {
365                x: field([
366                    0xc9562d608f25d51a,
367                    0x692cc7609525a7b2,
368                    0xc0a4e231fdd6dc5c,
369                    0x216936d3cd6e53fe,
370                ]),
371                y: field([
372                    0x6666666666666658,
373                    0x6666666666666666,
374                    0x6666666666666666,
375                    0x6666666666666666,
376                ]),
377                z: FieldElement51::one(),
378                t: field([
379                    0x6dde8ab3a5b7dda3,
380                    0x20f09f80775152f5,
381                    0x66ea4e8e64abe37d,
382                    0x67875f0fd78b7665,
383                ]),
384            },
385            field([
386                0xabc91205877aaa68,
387                0x26d9e823ccaac49e,
388                0x5a1b7dcbdd43598c,
389                0x6f117b689f0c65a8,
390            ]),
391        )
392    }
393
394    #[test]
395    fn identity_plus_basepoint_compares_equal_to_basepoint() {
396        let (base, t2d) = basepoint();
397        let request = EquationIn {
398            valid: true,
399            variable: ExtendedPoint::identity(),
400            r: base,
401            r_t2d: t2d,
402            fixed: base,
403            tag: b16(7),
404        };
405        let idle = EquationIn {
406            valid: false,
407            ..request
408        };
409        let outputs: Vec<_> = EquationPipe::default()
410            .run(
411                iter::once(idle)
412                    .chain(iter::once(request))
413                    .chain(iter::repeat_n(idle, 100))
414                    .with_reset(1)
415                    .clock_pos_edge(100),
416            )
417            .synchronous_sample()
418            .map(|sample| sample.output)
419            .filter(|output| output.valid)
420            .collect();
421        assert_eq!(outputs.len(), 1);
422        assert!(outputs[0].equal);
423        assert_eq!(outputs[0].tag, b16(7));
424    }
425
426    #[test]
427    fn basepoint_plus_basepoint_compares_equal_to_double() {
428        let (base, t2d) = basepoint();
429        let double = ExtendedPoint {
430            x: field([
431                0x83c5a14e2843ce0e,
432                0x080d8e4515d7a45f,
433                0x3d043b7d1833e7ac,
434                0x36ab384c9f5a046c,
435            ]),
436            y: field([
437                0x0e5f46ae6af8a3c9,
438                0x97390f5164385156,
439                0x1da25ee8c9a21f56,
440                0x2260cdf3092329c2,
441            ]),
442            z: FieldElement51::one(),
443            t: field([
444                0xb71a3f556d69b401,
445                0x5d79acc9c1f72402,
446                0x1de55f087303b413,
447                0x2498a7850b2f68dc,
448            ]),
449        };
450        let request = EquationIn {
451            valid: true,
452            variable: base,
453            r: base,
454            r_t2d: t2d,
455            fixed: double,
456            tag: b16(8),
457        };
458        let idle = EquationIn {
459            valid: false,
460            ..request
461        };
462        let output = EquationPipe::default()
463            .run(
464                iter::once(idle)
465                    .chain(iter::once(request))
466                    .chain(iter::repeat_n(idle, 100))
467                    .with_reset(1)
468                    .clock_pos_edge(100),
469            )
470            .synchronous_sample()
471            .map(|sample| sample.output)
472            .find(|output| output.valid)
473            .unwrap();
474        assert!(output.equal);
475        assert_eq!(output.tag, b16(8));
476    }
477}