sov-kernel-monster / haskell /SovKangarooShake.hs
SNAPKITTYWEST's picture
chore: push full sov-kernel-monster content from local build
9425aed verified
Raw
History Blame Contribute Delete
12.8 kB
-- ═══════════════════════════════════════════════════════════════════════════════
-- 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)