1use prism::pipeline::{output_shape, ConstraintRef};
30
31use crate::hash::{label_bytes, AddrHash};
32use prism::crypto::Sha256Hasher;
33
34pub const ADDRESS_LABEL_BYTES: usize = label_bytes(Sha256Hasher::LABEL_PREFIX, 32);
37
38#[must_use]
41pub const fn site_constraints<const N: usize>() -> [ConstraintRef; N] {
42 let mut sites = [ConstraintRef::Site { position: 0 }; N];
43 let mut i = 0;
44 while i < N {
45 sites[i] = ConstraintRef::Site { position: i as u32 };
46 i += 1;
47 }
48 sites
49}
50
51static SHA256_SITES: [ConstraintRef; 71] = site_constraints::<71>();
56output_shape! {
57 pub struct AddressLabelSha256;
58 impl ConstrainedTypeShape for AddressLabelSha256 {
59 const IRI: &'static str = "https://uor.foundation/addr/AddressLabel/sha256";
60 const SITE_COUNT: usize = 71;
61 const CONSTRAINTS: &'static [ConstraintRef] = &SHA256_SITES;
62 }
63}
64
65static BLAKE3_SITES: [ConstraintRef; 71] = site_constraints::<71>();
66output_shape! {
67 pub struct AddressLabelBlake3;
68 impl ConstrainedTypeShape for AddressLabelBlake3 {
69 const IRI: &'static str = "https://uor.foundation/addr/AddressLabel/blake3";
70 const SITE_COUNT: usize = 71;
71 const CONSTRAINTS: &'static [ConstraintRef] = &BLAKE3_SITES;
72 }
73}
74
75static SHA3_256_SITES: [ConstraintRef; 73] = site_constraints::<73>();
76output_shape! {
77 pub struct AddressLabelSha3_256;
78 impl ConstrainedTypeShape for AddressLabelSha3_256 {
79 const IRI: &'static str = "https://uor.foundation/addr/AddressLabel/sha3-256";
80 const SITE_COUNT: usize = 73;
81 const CONSTRAINTS: &'static [ConstraintRef] = &SHA3_256_SITES;
82 }
83}
84
85static KECCAK256_SITES: [ConstraintRef; 74] = site_constraints::<74>();
86output_shape! {
87 pub struct AddressLabelKeccak256;
88 impl ConstrainedTypeShape for AddressLabelKeccak256 {
89 const IRI: &'static str = "https://uor.foundation/addr/AddressLabel/keccak256";
90 const SITE_COUNT: usize = 74;
91 const CONSTRAINTS: &'static [ConstraintRef] = &KECCAK256_SITES;
92 }
93}
94
95static SHA512_SITES: [ConstraintRef; 135] = site_constraints::<135>();
96output_shape! {
97 pub struct AddressLabelSha512;
98 impl ConstrainedTypeShape for AddressLabelSha512 {
99 const IRI: &'static str = "https://uor.foundation/addr/AddressLabel/sha512";
100 const SITE_COUNT: usize = 135;
101 const CONSTRAINTS: &'static [ConstraintRef] = &SHA512_SITES;
102 }
103}
104
105pub type AddressLabel = AddressLabelSha256;
109
110output_shape! {
115 pub struct CompositionLabelG2Sha256;
116 impl ConstrainedTypeShape for CompositionLabelG2Sha256 {
117 const IRI: &'static str = "https://uor.foundation/addr/composition/g2-product/sha256";
118 const SITE_COUNT: usize = 71;
119 const CONSTRAINTS: &'static [ConstraintRef] = &SHA256_SITES;
120 }
121}
122
123output_shape! {
124 pub struct CompositionLabelG2Blake3;
125 impl ConstrainedTypeShape for CompositionLabelG2Blake3 {
126 const IRI: &'static str = "https://uor.foundation/addr/composition/g2-product/blake3";
127 const SITE_COUNT: usize = 71;
128 const CONSTRAINTS: &'static [ConstraintRef] = &BLAKE3_SITES;
129 }
130}
131
132output_shape! {
133 pub struct CompositionLabelG2Sha3_256;
134 impl ConstrainedTypeShape for CompositionLabelG2Sha3_256 {
135 const IRI: &'static str = "https://uor.foundation/addr/composition/g2-product/sha3-256";
136 const SITE_COUNT: usize = 73;
137 const CONSTRAINTS: &'static [ConstraintRef] = &SHA3_256_SITES;
138 }
139}
140
141output_shape! {
142 pub struct CompositionLabelG2Keccak256;
143 impl ConstrainedTypeShape for CompositionLabelG2Keccak256 {
144 const IRI: &'static str = "https://uor.foundation/addr/composition/g2-product/keccak256";
145 const SITE_COUNT: usize = 74;
146 const CONSTRAINTS: &'static [ConstraintRef] = &KECCAK256_SITES;
147 }
148}
149
150output_shape! {
151 pub struct CompositionLabelG2Sha512;
152 impl ConstrainedTypeShape for CompositionLabelG2Sha512 {
153 const IRI: &'static str = "https://uor.foundation/addr/composition/g2-product/sha512";
154 const SITE_COUNT: usize = 135;
155 const CONSTRAINTS: &'static [ConstraintRef] = &SHA512_SITES;
156 }
157}
158
159output_shape! {
160 pub struct CompositionLabelF4Sha256;
161 impl ConstrainedTypeShape for CompositionLabelF4Sha256 {
162 const IRI: &'static str = "https://uor.foundation/addr/composition/f4-quotient/sha256";
163 const SITE_COUNT: usize = 71;
164 const CONSTRAINTS: &'static [ConstraintRef] = &SHA256_SITES;
165 }
166}
167
168output_shape! {
169 pub struct CompositionLabelF4Blake3;
170 impl ConstrainedTypeShape for CompositionLabelF4Blake3 {
171 const IRI: &'static str = "https://uor.foundation/addr/composition/f4-quotient/blake3";
172 const SITE_COUNT: usize = 71;
173 const CONSTRAINTS: &'static [ConstraintRef] = &BLAKE3_SITES;
174 }
175}
176
177output_shape! {
178 pub struct CompositionLabelF4Sha3_256;
179 impl ConstrainedTypeShape for CompositionLabelF4Sha3_256 {
180 const IRI: &'static str = "https://uor.foundation/addr/composition/f4-quotient/sha3-256";
181 const SITE_COUNT: usize = 73;
182 const CONSTRAINTS: &'static [ConstraintRef] = &SHA3_256_SITES;
183 }
184}
185
186output_shape! {
187 pub struct CompositionLabelF4Keccak256;
188 impl ConstrainedTypeShape for CompositionLabelF4Keccak256 {
189 const IRI: &'static str = "https://uor.foundation/addr/composition/f4-quotient/keccak256";
190 const SITE_COUNT: usize = 74;
191 const CONSTRAINTS: &'static [ConstraintRef] = &KECCAK256_SITES;
192 }
193}
194
195output_shape! {
196 pub struct CompositionLabelF4Sha512;
197 impl ConstrainedTypeShape for CompositionLabelF4Sha512 {
198 const IRI: &'static str = "https://uor.foundation/addr/composition/f4-quotient/sha512";
199 const SITE_COUNT: usize = 135;
200 const CONSTRAINTS: &'static [ConstraintRef] = &SHA512_SITES;
201 }
202}
203
204output_shape! {
205 pub struct CompositionLabelE6Sha256;
206 impl ConstrainedTypeShape for CompositionLabelE6Sha256 {
207 const IRI: &'static str = "https://uor.foundation/addr/composition/e6-filtration/sha256";
208 const SITE_COUNT: usize = 71;
209 const CONSTRAINTS: &'static [ConstraintRef] = &SHA256_SITES;
210 }
211}
212
213output_shape! {
214 pub struct CompositionLabelE6Blake3;
215 impl ConstrainedTypeShape for CompositionLabelE6Blake3 {
216 const IRI: &'static str = "https://uor.foundation/addr/composition/e6-filtration/blake3";
217 const SITE_COUNT: usize = 71;
218 const CONSTRAINTS: &'static [ConstraintRef] = &BLAKE3_SITES;
219 }
220}
221
222output_shape! {
223 pub struct CompositionLabelE6Sha3_256;
224 impl ConstrainedTypeShape for CompositionLabelE6Sha3_256 {
225 const IRI: &'static str = "https://uor.foundation/addr/composition/e6-filtration/sha3-256";
226 const SITE_COUNT: usize = 73;
227 const CONSTRAINTS: &'static [ConstraintRef] = &SHA3_256_SITES;
228 }
229}
230
231output_shape! {
232 pub struct CompositionLabelE6Keccak256;
233 impl ConstrainedTypeShape for CompositionLabelE6Keccak256 {
234 const IRI: &'static str = "https://uor.foundation/addr/composition/e6-filtration/keccak256";
235 const SITE_COUNT: usize = 74;
236 const CONSTRAINTS: &'static [ConstraintRef] = &KECCAK256_SITES;
237 }
238}
239
240output_shape! {
241 pub struct CompositionLabelE6Sha512;
242 impl ConstrainedTypeShape for CompositionLabelE6Sha512 {
243 const IRI: &'static str = "https://uor.foundation/addr/composition/e6-filtration/sha512";
244 const SITE_COUNT: usize = 135;
245 const CONSTRAINTS: &'static [ConstraintRef] = &SHA512_SITES;
246 }
247}
248
249output_shape! {
250 pub struct CompositionLabelE7Sha256;
251 impl ConstrainedTypeShape for CompositionLabelE7Sha256 {
252 const IRI: &'static str = "https://uor.foundation/addr/composition/e7-augmentation/sha256";
253 const SITE_COUNT: usize = 71;
254 const CONSTRAINTS: &'static [ConstraintRef] = &SHA256_SITES;
255 }
256}
257
258output_shape! {
259 pub struct CompositionLabelE7Blake3;
260 impl ConstrainedTypeShape for CompositionLabelE7Blake3 {
261 const IRI: &'static str = "https://uor.foundation/addr/composition/e7-augmentation/blake3";
262 const SITE_COUNT: usize = 71;
263 const CONSTRAINTS: &'static [ConstraintRef] = &BLAKE3_SITES;
264 }
265}
266
267output_shape! {
268 pub struct CompositionLabelE7Sha3_256;
269 impl ConstrainedTypeShape for CompositionLabelE7Sha3_256 {
270 const IRI: &'static str = "https://uor.foundation/addr/composition/e7-augmentation/sha3-256";
271 const SITE_COUNT: usize = 73;
272 const CONSTRAINTS: &'static [ConstraintRef] = &SHA3_256_SITES;
273 }
274}
275
276output_shape! {
277 pub struct CompositionLabelE7Keccak256;
278 impl ConstrainedTypeShape for CompositionLabelE7Keccak256 {
279 const IRI: &'static str = "https://uor.foundation/addr/composition/e7-augmentation/keccak256";
280 const SITE_COUNT: usize = 74;
281 const CONSTRAINTS: &'static [ConstraintRef] = &KECCAK256_SITES;
282 }
283}
284
285output_shape! {
286 pub struct CompositionLabelE7Sha512;
287 impl ConstrainedTypeShape for CompositionLabelE7Sha512 {
288 const IRI: &'static str = "https://uor.foundation/addr/composition/e7-augmentation/sha512";
289 const SITE_COUNT: usize = 135;
290 const CONSTRAINTS: &'static [ConstraintRef] = &SHA512_SITES;
291 }
292}
293
294output_shape! {
295 pub struct CompositionLabelE8Sha256;
296 impl ConstrainedTypeShape for CompositionLabelE8Sha256 {
297 const IRI: &'static str = "https://uor.foundation/addr/composition/e8-embedding/sha256";
298 const SITE_COUNT: usize = 71;
299 const CONSTRAINTS: &'static [ConstraintRef] = &SHA256_SITES;
300 }
301}
302
303output_shape! {
304 pub struct CompositionLabelE8Blake3;
305 impl ConstrainedTypeShape for CompositionLabelE8Blake3 {
306 const IRI: &'static str = "https://uor.foundation/addr/composition/e8-embedding/blake3";
307 const SITE_COUNT: usize = 71;
308 const CONSTRAINTS: &'static [ConstraintRef] = &BLAKE3_SITES;
309 }
310}
311
312output_shape! {
313 pub struct CompositionLabelE8Sha3_256;
314 impl ConstrainedTypeShape for CompositionLabelE8Sha3_256 {
315 const IRI: &'static str = "https://uor.foundation/addr/composition/e8-embedding/sha3-256";
316 const SITE_COUNT: usize = 73;
317 const CONSTRAINTS: &'static [ConstraintRef] = &SHA3_256_SITES;
318 }
319}
320
321output_shape! {
322 pub struct CompositionLabelE8Keccak256;
323 impl ConstrainedTypeShape for CompositionLabelE8Keccak256 {
324 const IRI: &'static str = "https://uor.foundation/addr/composition/e8-embedding/keccak256";
325 const SITE_COUNT: usize = 74;
326 const CONSTRAINTS: &'static [ConstraintRef] = &KECCAK256_SITES;
327 }
328}
329
330output_shape! {
331 pub struct CompositionLabelE8Sha512;
332 impl ConstrainedTypeShape for CompositionLabelE8Sha512 {
333 const IRI: &'static str = "https://uor.foundation/addr/composition/e8-embedding/sha512";
334 const SITE_COUNT: usize = 135;
335 const CONSTRAINTS: &'static [ConstraintRef] = &SHA512_SITES;
336 }
337}
338
339#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
354pub struct KappaLabel<const N: usize>([u8; N]);
355
356impl<const N: usize> KappaLabel<N> {
357 pub fn from_bytes(bytes: &[u8]) -> Result<Self, LabelDecodeError> {
371 if bytes.len() != N {
372 return Err(LabelDecodeError::WrongLength);
373 }
374 let mut buf = [0u8; N];
375 for (dst, &src) in buf.iter_mut().zip(bytes.iter()) {
376 if !src.is_ascii() {
377 return Err(LabelDecodeError::NonAscii);
378 }
379 *dst = src;
380 }
381 Ok(Self(buf))
382 }
383
384 #[must_use]
386 pub fn as_array(&self) -> &[u8; N] {
387 &self.0
388 }
389
390 #[must_use]
393 pub fn as_str(&self) -> &str {
394 core::str::from_utf8(&self.0).expect("KappaLabel is ASCII by construction")
395 }
396
397 #[must_use]
399 pub fn as_bytes(&self) -> &[u8] {
400 &self.0
401 }
402
403 #[must_use]
408 pub fn sigma_axis(&self) -> Option<&str> {
409 self.as_str().split_once(':').map(|(axis, _)| axis)
410 }
411
412 #[must_use]
415 pub fn sigma_axis_digest_hex(&self) -> Option<&str> {
416 self.as_str().split_once(':').map(|(_, hex)| hex)
417 }
418}
419
420impl<const N: usize> core::ops::Deref for KappaLabel<N> {
421 type Target = str;
422 fn deref(&self) -> &str {
423 self.as_str()
424 }
425}
426
427impl<const N: usize> AsRef<str> for KappaLabel<N> {
428 fn as_ref(&self) -> &str {
429 self.as_str()
430 }
431}
432
433impl<const N: usize> AsRef<[u8]> for KappaLabel<N> {
434 fn as_ref(&self) -> &[u8] {
435 &self.0
436 }
437}
438
439impl<const N: usize> core::fmt::Display for KappaLabel<N> {
440 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
441 f.write_str(self.as_str())
442 }
443}
444
445impl<const N: usize> core::fmt::Debug for KappaLabel<N> {
446 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
447 f.debug_tuple("KappaLabel").field(&self.as_str()).finish()
448 }
449}
450
451impl<const N: usize> PartialEq<str> for KappaLabel<N> {
452 fn eq(&self, other: &str) -> bool {
453 self.as_str() == other
454 }
455}
456
457impl<const N: usize> PartialEq<&str> for KappaLabel<N> {
458 fn eq(&self, other: &&str) -> bool {
459 self.as_str() == *other
460 }
461}
462
463impl<const N: usize> PartialEq<KappaLabel<N>> for str {
464 fn eq(&self, other: &KappaLabel<N>) -> bool {
465 self == other.as_str()
466 }
467}
468
469impl<const N: usize> PartialEq<KappaLabel<N>> for &str {
470 fn eq(&self, other: &KappaLabel<N>) -> bool {
471 *self == other.as_str()
472 }
473}
474
475#[derive(Debug, Clone, Copy, PartialEq, Eq)]
477pub enum LabelDecodeError {
478 WrongLength,
480 NonAscii,
482}
483
484#[cfg(test)]
485mod tests {
486 use super::*;
487 use prism::crypto::{Blake3Hasher, Keccak256Hasher, Sha3_256Hasher};
488 use prism::pipeline::ConstrainedTypeShape;
489
490 #[test]
491 fn site_count_matches_wire_format_byte_width_per_axis() {
492 assert_eq!(
493 <AddressLabelSha256 as ConstrainedTypeShape>::SITE_COUNT,
494 Sha256Hasher::LABEL_BYTES
495 );
496 assert_eq!(
497 <AddressLabelBlake3 as ConstrainedTypeShape>::SITE_COUNT,
498 Blake3Hasher::LABEL_BYTES
499 );
500 assert_eq!(
501 <AddressLabelSha3_256 as ConstrainedTypeShape>::SITE_COUNT,
502 Sha3_256Hasher::LABEL_BYTES
503 );
504 assert_eq!(
505 <AddressLabelKeccak256 as ConstrainedTypeShape>::SITE_COUNT,
506 Keccak256Hasher::LABEL_BYTES
507 );
508 }
509
510 #[test]
511 fn iri_carries_axis_suffix_per_architecture() {
512 assert_eq!(
513 <AddressLabelSha256 as ConstrainedTypeShape>::IRI,
514 "https://uor.foundation/addr/AddressLabel/sha256"
515 );
516 assert_eq!(
517 <AddressLabelKeccak256 as ConstrainedTypeShape>::IRI,
518 "https://uor.foundation/addr/AddressLabel/keccak256"
519 );
520 }
521
522 #[test]
523 fn each_shape_carries_disjoint_site_constraints() {
524 let cs = <AddressLabelSha3_256 as ConstrainedTypeShape>::CONSTRAINTS;
525 assert_eq!(cs.len(), 73);
526 for (i, c) in cs.iter().enumerate() {
527 match c {
528 ConstraintRef::Site { position } => assert_eq!(*position, i as u32),
529 _ => panic!("expected Site constraint at index {i}"),
530 }
531 }
532 }
533
534 #[test]
535 fn kappa_label_from_bytes_round_trips_valid_input() {
536 let bytes = b"sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
537 let label = KappaLabel::<71>::from_bytes(bytes).expect("valid");
538 assert_eq!(label.as_str(), core::str::from_utf8(bytes).unwrap());
539 assert_eq!(label.as_bytes(), bytes);
540 assert_eq!(label.as_array(), bytes);
541 assert!(label.starts_with("sha256:"));
542 assert_eq!(label.len(), ADDRESS_LABEL_BYTES);
543 }
544
545 #[test]
546 fn kappa_label_rejects_wrong_length() {
547 let err = KappaLabel::<71>::from_bytes(b"too short").expect_err("rejects");
548 assert_eq!(err, LabelDecodeError::WrongLength);
549 }
550
551 #[test]
552 fn kappa_label_rejects_non_ascii_byte() {
553 let mut bytes = [b'a'; 71];
554 bytes[3] = 0x80;
555 let err = KappaLabel::<71>::from_bytes(&bytes).expect_err("rejects");
556 assert_eq!(err, LabelDecodeError::NonAscii);
557 }
558
559 #[test]
560 fn kappa_label_equality_against_str() {
561 let bytes = b"sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
562 let label = KappaLabel::<71>::from_bytes(bytes).expect("valid");
563 let s: &str = core::str::from_utf8(bytes).unwrap();
564 assert_eq!(label, *s);
565 assert_eq!(label, s);
566 assert_eq!(s, label);
567 }
568}