1use rhdl::prelude::*;
10use rhdl_fpga::core::{
11 dff::DFF,
12 ram::synchronous::{In as RamIn, SyncBRAM, Write as RamWrite},
13};
14
15use crate::{
16 equation::{EquationIn, EquationPipe},
17 field_parallel::FieldElement51,
18 multicomb::{ExtendedPoint, MultiCombMul, MultiCombMulIn},
19 point_compress::CompressedPoint,
20 point_decode::{PointDecodeIn, PointDecodePipe},
21 protocol::{VerifyRequest, Words512},
22 scalar::{ScalarWords, scalar_ge_kernel, scalar_l_kernel},
23 scalar_parallel::{ScalarReducePipe, ScalarReducePipeIn},
24 sha512_parallel::{Sha512Pool, Sha512PoolIn, Sha512PoolRequest},
25 variable_base::{VariableBaseIn, VariableBaseMul},
26};
27
28pub const VERIFY_OK: u128 = 0;
30pub const VERIFY_BAD_PUBLIC_KEY: u128 = 1;
32pub const VERIFY_BAD_R: u128 = 2;
34pub const VERIFY_SMALL_PUBLIC_KEY: u128 = 3;
36pub const VERIFY_SMALL_R: u128 = 4;
38pub const VERIFY_NONCANONICAL_S: u128 = 5;
40pub const VERIFY_EQUATION_FAILURE: u128 = 6;
42
43#[derive(Copy, Clone, Debug, PartialEq, Digital)]
45pub struct StrictVerifyIn {
46 pub valid: bool,
48 pub request: VerifyRequest,
50}
51
52#[derive(Copy, Clone, Debug, PartialEq, Digital)]
54pub struct StrictVerifyOut {
55 pub ready: bool,
57 pub valid: bool,
59 pub ok: bool,
61 pub error: b8,
63 pub tag: b16,
65 pub occupancy: b7,
67}
68
69#[derive(Copy, Clone, Debug, PartialEq, Digital)]
70#[doc(hidden)]
71pub struct VerifyStatus {
72 used: bool,
73 tag: b16,
74 canonical_s: bool,
75 a_ready: bool,
76 a_valid: bool,
77 a_small: bool,
78 r_ready: bool,
79 r_valid: bool,
80 r_small: bool,
81 fixed_ready: bool,
82 hash1_pending: bool,
83 challenge_ready: bool,
84 variable_issued: bool,
85 variable_ready: bool,
86 variable_lane: bool,
87 equation_issued: bool,
88}
89
90fn empty_status() -> VerifyStatus {
91 VerifyStatus {
92 used: false,
93 tag: b16(0),
94 canonical_s: false,
95 a_ready: false,
96 a_valid: false,
97 a_small: false,
98 r_ready: false,
99 r_valid: false,
100 r_small: false,
101 fixed_ready: false,
102 hash1_pending: false,
103 challenge_ready: false,
104 variable_issued: false,
105 variable_ready: false,
106 variable_lane: false,
107 equation_issued: false,
108 }
109}
110
111#[derive(Copy, Clone, Debug, PartialEq, Digital)]
112#[doc(hidden)]
113pub struct Fetch {
114 valid: bool,
115 context: b6,
116}
117
118#[derive(Copy, Clone, Debug, PartialEq, Digital)]
119#[doc(hidden)]
120pub struct VariableFetch {
121 valid: bool,
122 context: b6,
123 lane: bool,
124}
125
126#[derive(Copy, Clone, Debug, PartialEq, Digital)]
127#[doc(hidden)]
128pub struct HashIssue {
129 valid: bool,
130 context: b6,
131 state: [b64; 8],
132}
133
134#[derive(Copy, Clone, Debug, PartialEq, Digital)]
135#[doc(hidden)]
136pub struct VerifyControl {
137 status: [VerifyStatus; 64],
138 hash_fetch: Fetch,
139 hash_issue: HashIssue,
140 variable_fetch: VariableFetch,
141 equation_fetch: Fetch,
142 hash_rr: b6,
143 variable_rr: b6,
144 equation_rr: b6,
145 variable_lane_rr: bool,
146}
147
148fn reset_control() -> VerifyControl {
149 VerifyControl {
150 status: [empty_status(); 64],
151 hash_fetch: Fetch {
152 valid: false,
153 context: b6(0),
154 },
155 hash_issue: HashIssue {
156 valid: false,
157 context: b6(0),
158 state: [b64(0); 8],
159 },
160 variable_fetch: VariableFetch {
161 valid: false,
162 context: b6(0),
163 lane: false,
164 },
165 equation_fetch: Fetch {
166 valid: false,
167 context: b6(0),
168 },
169 hash_rr: b6(0),
170 variable_rr: b6(0),
171 equation_rr: b6(0),
172 variable_lane_rr: false,
173 }
174}
175
176#[derive(Copy, Clone, Debug, PartialEq, Digital)]
178#[doc(hidden)]
179pub struct DecodedPoint {
180 point: ExtendedPoint,
181 t2d: FieldElement51,
182}
183
184#[derive(Copy, Clone, Debug, PartialEq, Digital)]
185#[doc(hidden)]
186pub struct VerifyMemoryIn {
187 chain: RamIn<Words512, 6>,
188 a: RamIn<DecodedPoint, 6>,
189 r: RamIn<DecodedPoint, 6>,
190 fixed: RamIn<ExtendedPoint, 6>,
191 challenge: RamIn<ScalarWords, 6>,
192 variable0: RamIn<ExtendedPoint, 6>,
193 variable1: RamIn<ExtendedPoint, 6>,
194}
195
196#[derive(Copy, Clone, Debug, PartialEq, Digital)]
197#[doc(hidden)]
198pub struct VerifyMemoryOut {
199 chain: Words512,
200 a: DecodedPoint,
201 r: DecodedPoint,
202 fixed: ExtendedPoint,
203 challenge: ScalarWords,
204 variable0: ExtendedPoint,
205 variable1: ExtendedPoint,
206}
207
208#[derive(Clone, Debug, Synchronous, SynchronousDQ, Default)]
210#[doc(hidden)]
211pub struct VerifyMemory {
212 chain: SyncBRAM<Words512, 6>,
213 a: SyncBRAM<DecodedPoint, 6>,
214 r: SyncBRAM<DecodedPoint, 6>,
215 fixed: SyncBRAM<ExtendedPoint, 6>,
216 challenge: SyncBRAM<ScalarWords, 6>,
217 variable0: SyncBRAM<ExtendedPoint, 6>,
218 variable1: SyncBRAM<ExtendedPoint, 6>,
219}
220
221impl SynchronousIO for VerifyMemory {
222 type I = VerifyMemoryIn;
223 type O = VerifyMemoryOut;
224 type Kernel = verify_memory_kernel;
225}
226
227#[kernel]
229#[doc(hidden)]
230pub fn verify_memory_kernel(
231 _cr: ClockReset,
232 i: VerifyMemoryIn,
233 q: VerifyMemoryQ,
234) -> (VerifyMemoryOut, VerifyMemoryD) {
235 let output = VerifyMemoryOut {
236 chain: q.chain,
237 a: q.a,
238 r: q.r,
239 fixed: q.fixed,
240 challenge: q.challenge,
241 variable0: q.variable0,
242 variable1: q.variable1,
243 };
244 let mut d = VerifyMemoryD::dont_care();
245 d.chain = i.chain;
246 d.a = i.a;
247 d.r = i.r;
248 d.fixed = i.fixed;
249 d.challenge = i.challenge;
250 d.variable0 = i.variable0;
251 d.variable1 = i.variable1;
252 (output, d)
253}
254
255#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
257pub struct StrictVerifyCore {
258 control: DFF<VerifyControl>,
259 memory: VerifyMemory,
260 decode_a: PointDecodePipe,
261 decode_r: PointDecodePipe,
262 sha: Sha512Pool,
263 reducer: ScalarReducePipe,
264 fixed: MultiCombMul,
265 variable0: VariableBaseMul,
266 variable1: VariableBaseMul,
267 equation: EquationPipe,
268}
269
270impl Default for StrictVerifyCore {
271 fn default() -> Self {
272 Self {
273 control: DFF::new(reset_control()),
274 memory: VerifyMemory::default(),
275 decode_a: PointDecodePipe::default(),
276 decode_r: PointDecodePipe::default(),
277 sha: Sha512Pool::default(),
278 reducer: ScalarReducePipe::default(),
279 fixed: MultiCombMul::default(),
280 variable0: VariableBaseMul::default(),
281 variable1: VariableBaseMul::default(),
282 equation: EquationPipe::default(),
283 }
284 }
285}
286
287impl SynchronousIO for StrictVerifyCore {
288 type I = StrictVerifyIn;
289 type O = StrictVerifyOut;
290 type Kernel = strict_verify_kernel;
291}
292
293#[kernel]
294fn zero_field() -> FieldElement51 {
295 FieldElement51 { limbs: [b51(0); 5] }
296}
297
298#[kernel]
299fn one_field() -> FieldElement51 {
300 FieldElement51 {
301 limbs: [b51(1), b51(0), b51(0), b51(0), b51(0)],
302 }
303}
304
305#[kernel]
306fn identity() -> ExtendedPoint {
307 ExtendedPoint {
308 x: zero_field(),
309 y: one_field(),
310 z: one_field(),
311 t: zero_field(),
312 }
313}
314
315#[kernel]
316fn empty_status_kernel(_unused: bool) -> VerifyStatus {
317 VerifyStatus {
318 used: false,
319 tag: b16(0),
320 canonical_s: false,
321 a_ready: false,
322 a_valid: false,
323 a_small: false,
324 r_ready: false,
325 r_valid: false,
326 r_small: false,
327 fixed_ready: false,
328 hash1_pending: false,
329 challenge_ready: false,
330 variable_issued: false,
331 variable_ready: false,
332 variable_lane: false,
333 equation_issued: false,
334 }
335}
336
337#[kernel]
338fn reverse_bytes(value: b64) -> b64 {
339 let mut result = b64(0);
340 for byte in 0..8 {
341 result |= ((value >> (byte * 8) as u128).resize::<8>()).resize::<64>()
342 << ((7 - byte) * 8) as u128;
343 }
344 result
345}
346
347#[kernel]
348fn sha_iv() -> [b64; 8] {
349 [
350 b64(0x6a09e667f3bcc908),
351 b64(0xbb67ae8584caa73b),
352 b64(0x3c6ef372fe94f82b),
353 b64(0xa54ff53a5f1d36f1),
354 b64(0x510e527fade682d1),
355 b64(0x9b05688c2b3e6c1f),
356 b64(0x1f83d9abfb41bd6b),
357 b64(0x5be0cd19137e2179),
358 ]
359}
360
361#[kernel]
362fn challenge_block0(request: VerifyRequest) -> [b64; 16] {
363 let mut block = [b64(0); 16];
364 for index in 0..4 {
365 block[index] = reverse_bytes(request.signature.words[index]);
366 block[index + 4] = reverse_bytes(request.public_key.words[index]);
367 }
368 for index in 0..8 {
369 block[index + 8] = reverse_bytes(request.message.words[index]);
370 }
371 block
372}
373
374#[kernel]
375fn challenge_block1() -> [b64; 16] {
376 let mut block = [b64(0); 16];
377 block[0] = b64(0x8000000000000000);
378 block[15] = b64(0x400);
379 block
380}
381
382#[kernel]
383fn digest_words(state: [b64; 8]) -> Words512 {
384 let mut words = [b64(0); 8];
385 for index in 0..8 {
386 words[index] = reverse_bytes(state[index]);
387 }
388 Words512 { words }
389}
390
391#[kernel]
392fn request_r(request: VerifyRequest) -> CompressedPoint {
393 CompressedPoint {
394 words: [
395 request.signature.words[0],
396 request.signature.words[1],
397 request.signature.words[2],
398 request.signature.words[3],
399 ],
400 }
401}
402
403#[kernel]
404fn request_s(request: VerifyRequest) -> ScalarWords {
405 ScalarWords {
406 words: [
407 request.signature.words[4],
408 request.signature.words[5],
409 request.signature.words[6],
410 request.signature.words[7],
411 ],
412 }
413}
414
415#[kernel]
416fn wrap64(value: b6) -> b6 {
417 value
418}
419
420#[kernel]
421fn no_point_write() -> RamWrite<ExtendedPoint, 6> {
422 RamWrite::<ExtendedPoint, 6> {
423 addr: b6(0),
424 value: identity(),
425 enable: false,
426 }
427}
428
429#[kernel]
431pub fn strict_verify_kernel(
432 _cr: ClockReset,
433 i: StrictVerifyIn,
434 q: StrictVerifyCoreQ,
435) -> (StrictVerifyOut, StrictVerifyCoreD) {
436 let r = q.control;
437 let mut n = r;
438 let zero_words = Words512 { words: [b64(0); 8] };
439 let zero_scalar = ScalarWords { words: [b64(0); 4] };
440 let empty_decoded = DecodedPoint {
441 point: identity(),
442 t2d: zero_field(),
443 };
444 let mut output = StrictVerifyOut {
445 ready: false,
446 valid: false,
447 ok: false,
448 error: b8(0),
449 tag: b16(0),
450 occupancy: b7(0),
451 };
452
453 let mut free_found = false;
454 let mut free_context = b6(0);
455 for context in 0..64 {
456 if r.status[context].used {
457 output.occupancy += b7(1);
458 } else if !free_found {
459 free_found = true;
460 free_context = b6(context as u128);
461 }
462 }
463 let accept_resources =
464 free_found && q.decode_a.ready && q.decode_r.ready && q.fixed.ready && q.sha.request1_ready;
465 output.ready = accept_resources;
466 let accept = i.valid && accept_resources;
467
468 let mut chain_write = RamWrite::<Words512, 6> {
469 addr: b6(0),
470 value: zero_words,
471 enable: false,
472 };
473 let mut a_write = RamWrite::<DecodedPoint, 6> {
474 addr: b6(0),
475 value: empty_decoded,
476 enable: false,
477 };
478 let mut r_write = a_write;
479 let mut fixed_write = no_point_write();
480 let mut challenge_write = RamWrite::<ScalarWords, 6> {
481 addr: b6(0),
482 value: zero_scalar,
483 enable: false,
484 };
485 let mut variable0_write = no_point_write();
486 let mut variable1_write = no_point_write();
487
488 if accept {
489 let scalar = request_s(i.request);
490 n.status[free_context] = VerifyStatus {
491 used: true,
492 tag: i.request.tag,
493 canonical_s: !scalar_ge_kernel(scalar, scalar_l_kernel(false)),
494 ..empty_status_kernel(false)
495 };
496 }
497
498 if q.decode_a.valid {
499 let context = q.decode_a.tag.resize::<6>();
500 n.status[context].a_ready = true;
501 n.status[context].a_valid = q.decode_a.point_valid;
502 n.status[context].a_small = q.decode_a.small_order;
503 a_write = RamWrite::<DecodedPoint, 6> {
504 addr: context,
505 value: DecodedPoint {
506 point: q.decode_a.point,
507 t2d: q.decode_a.t2d,
508 },
509 enable: true,
510 };
511 }
512 if q.decode_r.valid {
513 let context = q.decode_r.tag.resize::<6>();
514 n.status[context].r_ready = true;
515 n.status[context].r_valid = q.decode_r.point_valid;
516 n.status[context].r_small = q.decode_r.small_order;
517 r_write = RamWrite::<DecodedPoint, 6> {
518 addr: context,
519 value: DecodedPoint {
520 point: q.decode_r.point,
521 t2d: q.decode_r.t2d,
522 },
523 enable: true,
524 };
525 }
526 if q.fixed.valid {
527 let context = q.fixed.tag.resize::<6>();
528 n.status[context].fixed_ready = true;
529 fixed_write = RamWrite::<ExtendedPoint, 6> {
530 addr: context,
531 value: q.fixed.point,
532 enable: true,
533 };
534 }
535
536 let sha_results = [q.sha.result0, q.sha.result1];
537 let mut reducer_input = ScalarReducePipeIn {
538 valid: false,
539 wide: zero_words,
540 tag: b16(0),
541 };
542 for result_index in 0..2 {
543 let result = sha_results[result_index];
544 if result.valid {
545 let context = result.tag.resize::<6>();
546 let second = (result.tag & b9(0x40)) != b9(0);
547 if second {
548 reducer_input = ScalarReducePipeIn {
549 valid: true,
550 wide: digest_words(result.state),
551 tag: context.resize::<16>(),
552 };
553 } else {
554 n.status[context].hash1_pending = true;
555 chain_write = RamWrite::<Words512, 6> {
556 addr: context,
557 value: Words512 {
558 words: result.state,
559 },
560 enable: true,
561 };
562 }
563 }
564 }
565 if q.reducer.valid {
566 let context = q.reducer.tag.resize::<6>();
567 n.status[context].challenge_ready = true;
568 challenge_write = RamWrite::<ScalarWords, 6> {
569 addr: context,
570 value: q.reducer.scalar,
571 enable: true,
572 };
573 }
574
575 let hash_issue_accepted = r.hash_issue.valid && q.sha.request0_ready;
576 if hash_issue_accepted {
577 n.hash_issue.valid = false;
578 }
579 if r.hash_fetch.valid && (!r.hash_issue.valid || hash_issue_accepted) {
580 n.hash_issue = HashIssue {
581 valid: true,
582 context: r.hash_fetch.context,
583 state: q.memory.chain.words,
584 };
585 n.hash_fetch.valid = false;
586 }
587 if !n.hash_fetch.valid {
588 let mut found = false;
589 let mut selected = b6(0);
590 for offset in 0..64 {
591 let candidate = wrap64(r.hash_rr + b6(offset as u128));
592 if !found && r.status[candidate].used && r.status[candidate].hash1_pending {
593 found = true;
594 selected = candidate;
595 }
596 }
597 if found {
598 n.status[selected].hash1_pending = false;
599 n.hash_fetch = Fetch {
600 valid: true,
601 context: selected,
602 };
603 n.hash_rr = selected + b6(1);
604 }
605 }
606
607 let lane0_ready = q.variable0.ready;
608 let lane1_ready = q.variable1.ready;
609 let variable_input_valid = r.variable_fetch.valid;
610 let variable_consumed = variable_input_valid
611 && if r.variable_fetch.lane {
612 lane1_ready
613 } else {
614 lane0_ready
615 };
616 if variable_consumed {
617 n.variable_fetch.valid = false;
618 }
619 if !n.variable_fetch.valid {
620 let mut found = false;
621 let mut selected = b6(0);
622 for offset in 0..64 {
623 let candidate = wrap64(r.variable_rr + b6(offset as u128));
624 let status = r.status[candidate];
625 if !found
626 && status.used
627 && status.a_ready
628 && status.challenge_ready
629 && !status.variable_issued
630 {
631 found = true;
632 selected = candidate;
633 }
634 }
635 if found && (lane0_ready || lane1_ready) {
636 let lane = if lane0_ready && lane1_ready {
637 r.variable_lane_rr
638 } else {
639 lane1_ready
640 };
641 n.status[selected].variable_issued = true;
642 n.status[selected].variable_lane = lane;
643 n.variable_fetch = VariableFetch {
644 valid: true,
645 context: selected,
646 lane,
647 };
648 n.variable_rr = selected + b6(1);
649 n.variable_lane_rr = !lane;
650 }
651 }
652 if q.variable0.valid {
653 let context = q.variable0.tag.resize::<6>();
654 n.status[context].variable_ready = true;
655 variable0_write = RamWrite::<ExtendedPoint, 6> {
656 addr: context,
657 value: q.variable0.point,
658 enable: true,
659 };
660 }
661 if q.variable1.valid {
662 let context = q.variable1.tag.resize::<6>();
663 n.status[context].variable_ready = true;
664 variable1_write = RamWrite::<ExtendedPoint, 6> {
665 addr: context,
666 value: q.variable1.point,
667 enable: true,
668 };
669 }
670
671 let equation_consumed = r.equation_fetch.valid && q.equation.ready;
672 if equation_consumed {
673 n.equation_fetch.valid = false;
674 }
675 if !n.equation_fetch.valid {
676 let mut found = false;
677 let mut selected = b6(0);
678 for offset in 0..64 {
679 let candidate = wrap64(r.equation_rr + b6(offset as u128));
680 let status = r.status[candidate];
681 if !found
682 && status.used
683 && status.r_ready
684 && status.fixed_ready
685 && status.variable_ready
686 && !status.equation_issued
687 {
688 found = true;
689 selected = candidate;
690 }
691 }
692 if found && q.equation.ready {
693 n.status[selected].equation_issued = true;
694 n.equation_fetch = Fetch {
695 valid: true,
696 context: selected,
697 };
698 n.equation_rr = selected + b6(1);
699 }
700 }
701
702 if q.equation.valid {
703 let context = q.equation.tag.resize::<6>();
704 let status = r.status[context];
705 let error = if !status.a_valid {
706 b8(VERIFY_BAD_PUBLIC_KEY)
707 } else if !status.r_valid {
708 b8(VERIFY_BAD_R)
709 } else if status.a_small {
710 b8(VERIFY_SMALL_PUBLIC_KEY)
711 } else if status.r_small {
712 b8(VERIFY_SMALL_R)
713 } else if !status.canonical_s {
714 b8(VERIFY_NONCANONICAL_S)
715 } else if !q.equation.equal {
716 b8(VERIFY_EQUATION_FAILURE)
717 } else {
718 b8(VERIFY_OK)
719 };
720 output.valid = true;
721 output.ok = error == b8(VERIFY_OK);
722 output.error = error;
723 output.tag = status.tag;
724 n.status[context] = empty_status_kernel(false);
725 }
726
727 let hash_read = if n.hash_fetch.valid {
728 n.hash_fetch.context
729 } else {
730 r.hash_issue.context
731 };
732 let variable_read = if n.variable_fetch.valid {
733 n.variable_fetch.context
734 } else {
735 r.variable_fetch.context
736 };
737 let equation_read = if n.equation_fetch.valid {
738 n.equation_fetch.context
739 } else {
740 r.equation_fetch.context
741 };
742
743 let mut d = StrictVerifyCoreD::dont_care();
744 d.control = n;
745 d.memory = VerifyMemoryIn {
746 chain: RamIn::<Words512, 6> {
747 read_addr: hash_read,
748 write: chain_write,
749 },
750 a: RamIn::<DecodedPoint, 6> {
751 read_addr: variable_read,
752 write: a_write,
753 },
754 r: RamIn::<DecodedPoint, 6> {
755 read_addr: equation_read,
756 write: r_write,
757 },
758 fixed: RamIn::<ExtendedPoint, 6> {
759 read_addr: equation_read,
760 write: fixed_write,
761 },
762 challenge: RamIn::<ScalarWords, 6> {
763 read_addr: variable_read,
764 write: challenge_write,
765 },
766 variable0: RamIn::<ExtendedPoint, 6> {
767 read_addr: equation_read,
768 write: variable0_write,
769 },
770 variable1: RamIn::<ExtendedPoint, 6> {
771 read_addr: equation_read,
772 write: variable1_write,
773 },
774 };
775 d.decode_a = PointDecodeIn {
776 valid: accept,
777 encoded: CompressedPoint {
778 words: i.request.public_key.words,
779 },
780 tag: free_context.resize::<16>(),
781 };
782 d.decode_r = PointDecodeIn {
783 valid: accept,
784 encoded: request_r(i.request),
785 tag: free_context.resize::<16>(),
786 };
787 d.fixed = MultiCombMulIn {
788 valid: accept,
789 scalar: request_s(i.request),
790 tag: free_context.resize::<16>(),
791 };
792 d.sha = Sha512PoolIn {
793 request0: Sha512PoolRequest {
794 valid: r.hash_issue.valid,
795 tag: b9(0x40) | r.hash_issue.context.resize::<9>(),
796 state: r.hash_issue.state,
797 block: challenge_block1(),
798 },
799 request1: Sha512PoolRequest {
800 valid: accept,
801 tag: free_context.resize::<9>(),
802 state: sha_iv(),
803 block: challenge_block0(i.request),
804 },
805 };
806 d.reducer = reducer_input;
807 let variable_request = VariableBaseIn {
808 valid: variable_input_valid,
809 point: q.memory.a.point,
810 t2d: q.memory.a.t2d,
811 scalar: q.memory.challenge,
812 tag: r.variable_fetch.context.resize::<16>(),
813 };
814 d.variable0 = VariableBaseIn {
815 valid: variable_request.valid && !r.variable_fetch.lane,
816 ..variable_request
817 };
818 d.variable1 = VariableBaseIn {
819 valid: variable_request.valid && r.variable_fetch.lane,
820 ..variable_request
821 };
822 d.equation = EquationIn {
823 valid: r.equation_fetch.valid,
824 variable: if r.status[r.equation_fetch.context].variable_lane {
825 q.memory.variable1
826 } else {
827 q.memory.variable0
828 },
829 r: q.memory.r.point,
830 r_t2d: q.memory.r.t2d,
831 fixed: q.memory.fixed,
832 tag: r.equation_fetch.context.resize::<16>(),
833 };
834 (output, d)
835}
836
837#[cfg(test)]
838mod tests {
839 use super::*;
840 use crate::sha512_parallel::{Sha512Compress, Sha512CompressIn};
841 use curve25519_dalek::scalar::Scalar;
842 use ed25519_dalek::{Signer, SigningKey};
843 use rhdl::core::sim::ResetOrData;
844 use sha2::{Digest, Sha512};
845 use std::iter;
846
847 fn scalar_words(bytes: [u8; 32]) -> ScalarWords {
848 ScalarWords {
849 words: std::array::from_fn(|index| {
850 b64(u64::from_le_bytes(bytes[index * 8..index * 8 + 8].try_into().unwrap()) as u128)
851 }),
852 }
853 }
854
855 fn words512(bytes: [u8; 64]) -> Words512 {
856 Words512 {
857 words: std::array::from_fn(|index| {
858 b64(u64::from_le_bytes(bytes[index * 8..index * 8 + 8].try_into().unwrap()) as u128)
859 }),
860 }
861 }
862
863 fn idle() -> StrictVerifyIn {
864 StrictVerifyIn {
865 valid: false,
866 request: crate::protocol::zero_verify_request(),
867 }
868 }
869
870 fn compress_block(state: [b64; 8], block: [b64; 16]) -> [b64; 8] {
871 let idle = Sha512CompressIn {
872 start: false,
873 state: [b64(0); 8],
874 block: [b64(0); 16],
875 };
876 Sha512Compress::default()
877 .run(
878 iter::once(idle)
879 .chain(iter::once(Sha512CompressIn {
880 start: true,
881 state,
882 block,
883 }))
884 .chain(iter::repeat_n(idle, 250))
885 .with_reset(1)
886 .clock_pos_edge(100),
887 )
888 .synchronous_sample()
889 .map(|sample| sample.output)
890 .find(|output| output.valid)
891 .unwrap()
892 .state
893 }
894
895 #[test]
896 fn challenge_hash_and_reduction_match_dalek() {
897 let seed = [0x31; 32];
898 let message = [0x79; 64];
899 let key = SigningKey::from_bytes(&seed);
900 let signature = key.sign(&message).to_bytes();
901 let request = VerifyRequest {
902 message: words512(message),
903 public_key: scalar_words(key.verifying_key().to_bytes()),
904 signature: words512(signature),
905 tag: b16(0),
906 };
907 let first = compress_block(sha_iv(), challenge_block0(request));
908 let second = compress_block(first, challenge_block1());
909 let wide = digest_words(second);
910 let idle = ScalarReducePipeIn {
911 valid: false,
912 wide: Words512 { words: [b64(0); 8] },
913 tag: b16(0),
914 };
915 let reduced = ScalarReducePipe::default()
916 .run(
917 iter::once(idle)
918 .chain(iter::once(ScalarReducePipeIn {
919 valid: true,
920 wide,
921 tag: b16(7),
922 }))
923 .chain(iter::repeat_n(idle, 100))
924 .with_reset(1)
925 .clock_pos_edge(100),
926 )
927 .synchronous_sample()
928 .map(|sample| sample.output)
929 .find(|output| output.valid)
930 .unwrap();
931 let mut hasher = Sha512::new();
932 hasher.update(&signature[..32]);
933 hasher.update(key.verifying_key().to_bytes());
934 hasher.update(message);
935 let digest: [u8; 64] = hasher.finalize().into();
936 let expected = Scalar::from_bytes_mod_order_wide(&digest).to_bytes();
937 let actual = std::array::from_fn(|index| {
938 ((reduced.scalar.words[index / 8].raw() >> ((index % 8) * 8)) & 0xff) as u8
939 });
940 assert_eq!(actual, expected);
941 }
942
943 #[test]
944 fn accepts_valid_and_rejects_modified_message() {
945 std::thread::Builder::new()
946 .name("strict-verify-sim".into())
947 .stack_size(512 * 1024 * 1024)
948 .spawn(|| {
949 let seed = std::array::from_fn(|index| (index as u8).wrapping_mul(19));
950 let message = std::array::from_fn(|index| (index as u8).wrapping_mul(37));
951 let key = SigningKey::from_bytes(&seed);
952 let signature = key.sign(&message).to_bytes();
953 let mut modified = message;
954 modified[17] ^= 0x80;
955 let valid = StrictVerifyIn {
956 valid: true,
957 request: VerifyRequest {
958 message: words512(message),
959 public_key: scalar_words(key.verifying_key().to_bytes()),
960 signature: words512(signature),
961 tag: b16(0x101),
962 },
963 };
964 let invalid = StrictVerifyIn {
965 valid: true,
966 request: VerifyRequest {
967 message: words512(modified),
968 tag: b16(0x102),
969 ..valid.request
970 },
971 };
972 let requests = [valid, invalid];
973 let mut need_reset = true;
974 let mut request_index = 0usize;
975 let mut result_count = 0usize;
976 let outputs: Vec<_> = StrictVerifyCore::default()
977 .run_fn(
978 move |output| {
979 if result_count == requests.len() {
980 return None;
981 }
982 if need_reset {
983 need_reset = false;
984 return Some(ResetOrData::Reset);
985 }
986 if output.valid {
987 result_count += 1;
988 }
989 let mut input = idle();
990 if request_index < requests.len() && output.ready {
991 input = requests[request_index];
992 request_index += 1;
993 }
994 Some(ResetOrData::Data(input))
995 },
996 100,
997 )
998 .take_while(|sample| sample.time < 4_000_000)
999 .synchronous_sample()
1000 .map(|sample| sample.output)
1001 .filter(|output| output.valid)
1002 .collect();
1003 assert_eq!(outputs.len(), 2);
1004 let accepted = outputs
1005 .iter()
1006 .find(|output| output.tag == b16(0x101))
1007 .unwrap();
1008 assert!(
1009 accepted.ok,
1010 "valid signature error={}",
1011 accepted.error.raw()
1012 );
1013 let rejected = outputs
1014 .iter()
1015 .find(|output| output.tag == b16(0x102))
1016 .unwrap();
1017 assert!(!rejected.ok);
1018 assert_eq!(rejected.error, b8(VERIFY_EQUATION_FAILURE));
1019 })
1020 .unwrap()
1021 .join()
1022 .unwrap();
1023 }
1024}