-- ═══════════════════════════════════════════════════════════════════════════════ -- 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)