1use core::fmt;
16
17use rhdl::prelude::*;
18
19pub const TASK_TAG_BITS: usize = 32;
21
22pub const CONTEXT_SHIFT: u32 = 0;
24pub const KIND_SHIFT: u32 = 4;
26pub const SEGMENT_SHIFT: u32 = 7;
28pub const CHAIN_STEP_SHIFT: u32 = 14;
30pub const BLOCK_INDEX_SHIFT: u32 = 18;
32pub const GENERATION_SHIFT: u32 = 24;
34
35#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
37#[repr(u8)]
38pub enum TaskKind {
39 Seed = 0,
41 PublicSeed = 1,
43 Mask = 2,
45 SegmentPrf = 3,
47 SecretData = 4,
49 SecretPadding = 5,
51 Chain = 6,
53 PublicKey = 7,
55}
56
57impl TaskKind {
58 #[must_use]
60 pub const fn code(self) -> u8 {
61 self as u8
62 }
63
64 #[must_use]
66 pub const fn from_code(code: u8) -> Option<Self> {
67 match code {
68 0 => Some(Self::Seed),
69 1 => Some(Self::PublicSeed),
70 2 => Some(Self::Mask),
71 3 => Some(Self::SegmentPrf),
72 4 => Some(Self::SecretData),
73 5 => Some(Self::SecretPadding),
74 6 => Some(Self::Chain),
75 7 => Some(Self::PublicKey),
76 _ => None,
77 }
78 }
79}
80
81#[derive(Clone, Copy, Debug, Default, Digital, Eq, PartialEq)]
83pub struct TagFields {
84 pub context: b4,
86 pub kind: b3,
88 pub segment: b7,
90 pub chain_step: b4,
92 pub block_index: b6,
94 pub generation: b8,
96}
97
98#[kernel]
100pub fn encode_tag_kernel(fields: TagFields) -> b32 {
101 let context: b32 = fields.context.resize();
102 let kind: b32 = fields.kind.resize();
103 let segment: b32 = fields.segment.resize();
104 let chain_step: b32 = fields.chain_step.resize();
105 let block_index: b32 = fields.block_index.resize();
106 let generation: b32 = fields.generation.resize();
107 context
108 | (kind << 4)
109 | (segment << 7)
110 | (chain_step << 14)
111 | (block_index << 18)
112 | (generation << 24)
113}
114
115#[kernel]
117pub fn decode_tag_kernel(tag: b32) -> TagFields {
118 TagFields {
119 context: tag.resize(),
120 kind: (tag >> 4).resize(),
121 segment: (tag >> 7).resize(),
122 chain_step: (tag >> 14).resize(),
123 block_index: (tag >> 18).resize(),
124 generation: (tag >> 24).resize(),
125 }
126}
127
128#[kernel]
133#[allow(clippy::match_same_arms)] pub fn tag_is_well_formed_kernel(tag: b32) -> bool {
135 let fields = decode_tag_kernel(tag);
136 let no_segment = fields.segment == b7(0);
137 let no_step = fields.chain_step == b4(0);
138 let no_block = fields.block_index == b6(0);
139 match fields.kind {
140 Bits::<3>(0) => no_segment && no_step && no_block,
141 Bits::<3>(1) => no_segment && no_step && no_block,
142 Bits::<3>(2) => fields.segment < b7(16) && no_step && no_block,
143 Bits::<3>(3) => fields.segment < b7(67) && no_step && no_block,
144 Bits::<3>(4) => fields.segment < b7(67) && no_step && no_block,
145 Bits::<3>(5) => fields.segment < b7(67) && no_step && fields.block_index == b6(1),
146 Bits::<3>(6) => {
147 fields.segment < b7(67)
148 && fields.chain_step > b4(0)
149 && fields.chain_step <= b4(15)
150 && no_block
151 }
152 _ => no_segment && no_step && fields.block_index < b6(34),
153 }
154}
155
156#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
158pub struct DecodedTaskTag {
159 pub context: u8,
161 pub kind: TaskKind,
163 pub segment: u8,
165 pub chain_step: u8,
167 pub block_index: u8,
169 pub generation: u8,
171}
172
173impl DecodedTaskTag {
174 pub fn new(
181 context: u8,
182 kind: TaskKind,
183 segment: u8,
184 chain_step: u8,
185 block_index: u8,
186 generation: u8,
187 ) -> Result<Self, TaskTagError> {
188 let candidate = Self {
189 context,
190 kind,
191 segment,
192 chain_step,
193 block_index,
194 generation,
195 };
196 if context >= 16 || !candidate.is_well_formed() {
197 return Err(TaskTagError::NonCanonical(candidate.encode_unchecked()));
198 }
199 Ok(candidate)
200 }
201
202 #[must_use]
204 pub fn encode(self) -> u32 {
205 self.encode_unchecked()
206 }
207
208 #[must_use]
210 pub const fn is_well_formed(self) -> bool {
211 let no_segment = self.segment == 0;
212 let no_step = self.chain_step == 0;
213 let no_block = self.block_index == 0;
214 match self.kind {
215 TaskKind::Seed | TaskKind::PublicSeed => no_segment && no_step && no_block,
216 TaskKind::Mask => self.segment < 16 && no_step && no_block,
217 TaskKind::SegmentPrf | TaskKind::SecretData => self.segment < 67 && no_step && no_block,
218 TaskKind::SecretPadding => self.segment < 67 && no_step && self.block_index == 1,
219 TaskKind::Chain => {
220 self.segment < 67 && self.chain_step > 0 && self.chain_step < 16 && no_block
221 }
222 TaskKind::PublicKey => no_segment && no_step && self.block_index < 34,
223 }
224 }
225
226 fn encode_unchecked(self) -> u32 {
227 u32::from(self.context)
228 | u32::from(self.kind.code()) << KIND_SHIFT
229 | u32::from(self.segment) << SEGMENT_SHIFT
230 | u32::from(self.chain_step) << CHAIN_STEP_SHIFT
231 | u32::from(self.block_index) << BLOCK_INDEX_SHIFT
232 | u32::from(self.generation) << GENERATION_SHIFT
233 }
234}
235
236#[derive(Clone, Copy, Debug, Eq, PartialEq)]
238pub enum TaskTagError {
239 NonCanonical(u32),
241}
242
243impl fmt::Display for TaskTagError {
244 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
245 match self {
246 Self::NonCanonical(raw) => write!(formatter, "noncanonical WOTS task tag {raw:#010x}"),
247 }
248 }
249}
250
251impl std::error::Error for TaskTagError {}
252
253pub fn decode_task_tag(raw: u32) -> Result<DecodedTaskTag, TaskTagError> {
259 let kind_code = ((raw >> KIND_SHIFT) & 0x07).to_le_bytes()[0];
260 let decoded = DecodedTaskTag {
261 context: (raw & 0x0f).to_le_bytes()[0],
262 kind: match kind_code {
263 0 => TaskKind::Seed,
264 1 => TaskKind::PublicSeed,
265 2 => TaskKind::Mask,
266 3 => TaskKind::SegmentPrf,
267 4 => TaskKind::SecretData,
268 5 => TaskKind::SecretPadding,
269 6 => TaskKind::Chain,
270 _ => TaskKind::PublicKey,
271 },
272 segment: ((raw >> SEGMENT_SHIFT) & 0x7f).to_le_bytes()[0],
273 chain_step: ((raw >> CHAIN_STEP_SHIFT) & 0x0f).to_le_bytes()[0],
274 block_index: ((raw >> BLOCK_INDEX_SHIFT) & 0x3f).to_le_bytes()[0],
275 generation: (raw >> GENERATION_SHIFT).to_le_bytes()[0],
276 };
277 if decoded.is_well_formed() {
278 Ok(decoded)
279 } else {
280 Err(TaskTagError::NonCanonical(raw))
281 }
282}
283
284#[must_use]
286pub fn software_fields(fields: TagFields) -> DecodedTaskTag {
287 let kind = fields.kind.raw().to_le_bytes()[0];
288 DecodedTaskTag {
289 context: fields.context.raw().to_le_bytes()[0],
290 kind: match kind {
291 0 => TaskKind::Seed,
292 1 => TaskKind::PublicSeed,
293 2 => TaskKind::Mask,
294 3 => TaskKind::SegmentPrf,
295 4 => TaskKind::SecretData,
296 5 => TaskKind::SecretPadding,
297 6 => TaskKind::Chain,
298 _ => TaskKind::PublicKey,
299 },
300 segment: fields.segment.raw().to_le_bytes()[0],
301 chain_step: fields.chain_step.raw().to_le_bytes()[0],
302 block_index: fields.block_index.raw().to_le_bytes()[0],
303 generation: fields.generation.raw().to_le_bytes()[0],
304 }
305}
306
307#[must_use]
309pub fn hardware_fields(fields: DecodedTaskTag) -> TagFields {
310 TagFields {
311 context: b4(u128::from(fields.context)),
312 kind: b3(u128::from(fields.kind.code())),
313 segment: b7(u128::from(fields.segment)),
314 chain_step: b4(u128::from(fields.chain_step)),
315 block_index: b6(u128::from(fields.block_index)),
316 generation: b8(u128::from(fields.generation)),
317 }
318}
319
320const _: () = {
321 assert!(CONTEXT_SHIFT == 0);
322 assert!(GENERATION_SHIFT + 8 == 32);
323};