1use crate::blake3_core::{
51 CHUNK_END, CHUNK_START, CompressionInput, CompressionOutput, IV, InlineCompressionPipeline, PARENT, ROOT,
52 SINGLE_BLOCK_ROOT,
53};
54use crate::chain_lane::le_words_kernel;
55use crate::verify::chains::{CONTEXTS, DigitsEvent, RowRequest};
56use crate::{CHAINS, MESSAGE_DIGITS};
57use rhdl::prelude::*;
58use rhdl_fpga::core::{
59 dff::DFF,
60 ram::synchronous::{In as RamIn, SyncBRAM, Write as RamWrite},
61};
62
63const STRAND_A: b2 = b2(0);
65const STRAND_C0: b2 = b2(1);
67const STRAND_C1: b2 = b2(2);
69const DIGEST_STEPS: usize = if crate::MESSAGE_BYTES == 64 { 2 } else { 1 };
73const TWO_BLOCK: bool = DIGEST_STEPS == 2;
75const LAST_DIGEST: b5 = b5((DIGEST_STEPS - 1) as u128);
77const FIRST_MERKLE: b5 = b5(DIGEST_STEPS as u128);
79const LAST_MERKLE: b5 = b5((DIGEST_STEPS + 6) as u128);
81const TAIL_ROW: b3 = b3(5);
83const A_DONE: b4 = b4((DIGEST_STEPS + 7) as u128);
85const C0_PARENT: b5 = b5(16);
87const C0_DONE: b5 = b5(17);
89const C1_DONE: b3 = b3(4);
91
92const IV_WORDS: [b32; 8] = iv_words();
94const FLAGS_SINGLE: b32 = b32(SINGLE_BLOCK_ROOT as u128);
96const FLAGS_START: b32 = b32(CHUNK_START as u128);
98const FLAGS_END: b32 = b32(CHUNK_END as u128);
100const FLAGS_END_ROOT: b32 = b32((CHUNK_END | ROOT) as u128);
102const FLAGS_PARENT: b32 = b32((PARENT | ROOT) as u128);
104
105const fn iv_words() -> [b32; 8] {
107 let mut words = [b32(0); 8];
108 let mut i = 0;
109 while i < 8 {
110 words[i] = b32(IV[i] as u128);
111 i += 1;
112 }
113 words
114}
115
116#[derive(Clone, Copy, Debug, PartialEq, Digital)]
118pub struct HeaderWrite {
119 pub enable: bool,
121 pub addr: b9,
123 pub value: [b8; 64],
125}
126
127impl Default for HeaderWrite {
128 fn default() -> Self {
130 Self {
131 enable: false,
132 addr: b9(0),
133 value: [b8(0); 64],
134 }
135 }
136}
137
138#[derive(Clone, Copy, Debug, PartialEq, Digital)]
140pub struct CheckWrite {
141 pub enable: bool,
143 pub ctx: b6,
145 pub value: [b8; 64],
147}
148
149impl Default for CheckWrite {
150 fn default() -> Self {
152 Self {
153 enable: false,
154 ctx: b6(0),
155 value: [b8(0); 64],
156 }
157 }
158}
159
160#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
162pub struct Loaded {
163 pub valid: bool,
165 pub ctx: b6,
167 pub index: b7,
170}
171
172#[derive(Clone, Copy, Debug, PartialEq, Digital)]
174pub struct HasherInput {
175 pub header: HeaderWrite,
177 pub check: CheckWrite,
179 pub loaded: Loaded,
181 pub chains_done: [bool; CONTEXTS],
183 pub row: [b8; 64],
185 pub oldest: b6,
187 pub retire_valid: bool,
189 pub retire_ctx: b6,
191}
192
193#[derive(Clone, Copy, Debug, PartialEq, Digital)]
195pub struct HasherOutput {
196 pub row: RowRequest,
198 pub digits: DigitsEvent,
200 pub done: [bool; CONTEXTS],
202 pub merkle_ok: [bool; CONTEXTS],
204 pub chains_ok: [bool; CONTEXTS],
206 pub issued: bool,
208}
209
210#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
212#[doc(hidden)]
213pub struct Progress {
214 loaded: bool,
216 index: b7,
218 a: b4,
220 a_busy: bool,
222 c0: b5,
224 c0_busy: bool,
226 c1: b3,
228 c1_busy: bool,
230 merkle_ok: bool,
232 chains_ok: bool,
234}
235
236#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
238#[doc(hidden)]
239pub struct Pick {
240 valid: bool,
242 ctx: b6,
244 strand: b2,
246 step: b5,
248 index: b7,
250}
251
252#[derive(Clone, Copy, Debug, PartialEq, Digital)]
254#[doc(hidden)]
255pub struct HasherControl {
256 progress: [Progress; CONTEXTS],
258 s1: Pick,
260 s2: Pick,
262 s3: Pick,
264 s4_valid: bool,
266 r1_valid: bool,
268 d_valid: bool,
270}
271
272impl Default for HasherControl {
273 fn default() -> Self {
275 Self {
276 progress: [Progress::default(); CONTEXTS],
277 s1: Pick::default(),
278 s2: Pick::default(),
279 s3: Pick::default(),
280 s4_valid: false,
281 r1_valid: false,
282 d_valid: false,
283 }
284 }
285}
286
287#[derive(Clone, Copy, Debug, PartialEq, Digital)]
289#[doc(hidden)]
290pub struct HasherData {
291 header: [b8; 64],
293 cv_m: [b32; 8],
295 cv_0: [b32; 8],
297 cv_1: [b32; 8],
299 input: CompressionInput,
301 result: CompressionOutput,
303 digits: [b2; CHAINS],
305 digits_ctx: b6,
307}
308
309#[derive(Clone, Debug, Synchronous, SynchronousDQ)]
311pub struct VerifyHasher {
312 lane: InlineCompressionPipeline,
314 header: SyncBRAM<[b8; 64], 9>,
316 check: SyncBRAM<[b8; 64], 6>,
318 cvs: [SyncBRAM<[b32; 8], 6>; 3],
320 control: DFF<HasherControl>,
322 data: DFF<HasherData>,
324}
325
326impl Default for VerifyHasher {
327 fn default() -> Self {
329 Self {
330 lane: InlineCompressionPipeline,
331 header: SyncBRAM::default(),
332 check: SyncBRAM::default(),
333 cvs: core::array::from_fn(|_| SyncBRAM::default()),
334 control: DFF::new(HasherControl::default()),
335 data: DFF::resetless(),
336 }
337 }
338}
339
340impl SynchronousIO for VerifyHasher {
341 type I = HasherInput;
342 type O = HasherOutput;
343 type Kernel = verify_hasher_kernel;
344}
345
346#[allow(clippy::needless_range_loop)]
352#[kernel]
353pub fn oldest_first_kernel(request: [bool; CONTEXTS], start: b6) -> (bool, b6) {
354 let mut late = [false; CONTEXTS];
355 for x in 0..CONTEXTS {
356 late[x] = request[x] && b6(x as u128) >= start;
357 }
358 let (late_found, late_index) = first_set_kernel(late);
359 let (any_found, any_index) = first_set_kernel(request);
360 if late_found {
361 (true, late_index)
362 } else {
363 (any_found, any_index)
364 }
365}
366
367#[allow(clippy::needless_range_loop)]
369#[kernel]
370pub fn first_set_kernel(bits: [bool; CONTEXTS]) -> (bool, b6) {
371 let mut group_any = [false; 8];
372 let mut group_low = [b3(0); 8];
373 for g in 0..8 {
374 let mut low = b3(0);
375 let mut any = false;
376 for i in 0..8 {
377 let x = 7 - i;
378 if bits[8 * g + x] {
379 low = b3(x as u128);
380 any = true;
381 }
382 }
383 group_any[g] = any;
384 group_low[g] = low;
385 }
386 let mut found = false;
387 let mut index = b6(0);
388 for i in 0..8 {
389 let g = 7 - i;
390 if group_any[g] {
391 found = true;
392 index = (b6(g as u128) << 3) | group_low[g].resize::<6>();
393 }
394 }
395 (found, index)
396}
397
398#[allow(clippy::needless_range_loop)]
401#[kernel]
402pub fn digits_kernel(words: [b32; 16]) -> [b2; CHAINS] {
403 let mut digits = [b2(0); CHAINS];
404 for w in 0..16 {
405 digits[4 * w] = words[w].resize();
406 digits[4 * w + 1] = (words[w] >> 8).resize();
407 digits[4 * w + 2] = (words[w] >> 16).resize();
408 digits[4 * w + 3] = (words[w] >> 24).resize();
409 }
410 let mut fours = [b4(0); 16];
412 for g in 0..16 {
413 fours[g] = digits[4 * g].resize::<4>()
414 + digits[4 * g + 1].resize::<4>()
415 + digits[4 * g + 2].resize::<4>()
416 + digits[4 * g + 3].resize::<4>();
417 }
418 let mut sixteens = [b6(0); 4];
419 for g in 0..4 {
420 sixteens[g] = fours[4 * g].resize::<6>()
421 + fours[4 * g + 1].resize::<6>()
422 + fours[4 * g + 2].resize::<6>()
423 + fours[4 * g + 3].resize::<6>();
424 }
425 let sum: b8 = sixteens[0].resize::<8>() + sixteens[1].resize::<8>() + sixteens[2].resize::<8>() + sixteens[3].resize::<8>();
426 let checksum: b8 = b8(192) - sum;
427 digits[MESSAGE_DIGITS] = checksum.resize();
428 digits[MESSAGE_DIGITS + 1] = (checksum >> 2).resize();
429 digits[MESSAGE_DIGITS + 2] = (checksum >> 4).resize();
430 digits[MESSAGE_DIGITS + 3] = (checksum >> 6).resize();
431 digits
432}
433
434#[kernel]
436pub fn index_bit_kernel(index: b7, level: b3) -> bool {
437 let bits = [
438 (index & b7(1)) != b7(0),
439 (index & b7(2)) != b7(0),
440 (index & b7(4)) != b7(0),
441 (index & b7(8)) != b7(0),
442 (index & b7(16)) != b7(0),
443 (index & b7(32)) != b7(0),
444 (index & b7(64)) != b7(0),
445 ];
446 let mut bit = false;
447 for l in 0..7 {
448 if level == b3(l as u128) {
449 bit = bits[l];
450 }
451 }
452 bit
453}
454
455#[allow(clippy::needless_range_loop, clippy::too_many_lines)]
457#[kernel]
458pub fn verify_hasher_kernel(
459 clock_reset: ClockReset,
460 input: HasherInput,
461 q: VerifyHasherQ,
462) -> (HasherOutput, VerifyHasherD) {
463 let resetting = clock_reset.reset.any();
467 let _ = resetting;
468 let mut c = q.control;
469 let mut data = q.data;
470
471 let mut want_a = [false; CONTEXTS];
474 let mut want_c0 = [false; CONTEXTS];
475 let mut want_c1 = [false; CONTEXTS];
476 let mut want = [false; CONTEXTS];
477 for x in 0..CONTEXTS {
478 let p = q.control.progress[x];
479 want_a[x] = p.loaded && !p.a_busy && p.a != A_DONE;
480 want_c1[x] = input.chains_done[x] && p.loaded && !p.c1_busy && p.c1 != C1_DONE;
481 want_c0[x] = input.chains_done[x]
482 && p.loaded
483 && !p.c0_busy
484 && p.c0 != C0_DONE
485 && (p.c0 != C0_PARENT || p.c1 == C1_DONE);
486 want[x] = want_a[x] || want_c0[x] || want_c1[x];
487 }
488 let mut want_digest = [false; CONTEXTS];
491 for x in 0..CONTEXTS {
492 want_digest[x] = want_a[x] && q.control.progress[x].a == b4(0);
493 }
494 let (digest_found, digest_ctx) = oldest_first_kernel(want_digest, input.oldest);
495 let (any_found, any_ctx) = oldest_first_kernel(want, input.oldest);
496 let found = digest_found || any_found;
497 let ctx = if digest_found { digest_ctx } else { any_ctx };
498 let mut pick = Pick {
499 valid: false,
500 ctx: b6(0),
501 strand: STRAND_A,
502 step: b5(0),
503 index: b7(0),
504 };
505 let mut ctx_progress = q.control.progress[0];
506 let mut pick_c0 = false;
507 let mut pick_c1 = false;
508 for x in 0..CONTEXTS {
509 if ctx == b6(x as u128) {
510 ctx_progress = q.control.progress[x];
511 pick_c0 = want_c0[x];
512 pick_c1 = want_c1[x];
513 }
514 }
515 if found {
516 pick.valid = true;
517 pick.ctx = ctx;
518 pick.index = ctx_progress.index;
519 if digest_found {
520 pick.strand = STRAND_A;
521 pick.step = b5(0);
522 } else if pick_c0 {
523 pick.strand = STRAND_C0;
524 pick.step = ctx_progress.c0;
525 } else if pick_c1 {
526 pick.strand = STRAND_C1;
527 pick.step = ctx_progress.c1.resize();
528 } else {
529 pick.strand = STRAND_A;
530 pick.step = ctx_progress.a.resize();
531 }
532 }
533
534 let s1 = q.control.s1;
537 let a_level: b5 = s1.step - FIRST_MERKLE;
538 let header_row: b3 = if s1.step == b5(0) {
539 b3(0)
540 } else if TWO_BLOCK && s1.step == b5(1) {
541 TAIL_ROW
542 } else {
543 b3(1) + ((a_level + b5(1)) >> 1).resize::<3>()
544 };
545 let header_read: b9 = (s1.ctx.resize::<9>() << 3) | header_row.resize::<9>();
546 let row = RowRequest {
547 valid: s1.valid && s1.strand != STRAND_A,
548 ctx: s1.ctx,
549 row: if s1.strand == STRAND_C1 { b5(16) + s1.step } else { s1.step },
550 };
551 c.s2 = s1;
552
553 data.header = q.header;
555 data.cv_m = q.cvs[0];
556 data.cv_0 = q.cvs[1];
557 data.cv_1 = q.cvs[2];
558 c.s3 = q.control.s2;
559
560 let s3 = q.control.s3;
562 let header_words = le_words_kernel(q.data.header);
563 let row_words = le_words_kernel(input.row);
564 let mut block = row_words;
565 let mut cv = IV_WORDS;
566 let mut counter_low = b32(0);
567 let mut block_len = b32(64);
568 let mut flags = b32(0);
569 if s3.strand == STRAND_A {
570 flags = FLAGS_SINGLE;
571 if s3.step == b5(0) {
572 block = header_words;
574 if TWO_BLOCK {
575 flags = FLAGS_START;
576 }
577 } else if TWO_BLOCK && s3.step == b5(1) {
578 block = header_words;
580 cv = q.data.cv_m;
581 block_len = b32(48);
582 flags = FLAGS_END_ROOT;
583 } else {
584 let level: b5 = s3.step - FIRST_MERKLE;
586 let mut sibling = [b32(0); 8];
587 let mut running = q.data.cv_m;
588 for w in 0..8 {
589 sibling[w] = if level == b5(0) || (level & b5(1)) == b5(1) { header_words[w] } else { header_words[w + 8] };
591 if level == b5(0) {
592 running[w] = header_words[w + 8];
593 }
594 }
595 let right = index_bit_kernel(s3.index, level.resize());
596 for w in 0..8 {
597 block[w] = if right { sibling[w] } else { running[w] };
598 block[w + 8] = if right { running[w] } else { sibling[w] };
599 }
600 }
601 } else if s3.strand == STRAND_C0 {
602 if s3.step == C0_PARENT {
603 for w in 0..8 {
604 block[w] = q.data.cv_0[w];
605 block[w + 8] = q.data.cv_1[w];
606 }
607 flags = FLAGS_PARENT;
608 } else {
609 if s3.step != b5(0) {
610 cv = q.data.cv_0;
611 } else {
612 flags = FLAGS_START;
613 }
614 if s3.step == b5(15) {
615 flags = flags | FLAGS_END;
616 }
617 }
618 } else {
619 counter_low = b32(1);
620 if s3.step != b5(0) {
621 cv = q.data.cv_1;
622 } else {
623 flags = FLAGS_START;
624 }
625 if s3.step == b5(3) {
626 flags = flags | FLAGS_END;
627 block_len = b32(24);
628 }
629 }
630 data.input = CompressionInput {
631 cv,
632 block,
633 counter_low,
634 counter_high: b32(0),
635 block_len,
636 flags,
637 tag: s3.ctx.resize::<32>() | (s3.strand.resize::<32>() << 6) | (s3.step.resize::<32>() << 8),
638 valid: true,
639 };
640 c.s4_valid = s3.valid;
641 let mut lane_in = q.data.input;
642 lane_in.valid = q.control.s4_valid;
643
644 let out = q.lane;
647 let out_ctx: b6 = out.tag.resize();
648 data.result = out;
649 c.r1_valid = out.valid;
650 let r1 = q.data.result;
652 let r1_ctx: b6 = r1.tag.resize();
653 let r1_strand: b2 = (r1.tag >> 6).resize();
654 let r1_step: b5 = (r1.tag >> 8).resize();
655 let mut cv_out = [b32(0); 8];
656 for w in 0..8 {
657 cv_out[w] = r1.words[w];
658 }
659 let check_words = le_words_kernel(q.check);
661 let mut matches_root = true;
662 let mut matches_pk = true;
663 for w in 0..8 {
664 matches_root = matches_root && cv_out[w] == check_words[w];
665 matches_pk = matches_pk && cv_out[w] == check_words[w + 8];
666 }
667 let r1_valid = q.control.r1_valid;
668 let write_m = r1_valid && r1_strand == STRAND_A && r1_step != LAST_DIGEST;
669 let write_0 = r1_valid && r1_strand == STRAND_C0;
670 let write_1 = r1_valid && r1_strand == STRAND_C1;
671 c.d_valid = r1_valid && r1_strand == STRAND_A && r1_step == LAST_DIGEST;
673 data.digits = digits_kernel(r1.words);
674 data.digits_ctx = r1_ctx;
675
676 let mut progress = q.control.progress;
678 for x in 0..CONTEXTS {
679 let mut p = q.control.progress[x];
680 let here = b6(x as u128);
681 if input.loaded.valid && input.loaded.ctx == here {
682 p.loaded = true;
683 p.index = input.loaded.index;
684 }
685 if found && ctx == here {
686 if digest_found {
687 p.a_busy = true;
688 } else if pick_c0 {
689 p.c0_busy = true;
690 } else if pick_c1 {
691 p.c1_busy = true;
692 } else {
693 p.a_busy = true;
694 }
695 }
696 if r1_valid && r1_ctx == here {
697 if r1_strand == STRAND_A {
698 p.a_busy = false;
699 p.a = p.a + b4(1);
700 if r1_step == LAST_MERKLE {
701 p.merkle_ok = matches_root;
702 }
703 } else if r1_strand == STRAND_C0 {
704 p.c0_busy = false;
705 p.c0 = p.c0 + b5(1);
706 if r1_step == C0_PARENT {
707 p.chains_ok = matches_pk;
708 }
709 } else {
710 p.c1_busy = false;
711 p.c1 = p.c1 + b3(1);
712 }
713 }
714 if input.retire_valid && input.retire_ctx == here {
715 p = Progress {
716 loaded: false,
717 index: b7(0),
718 a: b4(0),
719 a_busy: false,
720 c0: b5(0),
721 c0_busy: false,
722 c1: b3(0),
723 c1_busy: false,
724 merkle_ok: false,
725 chains_ok: false,
726 };
727 }
728 progress[x] = p;
729 }
730 c.progress = progress;
731 c.s1 = pick;
732
733 let mut done = [false; CONTEXTS];
734 let mut merkle_ok = [false; CONTEXTS];
735 let mut chains_ok = [false; CONTEXTS];
736 for x in 0..CONTEXTS {
737 let p = q.control.progress[x];
738 done[x] = p.a == A_DONE && p.c0 == C0_DONE;
739 merkle_ok[x] = p.merkle_ok;
740 chains_ok[x] = p.chains_ok;
741 }
742 let d = VerifyHasherD {
743 lane: lane_in,
744 header: RamIn::<[b8; 64], 9> {
745 read_addr: header_read,
746 write: RamWrite::<[b8; 64], 9> {
747 addr: input.header.addr,
748 value: input.header.value,
749 enable: input.header.enable,
750 },
751 },
752 check: RamIn::<[b8; 64], 6> {
753 read_addr: out_ctx,
754 write: RamWrite::<[b8; 64], 6> {
755 addr: input.check.ctx,
756 value: input.check.value,
757 enable: input.check.enable,
758 },
759 },
760 cvs: [
761 RamIn::<[b32; 8], 6> {
762 read_addr: s1.ctx,
763 write: RamWrite::<[b32; 8], 6> {
764 addr: r1_ctx,
765 value: cv_out,
766 enable: write_m,
767 },
768 },
769 RamIn::<[b32; 8], 6> {
770 read_addr: s1.ctx,
771 write: RamWrite::<[b32; 8], 6> {
772 addr: r1_ctx,
773 value: cv_out,
774 enable: write_0,
775 },
776 },
777 RamIn::<[b32; 8], 6> {
778 read_addr: s1.ctx,
779 write: RamWrite::<[b32; 8], 6> {
780 addr: r1_ctx,
781 value: cv_out,
782 enable: write_1,
783 },
784 },
785 ],
786 control: c,
787 data,
788 };
789 (
790 HasherOutput {
791 row,
792 digits: DigitsEvent {
793 valid: q.control.d_valid,
794 ctx: q.data.digits_ctx,
795 digits: q.data.digits,
796 },
797 done,
798 merkle_ok,
799 chains_ok,
800 issued: q.control.s4_valid,
801 },
802 d,
803 )
804}