Skip to main content

wots_rhdl/
tasks.rs

1//! Auditable task model for the serial single-context SHA-profile baseline.
2//!
3//! This host-side model is intentionally independent of the serial RHDL
4//! sequencer's state machine. Tests compare that controller's retired tags
5//! against this canonical total order, making missing, duplicated, stale, or
6//! reordered work visible rather than inferring correctness from a final digest
7//! alone. A parallel scheduler may use a different legal interleaving while
8//! preserving data dependencies and the same exact per-class counts.
9
10use core::fmt;
11
12use crate::tag::{DecodedTaskTag, TaskKind, decode_task_tag};
13
14/// Private-seed hash compression blocks.
15pub const SEED_TASKS: usize = 1;
16/// Public-seed PRF compression blocks.
17pub const PUBLIC_SEED_TASKS: usize = 1;
18/// Observable mask PRF compression blocks.
19pub const MASK_TASKS: usize = 16;
20/// Per-segment secret PRF compression blocks.
21pub const SEGMENT_PRF_TASKS: usize = 67;
22/// First secret-preimage compression blocks.
23pub const SECRET_DATA_TASKS: usize = 67;
24/// Second secret-preimage padding blocks.
25pub const SECRET_PADDING_TASKS: usize = 67;
26/// Fixed 15-step chain compression blocks across all segments.
27pub const CHAIN_TASKS: usize = 1_005;
28/// Public-key endpoint-stream compression blocks.
29pub const PUBLIC_KEY_TASKS: usize = 34;
30/// Complete compression-block count for fused signing from a private seed.
31pub const TOTAL_TASKS: usize = SEED_TASKS
32    + PUBLIC_SEED_TASKS
33    + MASK_TASKS
34    + SEGMENT_PRF_TASKS
35    + SECRET_DATA_TASKS
36    + SECRET_PADDING_TASKS
37    + CHAIN_TASKS
38    + PUBLIC_KEY_TASKS;
39
40/// Per-class counts observed in a task trace.
41#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
42pub struct TaskCounts {
43    /// Private-seed tasks.
44    pub seed: u16,
45    /// Public-seed tasks.
46    pub public_seed: u16,
47    /// Mask tasks.
48    pub mask: u16,
49    /// Per-segment PRF tasks.
50    pub segment_prf: u16,
51    /// Secret-preimage data blocks.
52    pub secret_data: u16,
53    /// Secret-preimage padding blocks.
54    pub secret_padding: u16,
55    /// Chain transitions.
56    pub chain: u16,
57    /// Public-key endpoint blocks.
58    pub public_key: u16,
59}
60
61impl TaskCounts {
62    /// Required class counts for one complete fused private-seed operation.
63    pub const EXPECTED: Self = Self {
64        seed: 1,
65        public_seed: 1,
66        mask: 16,
67        segment_prf: 67,
68        secret_data: 67,
69        secret_padding: 67,
70        chain: 1_005,
71        public_key: 34,
72    };
73
74    /// Return the sum of all classes.
75    #[must_use]
76    pub const fn total(self) -> u16 {
77        self.seed
78            + self.public_seed
79            + self.mask
80            + self.segment_prf
81            + self.secret_data
82            + self.secret_padding
83            + self.chain
84            + self.public_key
85    }
86
87    /// Record one canonical decoded task.
88    pub fn record(&mut self, kind: TaskKind) {
89        let counter = match kind {
90            TaskKind::Seed => &mut self.seed,
91            TaskKind::PublicSeed => &mut self.public_seed,
92            TaskKind::Mask => &mut self.mask,
93            TaskKind::SegmentPrf => &mut self.segment_prf,
94            TaskKind::SecretData => &mut self.secret_data,
95            TaskKind::SecretPadding => &mut self.secret_padding,
96            TaskKind::Chain => &mut self.chain,
97            TaskKind::PublicKey => &mut self.public_key,
98        };
99        *counter = counter.saturating_add(1);
100    }
101}
102
103/// Successfully audited trace metadata.
104#[derive(Clone, Copy, Debug, Eq, PartialEq)]
105pub struct TaskAudit {
106    /// Exact class counts observed in the trace.
107    pub counts: TaskCounts,
108    /// Context shared by every task.
109    pub context: u8,
110    /// Generation shared by every task.
111    pub generation: u8,
112}
113
114/// A precise task-trace validation failure.
115#[derive(Clone, Copy, Debug, Eq, PartialEq)]
116pub enum TaskTraceError {
117    /// A tag's class-specific fields were not canonical.
118    Malformed {
119        /// Trace offset.
120        index: usize,
121        /// Rejected raw tag.
122        raw: u32,
123    },
124    /// A response came from an earlier or future context generation.
125    StaleGeneration {
126        /// Trace offset.
127        index: usize,
128        /// Required generation.
129        expected: u8,
130        /// Observed generation.
131        observed: u8,
132    },
133    /// A response was routed to the wrong scheduler context.
134    WrongContext {
135        /// Trace offset.
136        index: usize,
137        /// Required context.
138        expected: u8,
139        /// Observed context.
140        observed: u8,
141    },
142    /// A canonical task appeared at the wrong serial-baseline position.
143    UnexpectedTask {
144        /// Trace offset.
145        index: usize,
146        /// Required task.
147        expected: DecodedTaskTag,
148        /// Observed task.
149        observed: DecodedTaskTag,
150    },
151    /// The trace ended before all 1,258 tasks retired.
152    Truncated {
153        /// Required trace length.
154        expected: usize,
155        /// Observed trace length.
156        observed: usize,
157    },
158    /// The trace continued after all required tasks retired.
159    Extra {
160        /// First unexpected trace offset.
161        index: usize,
162        /// First unexpected raw tag.
163        raw: u32,
164    },
165    /// Class counts did not equal the protocol constants.
166    CountMismatch {
167        /// Required counts.
168        expected: TaskCounts,
169        /// Observed counts.
170        observed: TaskCounts,
171    },
172}
173
174impl fmt::Display for TaskTraceError {
175    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
176        match self {
177            Self::Malformed { index, raw } => {
178                write!(
179                    formatter,
180                    "malformed task tag {raw:#010x} at trace index {index}"
181                )
182            }
183            Self::StaleGeneration {
184                index,
185                expected,
186                observed,
187            } => write!(
188                formatter,
189                "generation {observed} at trace index {index}, expected {expected}"
190            ),
191            Self::WrongContext {
192                index,
193                expected,
194                observed,
195            } => write!(
196                formatter,
197                "context {observed} at trace index {index}, expected {expected}"
198            ),
199            Self::UnexpectedTask { index, .. } => {
200                write!(
201                    formatter,
202                    "task dependency-order mismatch at trace index {index}"
203                )
204            }
205            Self::Truncated { expected, observed } => {
206                write!(
207                    formatter,
208                    "truncated task trace: {observed} entries, expected {expected}"
209                )
210            }
211            Self::Extra { index, raw } => {
212                write!(
213                    formatter,
214                    "extra task tag {raw:#010x} at trace index {index}"
215                )
216            }
217            Self::CountMismatch { expected, observed } => write!(
218                formatter,
219                "task-class count mismatch: observed {observed:?}, expected {expected:?}"
220            ),
221        }
222    }
223}
224
225impl std::error::Error for TaskTraceError {}
226
227fn canonical_tag(
228    context: u8,
229    generation: u8,
230    kind: TaskKind,
231    segment: u8,
232    chain_step: u8,
233    block_index: u8,
234) -> DecodedTaskTag {
235    DecodedTaskTag::new(context, kind, segment, chain_step, block_index, generation)
236        .expect("the canonical task builder uses protocol bounds")
237}
238
239/// Build the serial baseline's 1,258-task trace for one context generation.
240///
241/// Segment and mask tag indices are zero based. A segment PRF builder converts
242/// segment `s` to the protocol PRF index `s + 1`; chain steps remain one based.
243///
244/// # Panics
245///
246/// Panics if `context` does not fit the four-bit tag field.
247#[must_use]
248pub fn canonical_task_trace(context: u8, generation: u8) -> Vec<DecodedTaskTag> {
249    assert!(
250        context < 16,
251        "a scheduler context must fit the four-bit tag field"
252    );
253    let mut tasks = Vec::with_capacity(TOTAL_TASKS);
254    tasks.push(canonical_tag(context, generation, TaskKind::Seed, 0, 0, 0));
255    tasks.push(canonical_tag(
256        context,
257        generation,
258        TaskKind::PublicSeed,
259        0,
260        0,
261        0,
262    ));
263    for mask in 0_u8..16 {
264        tasks.push(canonical_tag(
265            context,
266            generation,
267            TaskKind::Mask,
268            mask,
269            0,
270            0,
271        ));
272    }
273    for segment in 0_u8..67 {
274        tasks.push(canonical_tag(
275            context,
276            generation,
277            TaskKind::SegmentPrf,
278            segment,
279            0,
280            0,
281        ));
282        tasks.push(canonical_tag(
283            context,
284            generation,
285            TaskKind::SecretData,
286            segment,
287            0,
288            0,
289        ));
290        tasks.push(canonical_tag(
291            context,
292            generation,
293            TaskKind::SecretPadding,
294            segment,
295            0,
296            1,
297        ));
298        for step in 1_u8..16 {
299            tasks.push(canonical_tag(
300                context,
301                generation,
302                TaskKind::Chain,
303                segment,
304                step,
305                0,
306            ));
307        }
308    }
309    for block in 0_u8..34 {
310        tasks.push(canonical_tag(
311            context,
312            generation,
313            TaskKind::PublicKey,
314            0,
315            0,
316            block,
317        ));
318    }
319    assert_eq!(tasks.len(), TOTAL_TASKS);
320    tasks
321}
322
323/// Validate canonical encoding, context/generation routing, total serial order,
324/// exact length, and exact per-class counts for a baseline-sequencer trace.
325///
326/// This auditor is a focused oracle for the historical/foundational serial
327/// sequencer. It is not a validator for the active
328/// [`crate::top::Sha256SignerTop`], whose three
329/// [`crate::cluster::LaneLocalCluster`] instances each schedule four contexts
330/// and may legally interleave independent work.
331///
332/// # Errors
333///
334/// Returns [`TaskTraceError`] at the first malformed, stale, misrouted,
335/// reordered, missing, extra, or miscounted task.
336///
337/// # Panics
338///
339/// Panics if `context` does not fit the four-bit tag field.
340pub fn audit_task_trace(
341    raw_tasks: &[u32],
342    context: u8,
343    generation: u8,
344) -> Result<TaskAudit, TaskTraceError> {
345    let expected = canonical_task_trace(context, generation);
346    let common = raw_tasks.len().min(expected.len());
347    let mut counts = TaskCounts::default();
348    for index in 0..common {
349        let raw = raw_tasks[index];
350        let observed =
351            decode_task_tag(raw).map_err(|_| TaskTraceError::Malformed { index, raw })?;
352        if observed.generation != generation {
353            return Err(TaskTraceError::StaleGeneration {
354                index,
355                expected: generation,
356                observed: observed.generation,
357            });
358        }
359        if observed.context != context {
360            return Err(TaskTraceError::WrongContext {
361                index,
362                expected: context,
363                observed: observed.context,
364            });
365        }
366        if observed != expected[index] {
367            return Err(TaskTraceError::UnexpectedTask {
368                index,
369                expected: expected[index],
370                observed,
371            });
372        }
373        counts.record(observed.kind);
374    }
375    if raw_tasks.len() < expected.len() {
376        return Err(TaskTraceError::Truncated {
377            expected: expected.len(),
378            observed: raw_tasks.len(),
379        });
380    }
381    if raw_tasks.len() > expected.len() {
382        return Err(TaskTraceError::Extra {
383            index: expected.len(),
384            raw: raw_tasks[expected.len()],
385        });
386    }
387    if counts != TaskCounts::EXPECTED {
388        return Err(TaskTraceError::CountMismatch {
389            expected: TaskCounts::EXPECTED,
390            observed: counts,
391        });
392    }
393    Ok(TaskAudit {
394        counts,
395        context,
396        generation,
397    })
398}
399
400const _: () = {
401    assert!(CHAIN_TASKS == 67 * 15);
402    assert!(TOTAL_TASKS == 1_258);
403    assert!(TaskCounts::EXPECTED.total() == 1_258_u16);
404};