Skip to main content

wots_rhdl/verify/
tasks.rs

1//! Exact, message-dependent verification work accounting.
2//!
3//! Verification emits only mask, real chain, and public-key endpoint-hash
4//! tasks. A skipped chain position is a bypass and never appears in the task
5//! set. The baseline deliberately derives mask zero to match the current
6//! software oracle's sixteen-mask work contract, even though verification only
7//! consumes masks one through fifteen.
8
9use rhdl::prelude::*;
10
11use crate::{
12    digits::{CHAIN_COUNT, MessageBytes, MessageDigitArray, message_digits_kernel},
13    tag::{DecodedTaskTag, TagFields, TaskKind, encode_tag_kernel, tag_is_well_formed_kernel},
14};
15
16/// Mask PRFs in the frozen current-oracle verifier contract.
17pub const VERIFY_MASK_TASKS: usize = 16;
18/// Serialized SHA-256 blocks in the 2,144-byte endpoint hash.
19pub const VERIFY_PUBLIC_KEY_TASKS: usize = 34;
20/// Minimum real chain hashes over every possible 32-byte message digest.
21pub const VERIFY_MIN_CHAIN_TASKS: usize = 45;
22/// Maximum real chain hashes over every possible 32-byte message digest.
23pub const VERIFY_MAX_CHAIN_TASKS: usize = 990;
24/// Minimum complete SHA verifier task count.
25pub const VERIFY_MIN_SHA_TASKS: usize = 95;
26/// Maximum complete SHA verifier task count.
27pub const VERIFY_MAX_SHA_TASKS: usize = 1_040;
28
29const _: () = {
30    assert!(VERIFY_MIN_SHA_TASKS == VERIFY_MASK_TASKS + VERIFY_MIN_CHAIN_TASKS + 34);
31    assert!(VERIFY_MAX_SHA_TASKS == VERIFY_MASK_TASKS + VERIFY_MAX_CHAIN_TASKS + 34);
32};
33
34/// Count real chain hashes from already-derived WOTS digits.
35#[allow(clippy::assign_op_pattern, clippy::needless_range_loop)]
36#[kernel]
37pub fn verification_chain_count_kernel(digits: MessageDigitArray) -> b10 {
38    let mut count = b10(0);
39    for segment in 0..CHAIN_COUNT {
40        let digit: b10 = digits[segment].resize();
41        count = count + (b10(15) - digit);
42    }
43    count
44}
45
46/// Count exact SHA-256 compression tasks for one verifier operation.
47#[kernel]
48pub fn verification_sha_task_count_kernel(digits: MessageDigitArray) -> b11 {
49    let chain: b11 = verification_chain_count_kernel(digits).resize();
50    chain + b11((VERIFY_MASK_TASKS + VERIFY_PUBLIC_KEY_TASKS) as u128)
51}
52
53/// Derive digits and exact SHA task count from a 32-byte message digest.
54#[kernel]
55pub fn verification_message_task_count_kernel(message: MessageBytes) -> b11 {
56    verification_sha_task_count_kernel(message_digits_kernel(message))
57}
58
59/// Return whether a tag is canonical and belongs to the verifier task subset.
60#[kernel]
61pub fn verification_tag_is_well_formed_kernel(tag: b32) -> bool {
62    if tag_is_well_formed_kernel(tag) {
63        let kind: b3 = (tag >> 4).resize();
64        kind == b3(2) || kind == b3(6) || kind == b3(7)
65    } else {
66        false
67    }
68}
69
70/// Build the one canonical chain tag named by a verifier segment stage.
71///
72/// Stages 3 through 17 name chain steps 1 through 15. Callers never invoke
73/// this kernel for the endpoint sentinel stage 18.
74#[kernel]
75pub fn verification_chain_fields_kernel(
76    stage: b5,
77    segment: b7,
78    context: b4,
79    generation: b8,
80) -> TagFields {
81    TagFields {
82        context,
83        kind: b3(6),
84        segment,
85        chain_step: (stage - b5(2)).resize(),
86        block_index: b6(0),
87        generation,
88    }
89}
90
91/// Encode and validate one verifier-owned task tag.
92#[kernel]
93pub fn verification_fields_are_canonical_kernel(fields: TagFields) -> bool {
94    verification_tag_is_well_formed_kernel(encode_tag_kernel(fields))
95}
96
97/// Host-side exact chain count, rejecting digits outside base 16.
98///
99/// # Errors
100///
101/// Returns the first `(segment, digit)` whose digit exceeds fifteen.
102pub fn verification_chain_count(digits: &[u8; CHAIN_COUNT]) -> Result<usize, (usize, u8)> {
103    digits
104        .iter()
105        .copied()
106        .enumerate()
107        .try_fold(0_usize, |count, (segment, digit)| {
108            if digit > 15 {
109                Err((segment, digit))
110            } else {
111                Ok(count + usize::from(15 - digit))
112            }
113        })
114}
115
116/// Host-side exact SHA compression count for a valid message digit array.
117///
118/// # Errors
119///
120/// Returns the same invalid-digit diagnostic as [`verification_chain_count`].
121pub fn verification_sha_task_count(digits: &[u8; CHAIN_COUNT]) -> Result<usize, (usize, u8)> {
122    verification_chain_count(digits)
123        .map(|chain| VERIFY_MASK_TASKS + chain + VERIFY_PUBLIC_KEY_TASKS)
124}
125
126/// Construct the unordered, unique set of all real verifier tasks.
127///
128/// The returned order is deterministic for audit readability: masks, real
129/// chain transitions in segment/step order, then endpoint-hash blocks. A
130/// parallel engine may issue them in any dependency-correct order.
131///
132/// # Panics
133///
134/// Panics if `context` does not fit the four-bit tag field.
135#[must_use]
136pub fn canonical_verification_tasks(
137    context: u8,
138    generation: u8,
139    digits: &[u8; CHAIN_COUNT],
140) -> Vec<DecodedTaskTag> {
141    assert!(context < 16, "verification context must fit four bits");
142    let expected = verification_sha_task_count(digits).expect("message digits are base 16");
143    let mut tasks = Vec::with_capacity(expected);
144    for mask in 0_u8..16 {
145        tasks.push(
146            DecodedTaskTag::new(context, TaskKind::Mask, mask, 0, 0, generation)
147                .expect("mask tag is canonical"),
148        );
149    }
150    for (segment, digit) in digits.iter().copied().enumerate() {
151        for step in digit + 1..=15 {
152            tasks.push(
153                DecodedTaskTag::new(
154                    context,
155                    TaskKind::Chain,
156                    u8::try_from(segment).expect("67 segments fit u8"),
157                    step,
158                    0,
159                    generation,
160                )
161                .expect("chain tag is canonical"),
162            );
163        }
164    }
165    for block in 0_u8..34 {
166        tasks.push(
167            DecodedTaskTag::new(context, TaskKind::PublicKey, 0, 0, block, generation)
168                .expect("public-key tag is canonical"),
169        );
170    }
171    assert_eq!(tasks.len(), expected);
172    tasks
173}