shadow_phase.go raw

   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