1use rhdl::prelude::*;
16use rhdl_fpga::core::dff::DFF;
17
18use crate::{
19 field_parallel::{
20 FieldElement51, FieldMulIn, FieldMulPipe, field_add_kernel, field_eq_kernel,
21 field_from_words_kernel, field_neg_kernel, field_sub_kernel, field_to_words_kernel,
22 },
23 multicomb::ExtendedPoint,
24 point_compress::CompressedPoint,
25};
26
27const CONTEXTS: usize = 20;
28const FREE: u128 = 0;
29const READY: u128 = 1;
30const WAIT: u128 = 2;
31
32#[derive(Copy, Clone, Debug, Default, PartialEq, Digital)]
33#[doc(hidden)]
34pub enum DecodeStep {
35 #[default]
36 Y2,
37 Dy2,
38 V2,
39 V3,
40 V6,
41 V7,
42 Uv3,
43 Uv7,
44 ExpSquare,
45 ExpMul,
46 Root,
47 Root2,
48 Check,
49 Correct,
50 T,
51 T2d,
52}
53
54#[derive(Copy, Clone, Debug, PartialEq, Digital)]
56pub struct PointDecodeIn {
57 pub valid: bool,
59 pub encoded: CompressedPoint,
61 pub tag: b16,
63}
64
65#[derive(Copy, Clone, Debug, PartialEq, Digital)]
67pub struct PointDecodeOut {
68 pub ready: bool,
70 pub valid: bool,
72 pub point_valid: bool,
74 pub small_order: bool,
76 pub point: ExtendedPoint,
78 pub t2d: FieldElement51,
80 pub tag: b16,
82 pub occupancy: b5,
84}
85
86#[derive(Copy, Clone, Debug, PartialEq, Digital)]
87#[doc(hidden)]
88pub struct DecodeContext {
89 state: b2,
90 step: DecodeStep,
91 tag: b16,
92 sign: bool,
93 canonical: bool,
94 small_order: bool,
95 square: bool,
96 flipped: bool,
97 exponent_bit: b8,
98 y: FieldElement51,
99 u: FieldElement51,
100 v: FieldElement51,
101 v2: FieldElement51,
102 v3: FieldElement51,
103 v7: FieldElement51,
104 uv3: FieldElement51,
105 base: FieldElement51,
106 accumulator: FieldElement51,
107 root: FieldElement51,
108 x: FieldElement51,
109 t: FieldElement51,
110}
111
112fn empty_context() -> DecodeContext {
113 let zero = FieldElement51::zero();
114 DecodeContext {
115 state: b2(FREE),
116 step: DecodeStep::Y2,
117 tag: b16(0),
118 sign: false,
119 canonical: false,
120 small_order: false,
121 square: false,
122 flipped: false,
123 exponent_bit: b8(0),
124 y: zero,
125 u: zero,
126 v: zero,
127 v2: zero,
128 v3: zero,
129 v7: zero,
130 uv3: zero,
131 base: zero,
132 accumulator: zero,
133 root: zero,
134 x: zero,
135 t: zero,
136 }
137}
138
139#[derive(Copy, Clone, Debug, PartialEq, Digital)]
140#[doc(hidden)]
141pub struct DecodeControl {
142 contexts: [DecodeContext; CONTEXTS],
143 issue_valid: [bool; 17],
144 issue_context: [b5; 17],
145 round_robin: b5,
146}
147
148fn reset_control() -> DecodeControl {
149 DecodeControl {
150 contexts: [empty_context(); CONTEXTS],
151 issue_valid: [false; 17],
152 issue_context: [b5(0); 17],
153 round_robin: b5(0),
154 }
155}
156
157#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
159pub struct PointDecodePipe {
160 control: DFF<DecodeControl>,
161 multiplier: FieldMulPipe,
162}
163
164impl Default for PointDecodePipe {
165 fn default() -> Self {
166 Self {
167 control: DFF::new(reset_control()),
168 multiplier: FieldMulPipe::default(),
169 }
170 }
171}
172
173impl SynchronousIO for PointDecodePipe {
174 type I = PointDecodeIn;
175 type O = PointDecodeOut;
176 type Kernel = point_decode_kernel;
177}
178
179#[kernel]
180fn field_zero() -> FieldElement51 {
181 FieldElement51 { limbs: [b51(0); 5] }
182}
183
184#[kernel]
185fn field_one() -> FieldElement51 {
186 FieldElement51 {
187 limbs: [b51(1), b51(0), b51(0), b51(0), b51(0)],
188 }
189}
190
191#[kernel]
192fn empty_context_kernel(_unused: bool) -> DecodeContext {
193 let zero = field_zero();
194 DecodeContext {
195 state: b2(FREE),
196 step: DecodeStep::Y2,
197 tag: b16(0),
198 sign: false,
199 canonical: false,
200 small_order: false,
201 square: false,
202 flipped: false,
203 exponent_bit: b8(0),
204 y: zero,
205 u: zero,
206 v: zero,
207 v2: zero,
208 v3: zero,
209 v7: zero,
210 uv3: zero,
211 base: zero,
212 accumulator: zero,
213 root: zero,
214 x: zero,
215 t: zero,
216 }
217}
218
219#[kernel]
220fn edwards_d() -> FieldElement51 {
221 field_from_words_kernel([
222 b64(0x75eb4dca135978a3),
223 b64(0x00700a4d4141d8ab),
224 b64(0x8cc740797779e898),
225 b64(0x52036cee2b6ffe73),
226 ])
227}
228
229#[kernel]
230fn sqrt_m1() -> FieldElement51 {
231 field_from_words_kernel([
232 b64(0xc4ee1b274a0ea0b0),
233 b64(0x2f431806ad2fe478),
234 b64(0x2b4d00993dfbd7a7),
235 b64(0x2b8324804fc1df0b),
236 ])
237}
238
239#[kernel]
240fn edwards_d2() -> FieldElement51 {
241 field_from_words_kernel([
242 b64(0xebd69b9426b2f159),
243 b64(0x00e0149a8283b156),
244 b64(0x198e80f2eef3d130),
245 b64(0x2406d9dc56dffce7),
246 ])
247}
248
249#[kernel]
250fn words_equal(a: [b64; 4], b: [b64; 4]) -> bool {
251 a[0] == b[0] && a[1] == b[1] && a[2] == b[2] && a[3] == b[3]
252}
253
254#[kernel]
255fn canonical_y(words: [b64; 4]) -> bool {
256 let high = words[3] & b64(0x7fff_ffff_ffff_ffff);
257 high < b64(0x7fff_ffff_ffff_ffff)
258 || (high == b64(0x7fff_ffff_ffff_ffff)
259 && words[2] == b64(0xffff_ffff_ffff_ffff)
260 && words[1] == b64(0xffff_ffff_ffff_ffff)
261 && words[0] < b64(0xffff_ffff_ffff_ffed))
262}
263
264#[kernel]
265fn small_order_encoding(words: [b64; 4]) -> bool {
266 words_equal(words, [b64(1), b64(0), b64(0), b64(0)])
267 || words_equal(
268 words,
269 [
270 b64(0x4fd84d3d706a17c7),
271 b64(0x0f67100d760b3cba),
272 b64(0xc6cc392cfa53202a),
273 b64(0x7a03ac9277fdc74e),
274 ],
275 )
276 || words_equal(words, [b64(0), b64(0), b64(0), b64(0x8000_0000_0000_0000)])
277 || words_equal(
278 words,
279 [
280 b64(0xb027b2c28f95e826),
281 b64(0xf098eff289f4c345),
282 b64(0x3933c6d305acdfd5),
283 b64(0x05fc536d880238b1),
284 ],
285 )
286 || words_equal(
287 words,
288 [
289 b64(0xffff_ffff_ffff_ffec),
290 b64(0xffff_ffff_ffff_ffff),
291 b64(0xffff_ffff_ffff_ffff),
292 b64(0x7fff_ffff_ffff_ffff),
293 ],
294 )
295 || words_equal(
296 words,
297 [
298 b64(0xb027b2c28f95e826),
299 b64(0xf098eff289f4c345),
300 b64(0x3933c6d305acdfd5),
301 b64(0x85fc536d880238b1),
302 ],
303 )
304 || words_equal(words, [b64(0), b64(0), b64(0), b64(0)])
305 || words_equal(
306 words,
307 [
308 b64(0x4fd84d3d706a17c7),
309 b64(0x0f67100d760b3cba),
310 b64(0xc6cc392cfa53202a),
311 b64(0xfa03ac9277fdc74e),
312 ],
313 )
314}
315
316#[kernel]
317fn wrap20(value: b5) -> b5 {
318 if value >= b5(20) {
319 value - b5(20)
320 } else {
321 value
322 }
323}
324
325#[kernel]
326fn signed_root(value: FieldElement51, sign: bool) -> FieldElement51 {
327 let odd = (field_to_words_kernel(value)[0] & b64(1)) != b64(0);
328 if odd == sign {
329 value
330 } else {
331 field_neg_kernel(value)
332 }
333}
334
335#[kernel]
337pub fn point_decode_kernel(
338 _cr: ClockReset,
339 i: PointDecodeIn,
340 q: PointDecodePipeQ,
341) -> (PointDecodeOut, PointDecodePipeD) {
342 let r = q.control;
343 let mut n = r;
344 let zero = field_zero();
345 let one = field_one();
346 let mut output = PointDecodeOut {
347 ready: false,
348 valid: false,
349 point_valid: false,
350 small_order: false,
351 point: ExtendedPoint {
352 x: zero,
353 y: one,
354 z: one,
355 t: zero,
356 },
357 t2d: zero,
358 tag: b16(0),
359 occupancy: b5(0),
360 };
361
362 let mut free_found = false;
363 let mut free_context = b5(0);
364 for index in 0..CONTEXTS {
365 if r.contexts[index].state != b2(FREE) {
366 output.occupancy += b5(1);
367 } else if !free_found {
368 free_found = true;
369 free_context = b5(index as u128);
370 }
371 }
372 output.ready = free_found;
373
374 let mut issue_found = false;
375 let mut issue_context = b5(0);
376 for offset in 0..CONTEXTS {
377 let candidate = wrap20(r.round_robin + b5(offset as u128));
378 if !issue_found && r.contexts[candidate].state == b2(READY) {
379 issue_found = true;
380 issue_context = candidate;
381 }
382 }
383
384 let context = r.contexts[issue_context];
385 let mut operand_a = zero;
386 let mut operand_b = zero;
387 if issue_found {
388 match context.step {
389 DecodeStep::Y2 => {
390 operand_a = context.y;
391 operand_b = context.y;
392 }
393 DecodeStep::Dy2 => {
394 operand_a = field_add_kernel(context.u, one);
395 operand_b = edwards_d();
396 }
397 DecodeStep::V2 => {
398 operand_a = context.v;
399 operand_b = context.v;
400 }
401 DecodeStep::V3 => {
402 operand_a = context.v2;
403 operand_b = context.v;
404 }
405 DecodeStep::V6 => {
406 operand_a = context.v3;
407 operand_b = context.v3;
408 }
409 DecodeStep::V7 => {
410 operand_a = context.v2;
411 operand_b = context.v;
412 }
413 DecodeStep::Uv3 => {
414 operand_a = context.u;
415 operand_b = context.v3;
416 }
417 DecodeStep::Uv7 => {
418 operand_a = context.u;
419 operand_b = context.v7;
420 }
421 DecodeStep::ExpSquare => {
422 operand_a = context.accumulator;
423 operand_b = context.accumulator;
424 }
425 DecodeStep::ExpMul => {
426 operand_a = context.accumulator;
427 operand_b = context.base;
428 }
429 DecodeStep::Root => {
430 operand_a = context.uv3;
431 operand_b = context.accumulator;
432 }
433 DecodeStep::Root2 => {
434 operand_a = context.root;
435 operand_b = context.root;
436 }
437 DecodeStep::Check => {
438 operand_a = context.v;
439 operand_b = context.accumulator;
440 }
441 DecodeStep::Correct => {
442 operand_a = context.root;
443 operand_b = if context.flipped { sqrt_m1() } else { one };
444 }
445 DecodeStep::T => {
446 operand_a = context.x;
447 operand_b = context.y;
448 }
449 _ => {
450 operand_a = context.t;
451 operand_b = edwards_d2();
452 }
453 }
454 n.contexts[issue_context].state = b2(WAIT);
455 n.round_robin = wrap20(issue_context + b5(1));
456 }
457
458 n.issue_valid[0] = issue_found;
459 n.issue_context[0] = issue_context;
460 for stage in 1..17 {
461 n.issue_valid[stage] = r.issue_valid[stage - 1];
462 n.issue_context[stage] = r.issue_context[stage - 1];
463 }
464
465 if r.issue_valid[16] && q.multiplier.valid {
466 let result_context = r.issue_context[16];
467 let current = r.contexts[result_context];
468 let result = q.multiplier.result;
469 n.contexts[result_context].state = b2(READY);
470 match current.step {
471 DecodeStep::Y2 => {
472 n.contexts[result_context].u = field_sub_kernel(result, one);
473 n.contexts[result_context].step = DecodeStep::Dy2;
474 }
475 DecodeStep::Dy2 => {
476 n.contexts[result_context].v = field_add_kernel(result, one);
477 n.contexts[result_context].step = DecodeStep::V2;
478 }
479 DecodeStep::V2 => {
480 n.contexts[result_context].v2 = result;
481 n.contexts[result_context].step = DecodeStep::V3;
482 }
483 DecodeStep::V3 => {
484 n.contexts[result_context].v3 = result;
485 n.contexts[result_context].step = DecodeStep::V6;
486 }
487 DecodeStep::V6 => {
488 n.contexts[result_context].v2 = result;
489 n.contexts[result_context].step = DecodeStep::V7;
490 }
491 DecodeStep::V7 => {
492 n.contexts[result_context].v7 = result;
493 n.contexts[result_context].step = DecodeStep::Uv3;
494 }
495 DecodeStep::Uv3 => {
496 n.contexts[result_context].uv3 = result;
497 n.contexts[result_context].step = DecodeStep::Uv7;
498 }
499 DecodeStep::Uv7 => {
500 n.contexts[result_context].base = result;
501 n.contexts[result_context].accumulator = one;
502 n.contexts[result_context].exponent_bit = b8(251);
503 n.contexts[result_context].step = DecodeStep::ExpSquare;
504 }
505 DecodeStep::ExpSquare => {
506 n.contexts[result_context].accumulator = result;
507 if current.exponent_bit == b8(1) {
508 n.contexts[result_context].exponent_bit = b8(0);
509 n.contexts[result_context].step = DecodeStep::ExpSquare;
510 } else {
511 n.contexts[result_context].step = DecodeStep::ExpMul;
512 }
513 }
514 DecodeStep::ExpMul => {
515 n.contexts[result_context].accumulator = result;
516 if current.exponent_bit == b8(0) {
517 n.contexts[result_context].step = DecodeStep::Root;
518 } else {
519 n.contexts[result_context].exponent_bit = current.exponent_bit - b8(1);
520 n.contexts[result_context].step = DecodeStep::ExpSquare;
521 }
522 }
523 DecodeStep::Root => {
524 n.contexts[result_context].root = result;
525 n.contexts[result_context].step = DecodeStep::Root2;
526 }
527 DecodeStep::Root2 => {
528 n.contexts[result_context].accumulator = result;
529 n.contexts[result_context].step = DecodeStep::Check;
530 }
531 DecodeStep::Check => {
532 let negative_u = field_neg_kernel(current.u);
533 n.contexts[result_context].square =
534 field_eq_kernel(result, current.u) || field_eq_kernel(result, negative_u);
535 n.contexts[result_context].flipped = !field_eq_kernel(result, current.u);
536 n.contexts[result_context].step = DecodeStep::Correct;
537 }
538 DecodeStep::Correct => {
539 let x = signed_root(result, current.sign);
540 n.contexts[result_context].x = x;
541 n.contexts[result_context].step = DecodeStep::T;
542 }
543 DecodeStep::T => {
544 n.contexts[result_context].t = result;
545 n.contexts[result_context].step = DecodeStep::T2d;
546 }
547 _ => {
548 output.valid = true;
549 output.point_valid = current.canonical
550 && current.square
551 && !(field_eq_kernel(current.x, zero) && current.sign);
552 output.small_order = current.small_order;
553 output.point = ExtendedPoint {
554 x: current.x,
555 y: current.y,
556 z: one,
557 t: current.t,
558 };
559 output.t2d = result;
560 output.tag = current.tag;
561 n.contexts[result_context] = empty_context_kernel(false);
562 }
563 }
564 }
565
566 if i.valid && free_found {
567 let mut y_words = i.encoded.words;
568 let sign = (y_words[3] >> 63) != b64(0);
569 y_words[3] &= b64(0x7fff_ffff_ffff_ffff);
570 n.contexts[free_context] = DecodeContext {
571 state: b2(READY),
572 step: DecodeStep::Y2,
573 tag: i.tag,
574 sign,
575 canonical: canonical_y(i.encoded.words),
576 small_order: small_order_encoding(i.encoded.words),
577 exponent_bit: b8(0),
578 y: field_from_words_kernel(y_words),
579 ..empty_context_kernel(false)
580 };
581 }
582
583 let mut d = PointDecodePipeD::dont_care();
584 d.control = n;
585 d.multiplier = FieldMulIn {
586 valid: issue_found,
587 a: operand_a,
588 b: operand_b,
589 };
590 (output, d)
591}
592
593#[cfg(test)]
594mod tests {
595 use super::*;
596 use curve25519_dalek::{constants::ED25519_BASEPOINT_COMPRESSED, edwards::CompressedEdwardsY};
597 use ed25519_dalek::{Signer, SigningKey};
598 use std::iter;
599
600 fn compressed(bytes: [u8; 32]) -> CompressedPoint {
601 CompressedPoint {
602 words: std::array::from_fn(|index| {
603 b64(u64::from_le_bytes(bytes[index * 8..index * 8 + 8].try_into().unwrap()) as u128)
604 }),
605 }
606 }
607
608 fn point_bytes(point: ExtendedPoint) -> [u8; 32] {
609 let mut bytes = [0u8; 32];
610 for (index, word) in field_to_words_kernel(point.y).iter().enumerate() {
611 bytes[index * 8..index * 8 + 8].copy_from_slice(&(word.raw() as u64).to_le_bytes());
612 }
613 bytes[31] |= ((field_to_words_kernel(point.x)[0].raw() as u8) & 1) << 7;
614 bytes
615 }
616
617 fn idle() -> PointDecodeIn {
618 PointDecodeIn {
619 valid: false,
620 encoded: CompressedPoint::zero(),
621 tag: b16(0),
622 }
623 }
624
625 #[test]
626 fn decodes_basepoint_and_rejects_noncanonical_and_low_order() {
627 std::thread::Builder::new()
628 .name("point-decode-sim".into())
629 .stack_size(256 * 1024 * 1024)
630 .spawn(|| {
631 let mut noncanonical = [0xff; 32];
632 noncanonical[31] = 0x7f;
633 let identity = {
634 let mut value = [0u8; 32];
635 value[0] = 1;
636 value
637 };
638 let key = SigningKey::from_bytes(&[0x42; 32]);
639 let signature = key.sign(&[0x5a; 64]).to_bytes();
640 let mut encoded_r = [0u8; 32];
641 encoded_r.copy_from_slice(&signature[..32]);
642 let public_key = key.verifying_key().to_bytes();
643 let requests = [
644 PointDecodeIn {
645 valid: true,
646 encoded: compressed(ED25519_BASEPOINT_COMPRESSED.to_bytes()),
647 tag: b16(1),
648 },
649 PointDecodeIn {
650 valid: true,
651 encoded: compressed(noncanonical),
652 tag: b16(2),
653 },
654 PointDecodeIn {
655 valid: true,
656 encoded: compressed(identity),
657 tag: b16(3),
658 },
659 PointDecodeIn {
660 valid: true,
661 encoded: compressed(public_key),
662 tag: b16(4),
663 },
664 PointDecodeIn {
665 valid: true,
666 encoded: compressed(encoded_r),
667 tag: b16(5),
668 },
669 ];
670 let outputs: Vec<_> = PointDecodePipe::default()
671 .run(
672 iter::once(idle())
673 .chain(requests)
674 .chain(iter::repeat_n(idle(), 12_000))
675 .with_reset(1)
676 .clock_pos_edge(100),
677 )
678 .synchronous_sample()
679 .map(|sample| sample.output)
680 .filter(|output| output.valid)
681 .collect();
682 assert_eq!(outputs.len(), 5);
683 let base = outputs.iter().find(|output| output.tag == b16(1)).unwrap();
684 assert!(base.point_valid);
685 assert!(!base.small_order);
686 assert_eq!(
687 CompressedEdwardsY(point_bytes(base.point)),
688 ED25519_BASEPOINT_COMPRESSED
689 );
690 let bad = outputs.iter().find(|output| output.tag == b16(2)).unwrap();
691 assert!(!bad.point_valid);
692 let low = outputs.iter().find(|output| output.tag == b16(3)).unwrap();
693 assert!(low.point_valid);
694 assert!(low.small_order);
695 let public = outputs.iter().find(|output| output.tag == b16(4)).unwrap();
696 assert!(public.point_valid);
697 assert_eq!(point_bytes(public.point), public_key);
698 let r = outputs.iter().find(|output| output.tag == b16(5)).unwrap();
699 assert!(r.point_valid);
700 assert_eq!(point_bytes(r.point), encoded_r);
701 })
702 .unwrap()
703 .join()
704 .unwrap();
705 }
706
707 #[test]
708 fn compiles_to_native_rhdl_netlist() -> miette::Result<()> {
709 unsafe { std::env::set_var("RHDL_SKIP_IVERILOG_CHECK", "1") };
710 PointDecodePipe::default()
711 .descriptor(ScopedName::top())?
712 .hdl()?;
713 Ok(())
714 }
715}