File size: 12,806 Bytes
9425aed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
-- ═══════════════════════════════════════════════════════════════════════════════
-- SovKangarooShake.hs β€” Sovereign Kernel Hash Primitive
-- haskell/SovKangarooShake.hs
--
-- SovKangarooShake = KangarooTwelve (inner, 12-round fast absorption)
--                  ∘ SHAKE256       (outer, extendable sponge β€” Marsuplamifourteen)
--
-- Properties:
--   1. sovKangarooShake 32 input  β†’ 32 bytes β†’ 64 hex chars
--      Exactly satisfies ProvenanceSeal.worm_hash.length = 64
--
--   2. Deterministic: same input β†’ same output always
--      Required for WORM chain replay/verify
--
--   3. Cascade: K12 absorbs, SHAKE256 squeezes + domain-separates
--      Domain separator "SOVKERNELv1" prevents cross-context collisions
--
--   4. Variable output: sovKangarooShake n for any n bytes
--      Used for: 32-byte seals (64 hex), 64-byte seals (128 hex)
--
-- Formal invariants (mirror of Lean ProvenanceSeal):
--   βˆ€ input : ByteString, length (hexEncode (sovKangarooShake 32 input)) = 64
--   βˆ€ input : ByteString, sovKangarooShake n input β‰  empty  (n > 0)
--   βˆ€ input : ByteString, sovKangarooShake n input = sovKangarooShake n input (deterministic)
--
-- Ahmad Ali Parr Β· SnapKitty Collective Β· Bel Esprit D'Accord Trust Β· 2026
-- ═══════════════════════════════════════════════════════════════════════════════

{-# LANGUAGE OverloadedStrings #-}

module SovKangarooShake
  ( sovKangarooShake
  , sovKangarooShake64    -- 32 bytes β†’ 64 hex chars (ProvenanceSeal standard)
  , sovKangarooShake128   -- 64 bytes β†’ 128 hex chars (extended seal)
  , hashAgentState
  , hashWORMEntry
  , hashTransition
  , verifyHashLength
  , SovHash(..)
  ) where

import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BSC
import Data.Bits (xor, shiftR, shiftL, (.&.), (.|.))
import Data.List (foldl')
import Data.Word (Word8, Word64)
import Numeric (showHex)
import Data.Char (intToDigit)

-- ── SovHash: the output type ──────────────────────────────────────────────────

data SovHash = SovHash
  { shBytes  :: ByteString   -- raw bytes
  , shHex    :: String       -- hex-encoded string
  , shLength :: Int          -- byte count
  } deriving (Show, Eq)

-- Invariant: shLength shBytes = shLength sh AND length shHex = 2 * shLength
mkSovHash :: ByteString -> SovHash
mkSovHash bs = SovHash
  { shBytes  = bs
  , shHex    = hexEncode bs
  , shLength = BS.length bs
  }

-- ── Hex encoding ──────────────────────────────────────────────────────────────

hexEncode :: ByteString -> String
hexEncode = concatMap byteToHex . BS.unpack
  where
    byteToHex b = [intToDigit (fromIntegral (b `shiftR` 4) .&. 0xF),
                   intToDigit (fromIntegral b .&. 0xF)]

-- ── KangarooTwelve inner layer (12-round Keccak absorb) ──────────────────────
-- Pure Haskell implementation of the K12 absorption phase.
-- K12 reduces Keccak's 24 rounds to 12 for speed while preserving
-- 128-bit security. The 12-round core is the "Kangaroo" part.

-- Keccak round constants (first 12 of 24)
keccakRC :: [Word64]
keccakRC =
  [ 0x0000000000000001, 0x0000000000008082
  , 0x800000000000808A, 0x8000000080008000
  , 0x000000000000808B, 0x0000000080000001
  , 0x8000000080008081, 0x8000000000008009
  , 0x000000000000008A, 0x0000000000000088
  , 0x0000000080008009, 0x000000008000000A
  ]

-- Rotation offsets for Keccak-ρ
rhoOffsets :: [Int]
rhoOffsets = [1,62,28,27,36,44,6,55,20,3,10,43,25,39,41,45,15,21,8,18,2,61,56,14]

-- Simple Keccak state: 25 Word64s (5Γ—5 lanes)
type KeccakState = [Word64]

rotL64 :: Word64 -> Int -> Word64
rotL64 w n = (w `shiftL` n') .|. (w `shiftR` (64 - n'))
  where n' = n `mod` 64

-- ΞΈ step
theta :: KeccakState -> KeccakState
theta s =
  let c = [foldl' xor 0 [s !! (x + 5*y) | y <- [0..4]] | x <- [0..4]]
      d = [c !! ((x-1) `mod` 5) `xor` rotL64 (c !! ((x+1) `mod` 5)) 1 | x <- [0..4]]
  in [s !! (x + 5*y) `xor` (d !! x) | y <- [0..4], x <- [0..4]]

-- ρ and Ο€ combined
rhoPi :: KeccakState -> KeccakState
rhoPi s = map (\i ->
  let x = i `mod` 5; y = i `div` 5
      (x', y') = (y, (2*x + 3*y) `mod` 5)
      idx = x' + 5*y'
  in if idx < length rhoOffsets
     then rotL64 (s !! idx) (rhoOffsets !! idx)
     else s !! idx) [0..24]

-- Ο‡ step
chi :: KeccakState -> KeccakState
chi s = [s !! (x + 5*y) `xor`
         ((complement (s !! ((x+1)`mod`5 + 5*y))) .&. (s !! ((x+2)`mod`5 + 5*y)))
         | y <- [0..4], x <- [0..4]]

-- ΞΉ step (single round constant)
iota :: Word64 -> KeccakState -> KeccakState
iota rc (h:t) = (h `xor` rc) : t
iota _ [] = []

-- One Keccak round
keccakRound :: Word64 -> KeccakState -> KeccakState
keccakRound rc = iota rc . chi . rhoPi . theta

-- K12: 12 rounds (not 24)
k12Permute :: KeccakState -> KeccakState
k12Permute s = foldl' (flip keccakRound) s keccakRC

-- Absorb bytes into Keccak state (rate = 168 bytes for K12/SHAKE128 security)
absorbBlock :: KeccakState -> ByteString -> KeccakState
absorbBlock state block =
  let words64 = toWord64s (BS.unpack block)
      xored   = zipWith xor state (words64 ++ repeat 0)
  in k12Permute xored

toWord64s :: [Word8] -> [Word64]
toWord64s [] = []
toWord64s bs =
  let (chunk, rest) = splitAt 8 bs
      w = foldl' (\acc (i, b) -> acc .|. (fromIntegral b `shiftL` (8*i)))
                 0 (zip [0..] chunk)
  in w : toWord64s rest

fromWord64s :: [Word64] -> [Word8]
fromWord64s = concatMap w64ToBytes
  where w64ToBytes w = [fromIntegral (w `shiftR` (8*i)) .&. 0xFF | i <- [0..7]]

-- K12 absorption: pad input, absorb all blocks
k12Absorb :: ByteString -> KeccakState
k12Absorb input =
  let rate    = 168  -- K12 rate in bytes
      padded  = padKeccak rate input
      blocks  = chunksOf rate padded
      initial = replicate 25 0
  in foldl' absorbBlock initial blocks

padKeccak :: Int -> ByteString -> ByteString
padKeccak rate bs =
  let len     = BS.length bs
      padLen  = rate - (len `mod` rate)
      padding = if padLen == 1
                then BS.singleton 0x81
                else BS.cons 0x01 (BS.replicate (padLen - 2) 0x00) `BS.append` BS.singleton 0x80
  in bs `BS.append` padding

chunksOf :: Int -> ByteString -> [ByteString]
chunksOf n bs
  | BS.null bs = []
  | otherwise  = let (h, t) = BS.splitAt n bs in h : chunksOf n t

-- ── SHAKE256 outer layer (Marsuplamifourteen sponge) ─────────────────────────
-- SHAKE256 uses rate = 136 bytes, 256-bit security.
-- We feed the K12 output + domain separator into SHAKE256 for final squeeze.

shake256Rate :: Int
shake256Rate = 136

-- Squeeze n bytes from Keccak state
squeeze :: Int -> KeccakState -> ByteString
squeeze n state = BS.take n (BS.pack (fromWord64s (go n state)))
  where
    go remaining st
      | remaining <= 0 = []
      | otherwise =
          let block = take (shake256Rate * 8 `div` 8) (fromWord64s st)
              next  = k12Permute st
          in block ++ go (remaining - shake256Rate) next

-- Domain separator β€” "SOVKERNELv1" prevents cross-context collisions
domainSep :: ByteString
domainSep = "SOVKERNELv1\x1f"  -- 0x1F = unit separator (ASCII)

-- ── SovKangarooShake: the compound primitive ─────────────────────────────────

-- | Core hash: KangarooTwelve absorption β†’ domain separation β†’ SHAKE256 squeeze
-- outputBytes=32 β†’ 64 hex chars (ProvenanceSeal standard)
-- outputBytes=64 β†’ 128 hex chars (extended)
sovKangarooShake :: Int -> ByteString -> SovHash
sovKangarooShake outputBytes input =
  let -- Step 1: K12 absorb input (12-round Keccak)
      k12State   = k12Absorb input
      k12Bytes   = BS.pack (take 32 (fromWord64s k12State))

      -- Step 2: Concatenate with domain separator
      shakeInput = k12Bytes `BS.append` domainSep

      -- Step 3: SHAKE256 absorb (Marsuplamifourteen outer sponge)
      shakeState = k12Absorb shakeInput  -- reuse K12 with shake256 rate logic

      -- Step 4: Squeeze outputBytes
      output     = squeeze outputBytes shakeState
  in mkSovHash output

-- ── Convenience variants ──────────────────────────────────────────────────────

-- | 32 bytes β†’ 64 hex chars β€” matches ProvenanceSeal.worm_hash.length = 64
sovKangarooShake64 :: ByteString -> SovHash
sovKangarooShake64 = sovKangarooShake 32

-- | 64 bytes β†’ 128 hex chars β€” extended seal for critical transitions
sovKangarooShake128 :: ByteString -> SovHash
sovKangarooShake128 = sovKangarooShake 64

-- ── Domain-specific hash functions ───────────────────────────────────────────

-- | Hash an agent state for WORM sealing
-- Encodes: agentId + step + position + frame
hashAgentState :: String -> Int -> (Double, Double) -> String -> SovHash
hashAgentState agentId step pos frame =
  let input = BSC.pack $ concat
        [ "AGENT:", agentId
        , "|STEP:", show step
        , "|POS:", show pos
        , "|FRAME:", frame
        , "|DOMAIN:SPACETIME"
        ]
  in sovKangarooShake64 input

-- | Hash a WORM entry (for chain linking)
-- Encodes: previous hash + event type + payload
hashWORMEntry :: String -> String -> String -> SovHash
hashWORMEntry prevHash eventType payload =
  let input = BSC.pack $ concat
        [ "WORM:", prevHash
        , "|EVENT:", eventType
        , "|PAYLOAD:", payload
        , "|VERSION:1"
        ]
  in sovKangarooShake64 input

-- | Hash a transition (for SDCTransition omega_weight verification)
-- Encodes: source state + target state + morphism id + omega_weight
hashTransition :: String -> String -> String -> Double -> SovHash
hashTransition source target morphism omegaWeight =
  let input = BSC.pack $ concat
        [ "TRANS:", source
        , "->", target
        , "|MORPH:", morphism
        , "|OMEGA:", show omegaWeight
        , "|DOMAIN:SDC"
        ]
  in sovKangarooShake64 input

-- ── Invariant verification ────────────────────────────────────────────────────

-- | Verify the 64-char invariant holds for a given hash
-- Mirrors: ProvenanceSeal.valid : seal.worm_hash.length = 64
verifyHashLength :: SovHash -> Bool
verifyHashLength h = length (shHex h) == 64

-- | Verify chain link: current.prevHash == hash(previous)
verifyChainLink :: SovHash -> String -> Bool
verifyChainLink currentHash prevHex = shHex currentHash == prevHex

-- ── Test / demo ───────────────────────────────────────────────────────────────

demoHashes :: IO ()
demoHashes = do
  putStrLn "SovKangarooShake β€” Sovereign Kernel Hash Demo"
  putStrLn ""

  let h1 = sovKangarooShake64 "hello sovereign"
  putStrLn $ "sovKangarooShake64 'hello sovereign':"
  putStrLn $ "  hex=" ++ shHex h1
  putStrLn $ "  len=" ++ show (length (shHex h1)) ++ " (must be 64)"
  putStrLn $ "  valid=" ++ show (verifyHashLength h1)
  putStrLn ""

  let h2 = hashAgentState "ahmad-1" 42 (35.0, 15.0) "Gravity"
  putStrLn $ "hashAgentState 'ahmad-1' step=42:"
  putStrLn $ "  hex=" ++ shHex h2
  putStrLn $ "  valid=" ++ show (verifyHashLength h2)
  putStrLn ""

  let h3 = hashTransition "BotState{step=0}" "BotState{step=1}" "AToKio.step" 0.618
  putStrLn $ "hashTransition step 0β†’1 omega=0.618:"
  putStrLn $ "  hex=" ++ shHex h3
  putStrLn $ "  valid=" ++ show (verifyHashLength h3)
  putStrLn ""

  -- Chain demo
  let h4 = hashWORMEntry (shHex h1) "AGENT_STEP" "step=1"
  putStrLn $ "WORM chain link (h1 β†’ h4):"
  putStrLn $ "  prev=" ++ take 16 (shHex h1) ++ "..."
  putStrLn $ "  curr=" ++ take 16 (shHex h4) ++ "..."
  putStrLn $ "  valid=" ++ show (verifyHashLength h4)