1use serde::{Deserialize, Serialize};
40use std::fmt;
41
42#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
46#[repr(i8)]
47pub enum Trit {
48 N = -1,
50 #[default]
52 Z = 0,
53 P = 1,
55}
56
57impl fmt::Debug for Trit {
58 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59 match self {
60 Trit::N => write!(f, "N"),
61 Trit::Z => write!(f, "Z"),
62 Trit::P => write!(f, "P"),
63 }
64 }
65}
66
67impl fmt::Display for Trit {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 match self {
70 Trit::N => write!(f, "-"),
71 Trit::Z => write!(f, "0"),
72 Trit::P => write!(f, "+"),
73 }
74 }
75}
76
77impl Trit {
78 pub const ALL: [Trit; 3] = [Trit::N, Trit::Z, Trit::P];
80
81 #[inline]
83 pub const fn from_i8_clamped(v: i8) -> Self {
84 match v {
85 i8::MIN..=-1 => Trit::N,
86 0 => Trit::Z,
87 1..=i8::MAX => Trit::P,
88 }
89 }
90
91 #[inline]
93 pub const fn from_i8_exact(v: i8) -> Option<Self> {
94 match v {
95 -1 => Some(Trit::N),
96 0 => Some(Trit::Z),
97 1 => Some(Trit::P),
98 _ => None,
99 }
100 }
101
102 #[inline]
104 pub const fn to_i8(self) -> i8 {
105 self as i8
106 }
107
108 #[inline]
110 pub const fn neg(self) -> Trit {
111 match self {
112 Trit::N => Trit::P,
113 Trit::Z => Trit::Z,
114 Trit::P => Trit::N,
115 }
116 }
117
118 #[inline]
120 pub const fn abs(self) -> Trit {
121 match self {
122 Trit::N => Trit::P,
123 Trit::Z => Trit::Z,
124 Trit::P => Trit::P,
125 }
126 }
127
128 #[inline]
130 pub const fn sign(self) -> i8 {
131 self as i8
132 }
133
134 #[inline]
136 pub const fn is_zero(self) -> bool {
137 matches!(self, Trit::Z)
138 }
139
140 #[inline]
142 pub const fn is_nonzero(self) -> bool {
143 !self.is_zero()
144 }
145
146 #[inline]
159 pub const fn mul(self, other: Trit) -> Trit {
160 match (self, other) {
161 (Trit::Z, _) | (_, Trit::Z) => Trit::Z,
162 (Trit::P, Trit::P) | (Trit::N, Trit::N) => Trit::P,
163 (Trit::P, Trit::N) | (Trit::N, Trit::P) => Trit::N,
164 }
165 }
166
167 #[inline]
175 pub const fn add_with_carry(self, other: Trit, carry_in: Trit) -> (Trit, Trit) {
176 let sum = self.to_i8() + other.to_i8() + carry_in.to_i8();
177 match sum {
178 -3 => (Trit::Z, Trit::N), -2 => (Trit::P, Trit::N), -1 => (Trit::N, Trit::Z), 0 => (Trit::Z, Trit::Z), 1 => (Trit::P, Trit::Z), 2 => (Trit::N, Trit::P), 3 => (Trit::Z, Trit::P), _ => unreachable!(),
186 }
187 }
188
189 #[inline]
192 pub const fn add_saturating(self, other: Trit) -> Trit {
193 let sum = self.to_i8() + other.to_i8();
194 Trit::from_i8_clamped(sum)
195 }
196
197 #[inline]
199 pub const fn majority3(a: Trit, b: Trit, c: Trit) -> Trit {
200 let sum = a.to_i8() + b.to_i8() + c.to_i8();
201 Trit::from_i8_clamped(sum)
202 }
203
204 #[inline]
207 pub const fn from_bits(b1: bool, b0: bool) -> Option<Trit> {
208 match (b1, b0) {
209 (false, false) => Some(Trit::Z),
210 (false, true) => Some(Trit::P),
211 (true, false) => Some(Trit::N),
212 (true, true) => None, }
214 }
215
216 #[inline]
219 pub const fn to_bits(self) -> (bool, bool) {
220 match self {
221 Trit::Z => (false, false),
222 Trit::P => (false, true),
223 Trit::N => (true, false),
224 }
225 }
226}
227
228impl std::ops::Neg for Trit {
229 type Output = Trit;
230 #[inline]
231 fn neg(self) -> Trit {
232 Trit::neg(self)
233 }
234}
235
236impl std::ops::Mul for Trit {
237 type Output = Trit;
238 #[inline]
239 fn mul(self, rhs: Trit) -> Trit {
240 Trit::mul(self, rhs)
241 }
242}
243
244impl std::ops::MulAssign for Trit {
245 #[inline]
246 fn mul_assign(&mut self, rhs: Trit) {
247 *self = *self * rhs;
248 }
249}
250
251#[derive(Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
258pub struct Tryte3 {
259 pub trits: [Trit; 3],
261}
262
263impl fmt::Debug for Tryte3 {
264 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
265 write!(
266 f,
267 "Tryte3[{}{}{} = {}]",
268 self.trits[2],
269 self.trits[1],
270 self.trits[0],
271 self.to_i8()
272 )
273 }
274}
275
276impl fmt::Display for Tryte3 {
277 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
278 write!(f, "{}{}{}", self.trits[2], self.trits[1], self.trits[0])
279 }
280}
281
282impl Tryte3 {
283 pub const ZERO: Tryte3 = Tryte3 {
285 trits: [Trit::Z, Trit::Z, Trit::Z],
286 };
287
288 pub const MAX: Tryte3 = Tryte3 {
290 trits: [Trit::P, Trit::P, Trit::P],
291 };
292
293 pub const MIN: Tryte3 = Tryte3 {
295 trits: [Trit::N, Trit::N, Trit::N],
296 };
297
298 pub const MAX_VALUE: i8 = 13;
300
301 pub const MIN_VALUE: i8 = -13;
303
304 pub const NUM_STATES: u8 = 27;
306
307 #[inline]
309 pub const fn new(t0: Trit, t1: Trit, t2: Trit) -> Self {
310 Tryte3 {
311 trits: [t0, t1, t2],
312 }
313 }
314
315 pub const fn from_i8(mut value: i8) -> Option<Self> {
317 if value < Self::MIN_VALUE || value > Self::MAX_VALUE {
318 return None;
319 }
320
321 let negative = value < 0;
322 if negative {
323 value = -value;
324 }
325
326 let mut trits = [Trit::Z; 3];
327 let mut i = 0;
328
329 while i < 3 && value != 0 {
330 let remainder = value % 3;
331 value /= 3;
332
333 trits[i] = match remainder {
334 0 => Trit::Z,
335 1 => Trit::P,
336 2 => {
337 value += 1; Trit::N
339 }
340 _ => return None, };
342 i += 1;
343 }
344
345 if negative {
346 trits[0] = trits[0].neg();
347 trits[1] = trits[1].neg();
348 trits[2] = trits[2].neg();
349 }
350
351 Some(Tryte3 { trits })
352 }
353
354 #[inline]
356 pub const fn to_i8(self) -> i8 {
357 self.trits[0].to_i8() + 3 * self.trits[1].to_i8() + 9 * self.trits[2].to_i8()
358 }
359
360 #[inline]
362 pub const fn neg(self) -> Self {
363 Tryte3 {
364 trits: [
365 self.trits[0].neg(),
366 self.trits[1].neg(),
367 self.trits[2].neg(),
368 ],
369 }
370 }
371
372 #[inline]
374 pub const fn mul(self, other: Tryte3) -> Tryte3 {
375 Tryte3 {
376 trits: [
377 self.trits[0].mul(other.trits[0]),
378 self.trits[1].mul(other.trits[1]),
379 self.trits[2].mul(other.trits[2]),
380 ],
381 }
382 }
383
384 #[inline]
386 pub const fn bundle(self, other: Tryte3) -> Tryte3 {
387 Tryte3 {
388 trits: [
389 self.trits[0].add_saturating(other.trits[0]),
390 self.trits[1].add_saturating(other.trits[1]),
391 self.trits[2].add_saturating(other.trits[2]),
392 ],
393 }
394 }
395
396 pub const fn add_with_carry(self, other: Tryte3, carry_in: Trit) -> (Tryte3, Trit) {
398 let (t0, c0) = self.trits[0].add_with_carry(other.trits[0], carry_in);
399 let (t1, c1) = self.trits[1].add_with_carry(other.trits[1], c0);
400 let (t2, c2) = self.trits[2].add_with_carry(other.trits[2], c1);
401
402 (
403 Tryte3 {
404 trits: [t0, t1, t2],
405 },
406 c2,
407 )
408 }
409
410 #[inline]
412 pub const fn dot(self, other: Tryte3) -> i8 {
413 self.trits[0].to_i8() * other.trits[0].to_i8()
414 + self.trits[1].to_i8() * other.trits[1].to_i8()
415 + self.trits[2].to_i8() * other.trits[2].to_i8()
416 }
417
418 #[inline]
420 pub const fn nnz(self) -> u8 {
421 self.trits[0].is_nonzero() as u8
422 + self.trits[1].is_nonzero() as u8
423 + self.trits[2].is_nonzero() as u8
424 }
425
426 #[inline]
429 pub const fn pack(self) -> u8 {
430 let t0 = (self.trits[0].to_i8() + 1) as u8; let t1 = (self.trits[1].to_i8() + 1) as u8;
433 let t2 = (self.trits[2].to_i8() + 1) as u8;
434 t0 + 3 * t1 + 9 * t2
435 }
436
437 #[inline]
439 pub const fn unpack(byte: u8) -> Option<Self> {
440 if byte >= 27 {
441 return None;
442 }
443
444 let t0 = (byte % 3) as i8 - 1;
445 let t1 = ((byte / 3) % 3) as i8 - 1;
446 let t2 = (byte / 9) as i8 - 1;
447
448 Some(Tryte3 {
449 trits: [
450 Trit::from_i8_clamped(t0),
451 Trit::from_i8_clamped(t1),
452 Trit::from_i8_clamped(t2),
453 ],
454 })
455 }
456}
457
458impl std::ops::Neg for Tryte3 {
459 type Output = Tryte3;
460 fn neg(self) -> Tryte3 {
461 Tryte3::neg(self)
462 }
463}
464
465impl std::ops::Mul for Tryte3 {
466 type Output = Tryte3;
467 fn mul(self, rhs: Tryte3) -> Tryte3 {
468 Tryte3::mul(self, rhs)
469 }
470}
471
472#[derive(Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
479pub struct Word6 {
480 pub low: Tryte3,
482 pub high: Tryte3,
483}
484
485impl fmt::Debug for Word6 {
486 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
487 write!(f, "Word6[{}{} = {}]", self.high, self.low, self.to_i16())
488 }
489}
490
491impl Word6 {
492 pub const ZERO: Word6 = Word6 {
494 low: Tryte3::ZERO,
495 high: Tryte3::ZERO,
496 };
497
498 pub const MAX_VALUE: i16 = 364;
500
501 pub const MIN_VALUE: i16 = -364;
503
504 pub const NUM_STATES: u16 = 729;
506
507 pub fn from_i16(value: i16) -> Option<Self> {
509 if !(Self::MIN_VALUE..=Self::MAX_VALUE).contains(&value) {
510 return None;
511 }
512
513 let mut v = value;
518 let negative = v < 0;
519 if negative {
520 v = -v;
521 }
522
523 let low_val = balanced_mod(v, 27);
525 let high_val = balanced_div(v, 27);
526
527 let low = Tryte3::from_i8(if negative { -low_val } else { low_val })?;
528 let high = Tryte3::from_i8(if negative { -high_val } else { high_val })?;
529
530 Some(Word6 { low, high })
531 }
532
533 #[inline]
535 pub fn to_i16(self) -> i16 {
536 self.low.to_i8() as i16 + 27 * self.high.to_i8() as i16
537 }
538
539 #[allow(clippy::should_implement_trait)]
541 pub fn mul(self, other: Word6) -> Word6 {
542 Word6 {
543 low: self.low.mul(other.low),
544 high: self.high.mul(other.high),
545 }
546 }
547
548 pub fn bundle(self, other: Word6) -> Word6 {
550 Word6 {
551 low: self.low.bundle(other.low),
552 high: self.high.bundle(other.high),
553 }
554 }
555
556 pub fn pack(self) -> u16 {
558 self.low.pack() as u16 + 27 * self.high.pack() as u16
559 }
560
561 pub fn unpack(bits: u16) -> Option<Self> {
563 if bits >= 729 {
564 return None;
565 }
566 let low = Tryte3::unpack((bits % 27) as u8)?;
567 let high = Tryte3::unpack((bits / 27) as u8)?;
568 Some(Word6 { low, high })
569 }
570}
571
572const fn balanced_mod(value: i16, n: i16) -> i8 {
574 let r = value % n;
575 if r > n / 2 {
576 (r - n) as i8
577 } else if r < -(n / 2) {
578 (r + n) as i8
579 } else {
580 r as i8
581 }
582}
583
584const fn balanced_div(value: i16, n: i16) -> i8 {
586 let r = balanced_mod(value, n);
587 ((value - r as i16) / n) as i8
588}
589
590#[derive(Clone, Debug, Serialize, Deserialize)]
596pub struct CorrectionEntry {
597 pub position: u64,
599 pub exact_value: Vec<u8>,
601 pub verification_hash: [u8; 8],
603}
604
605#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
609pub struct ParityTrit(pub Trit);
610
611impl ParityTrit {
612 pub fn compute(trits: &[Trit]) -> Self {
614 let sum: i32 = trits.iter().map(|t| t.to_i8() as i32).sum();
615 let parity = sum.rem_euclid(3);
616 ParityTrit(match parity {
617 0 => Trit::Z,
618 1 => Trit::P,
619 2 => Trit::N,
620 _ => unreachable!(),
621 })
622 }
623
624 pub fn verify(&self, trits: &[Trit]) -> bool {
626 Self::compute(trits) == *self
627 }
628}
629
630#[cfg(test)]
631mod tests {
632 use super::*;
633
634 #[test]
637 fn test_trit_multiplication_truth_table() {
638 assert_eq!(Trit::N * Trit::N, Trit::P, "N × N = P");
640 assert_eq!(Trit::N * Trit::Z, Trit::Z, "N × Z = Z");
641 assert_eq!(Trit::N * Trit::P, Trit::N, "N × P = N");
642 assert_eq!(Trit::Z * Trit::N, Trit::Z, "Z × N = Z");
643 assert_eq!(Trit::Z * Trit::Z, Trit::Z, "Z × Z = Z");
644 assert_eq!(Trit::Z * Trit::P, Trit::Z, "Z × P = Z");
645 assert_eq!(Trit::P * Trit::N, Trit::N, "P × N = N");
646 assert_eq!(Trit::P * Trit::Z, Trit::Z, "P × Z = Z");
647 assert_eq!(Trit::P * Trit::P, Trit::P, "P × P = P");
648 }
649
650 #[test]
651 fn test_trit_self_inverse() {
652 assert_eq!(Trit::P * Trit::P, Trit::P, "P is self-inverse");
654 assert_eq!(Trit::N * Trit::N, Trit::P, "N is self-inverse");
655 }
656
657 #[test]
658 fn test_trit_multiplication_commutative() {
659 for &a in &Trit::ALL {
660 for &b in &Trit::ALL {
661 assert_eq!(a * b, b * a, "Commutativity: {:?} × {:?}", a, b);
662 }
663 }
664 }
665
666 #[test]
667 fn test_trit_multiplication_associative() {
668 for &a in &Trit::ALL {
669 for &b in &Trit::ALL {
670 for &c in &Trit::ALL {
671 assert_eq!(
672 (a * b) * c,
673 a * (b * c),
674 "Associativity: ({:?} × {:?}) × {:?}",
675 a,
676 b,
677 c
678 );
679 }
680 }
681 }
682 }
683
684 #[test]
685 fn test_trit_negation() {
686 assert_eq!(-Trit::N, Trit::P);
687 assert_eq!(-Trit::Z, Trit::Z);
688 assert_eq!(-Trit::P, Trit::N);
689
690 for &t in &Trit::ALL {
692 assert_eq!(-(-t), t, "Double negation of {:?}", t);
693 }
694 }
695
696 #[test]
697 fn test_trit_add_with_carry_complete() {
698 #[allow(clippy::type_complexity)]
700 let expected: [((Trit, Trit, Trit), (Trit, Trit)); 27] = [
701 ((Trit::N, Trit::N, Trit::N), (Trit::Z, Trit::N)), ((Trit::N, Trit::Z, Trit::N), (Trit::P, Trit::N)), ((Trit::N, Trit::P, Trit::N), (Trit::N, Trit::Z)), ((Trit::Z, Trit::N, Trit::N), (Trit::P, Trit::N)), ((Trit::Z, Trit::Z, Trit::N), (Trit::N, Trit::Z)), ((Trit::Z, Trit::P, Trit::N), (Trit::Z, Trit::Z)), ((Trit::P, Trit::N, Trit::N), (Trit::N, Trit::Z)), ((Trit::P, Trit::Z, Trit::N), (Trit::Z, Trit::Z)), ((Trit::P, Trit::P, Trit::N), (Trit::P, Trit::Z)), ((Trit::N, Trit::N, Trit::Z), (Trit::P, Trit::N)), ((Trit::N, Trit::Z, Trit::Z), (Trit::N, Trit::Z)), ((Trit::N, Trit::P, Trit::Z), (Trit::Z, Trit::Z)), ((Trit::Z, Trit::N, Trit::Z), (Trit::N, Trit::Z)), ((Trit::Z, Trit::Z, Trit::Z), (Trit::Z, Trit::Z)), ((Trit::Z, Trit::P, Trit::Z), (Trit::P, Trit::Z)), ((Trit::P, Trit::N, Trit::Z), (Trit::Z, Trit::Z)), ((Trit::P, Trit::Z, Trit::Z), (Trit::P, Trit::Z)), ((Trit::P, Trit::P, Trit::Z), (Trit::N, Trit::P)), ((Trit::N, Trit::N, Trit::P), (Trit::N, Trit::Z)), ((Trit::N, Trit::Z, Trit::P), (Trit::Z, Trit::Z)), ((Trit::N, Trit::P, Trit::P), (Trit::P, Trit::Z)), ((Trit::Z, Trit::N, Trit::P), (Trit::Z, Trit::Z)), ((Trit::Z, Trit::Z, Trit::P), (Trit::P, Trit::Z)), ((Trit::Z, Trit::P, Trit::P), (Trit::N, Trit::P)), ((Trit::P, Trit::N, Trit::P), (Trit::P, Trit::Z)), ((Trit::P, Trit::Z, Trit::P), (Trit::N, Trit::P)), ((Trit::P, Trit::P, Trit::P), (Trit::Z, Trit::P)), ];
732
733 for ((a, b, c), (expected_sum, expected_carry)) in expected {
734 let (sum, carry) = a.add_with_carry(b, c);
735 assert_eq!(
736 (sum, carry),
737 (expected_sum, expected_carry),
738 "add_with_carry({:?}, {:?}, {:?})",
739 a,
740 b,
741 c
742 );
743 }
744 }
745
746 #[test]
749 fn test_tryte3_roundtrip() {
750 for v in Tryte3::MIN_VALUE..=Tryte3::MAX_VALUE {
751 let tryte =
752 Tryte3::from_i8(v).unwrap_or_else(|| panic!("Should create tryte for {}", v));
753 let decoded = tryte.to_i8();
754 assert_eq!(v, decoded, "Roundtrip failed for {}", v);
755 }
756 }
757
758 #[test]
759 fn test_tryte3_pack_unpack() {
760 for packed in 0..27u8 {
761 let tryte = Tryte3::unpack(packed).expect("Tryte3 unpack must succeed for values 0-26");
762 let repacked = tryte.pack();
763 assert_eq!(packed, repacked, "Pack/unpack failed for {}", packed);
764 }
765 }
766
767 #[test]
768 fn test_tryte3_bind_self_inverse() {
769 for v in Tryte3::MIN_VALUE..=Tryte3::MAX_VALUE {
770 let tryte =
772 Tryte3::from_i8(v).expect("Tryte3::from_i8 must succeed for values in valid range");
773 let bound = tryte * tryte;
774
775 for i in 0..3 {
777 if tryte.trits[i].is_nonzero() {
778 assert_eq!(
779 bound.trits[i],
780 Trit::P,
781 "Self-bind trit {} should be P for value {}",
782 i,
783 v
784 );
785 }
786 }
787 }
788 }
789
790 #[test]
793 fn test_word6_roundtrip() {
794 let test_values = [0, 1, -1, 13, -13, 100, -100, 364, -364];
795 for &v in &test_values {
796 let word = Word6::from_i16(v).unwrap_or_else(|| panic!("Should create word for {}", v));
797 let decoded = word.to_i16();
798 assert_eq!(v, decoded, "Roundtrip failed for {}", v);
799 }
800 }
801
802 #[test]
803 fn test_word6_pack_unpack() {
804 for packed in (0..729u16).step_by(7) {
805 let word = Word6::unpack(packed).expect("Word6 unpack must succeed for values 0-728");
807 let repacked = word.pack();
808 assert_eq!(packed, repacked, "Pack/unpack failed for {}", packed);
809 }
810 }
811
812 #[test]
815 fn test_parity_detection() {
816 let trits = vec![Trit::P, Trit::N, Trit::P, Trit::Z, Trit::N];
817 let parity = ParityTrit::compute(&trits);
818 assert!(parity.verify(&trits), "Parity should verify");
819
820 let mut corrupted = trits.clone();
822 corrupted[0] = Trit::N;
823 assert!(
824 !parity.verify(&corrupted),
825 "Parity should fail on corrupted data"
826 );
827 }
828}