1 package crypto
2 3 import (
4 "git.mleku.dev/mleku/dendrite/pkg/epoch"
5 "git.mleku.dev/mleku/dendrite/pkg/permutation"
6 "git.mleku.dev/mleku/dendrite/pkg/state"
7 )
8 9 // PhasePerms returns the S_3 permutation pair (binPerm, decPerm) for a
10 // given token index within an epoch's phase schedule.
11 //
12 // binPerm is selected by |Phase.Num| mod 6 -- the binary clock's
13 // fractional position determines the inner trigram rotation.
14 // decPerm is selected by Phase.Denom mod 6 -- the decimal clock's
15 // normalization factor determines the outer trigram rotation.
16 //
17 // At epoch boundaries (Phase = 0/1), both permutations are Identity
18 // because 0 mod 6 = 0 and 1 mod 6 = 1 (Identity and Swap01).
19 // This is intentional: epoch boundaries are synchronization points
20 // where the shadow channel collapses to zero.
21 func PhasePerms(ep epoch.Epoch, index int) (binPerm, decPerm permutation.Perm) {
22 phase := ep.Phase(int64(index + 1))
23 num := phase.Num
24 if num < 0 {
25 num = -num
26 }
27 binPerm = permutation.Perm(num % 6)
28 decPerm = permutation.Perm(phase.Denom % 6)
29 return
30 }
31 32 // PhaseProjection returns the projection vertex and key derived from
33 // the epoch phase at a given token index.
34 //
35 // vertex = Phase.Denom mod 8 (3-bit cube corner from decimal clock)
36 // key = |Phase.Num| mod 8 (3-bit projection direction from binary clock)
37 func PhaseProjection(ep epoch.Epoch, index int) (vertex, key uint8) {
38 phase := ep.Phase(int64(index + 1))
39 num := phase.Num
40 if num < 0 {
41 num = -num
42 }
43 vertex = uint8(phase.Denom % 8)
44 key = uint8(num % 8)
45 return
46 }
47 48 // ShadowDecompose applies the phase-derived permutation pair to a
49 // hexagram, rotating inner and outer trigrams independently.
50 // This is the forward (encryption) direction.
51 //
52 // The inner trigram is permuted by binPerm (binary clock component).
53 // The outer trigram is permuted by decPerm (decimal clock component).
54 // Neither projection alone determines the original hexagram.
55 func ShadowDecompose(h state.Hexagram, binP, decP permutation.Perm) state.Hexagram {
56 inner := binP.ApplyTrigram(h.Inner())
57 outer := decP.ApplyTrigram(h.Outer())
58 return state.Hex(inner, outer)
59 }
60 61 // ShadowRecompose applies the inverse permutation pair to recover the
62 // original hexagram from a shadow-decomposed one.
63 // This is the reverse (decryption) direction.
64 func ShadowRecompose(h state.Hexagram, binP, decP permutation.Perm) state.Hexagram {
65 inner := binP.Inverse().ApplyTrigram(h.Inner())
66 outer := decP.Inverse().ApplyTrigram(h.Outer())
67 return state.Hex(inner, outer)
68 }
69 70 // PhasePairIndex returns the canonical index (0-35) of an S_3×S_3
71 // permutation pair. The home pair (determined by the epoch phase)
72 // always maps to index 0. Other pairs are numbered 1-35.
73 //
74 // This supports the shadow compression algorithm: the deviation
75 // from the home pair encodes auxiliary data bits.
76 func PhasePairIndex(binP, decP, homeBin, homeDec permutation.Perm) uint8 {
77 if binP == homeBin && decP == homeDec {
78 return 0
79 }
80 // Canonical ordering: (binP * 6 + decP), with home pair removed.
81 raw := uint8(binP)*6 + uint8(decP)
82 home := uint8(homeBin)*6 + uint8(homeDec)
83 if raw < home {
84 return raw + 1
85 }
86 return raw // home was removed, so indices above home shift down by 1
87 }
88 89 // PairFromIndex recovers the (binPerm, decPerm) pair from a canonical
90 // index, given the home pair.
91 func PairFromIndex(idx uint8, homeBin, homeDec permutation.Perm) (permutation.Perm, permutation.Perm) {
92 if idx == 0 {
93 return homeBin, homeDec
94 }
95 home := uint8(homeBin)*6 + uint8(homeDec)
96 raw := idx
97 if raw <= home {
98 raw = raw - 1
99 }
100 // raw is now the absolute index in the 6×6 grid
101 return permutation.Perm(raw / 6), permutation.Perm(raw % 6)
102 }
103