/ reference / haskell / src / Slot.hs
Slot.hs
  1  
  2  {-# LANGUAGE BangPatterns, StrictData #-}
  3  module Slot where
  4  
  5  --------------------------------------------------------------------------------
  6  
  7  import Data.Bits
  8  import Data.Word
  9  import Data.Array
 10  
 11  import Data.ByteString (ByteString)
 12  import qualified Data.ByteString       as B
 13  import qualified Data.ByteString.Char8 as C
 14  
 15  import Control.Monad
 16  import System.IO
 17  
 18  import Poseidon2
 19  import Misc
 20  
 21  --------------------------------------------------------------------------------
 22  
 23  type Hash = Fr
 24  
 25  newtype Seed     = Seed     Int deriving (Eq,Show)
 26  newtype CellIdx  = CellIdx  Int deriving (Eq,Show)
 27  newtype BlockIdx = BlockIdx Int deriving (Eq,Show)
 28  newtype SlotIdx  = SlotIdx  Int deriving (Eq,Show)
 29  
 30  newtype CellData  = CellData  { fromCellData  :: ByteString }
 31  newtype BlockData = BlockData { fromBlockData :: ByteString }
 32  
 33  instance Show CellData  where show (CellData  bs) = "CellData<"  ++ show (B.length bs) ++ ">"
 34  instance Show BlockData where show (BlockData bs) = "BlockData<" ++ show (B.length bs) ++ ">"
 35  
 36  mkCellData :: SlotConfig -> ByteString -> CellData
 37  mkCellData cfg bs = if B.length bs == _cellSize cfg 
 38    then CellData bs
 39    else error $ "mkCellData: invalid cell size: " ++ show (B.length bs)
 40  
 41  mkBlockData :: SlotConfig -> ByteString -> BlockData
 42  mkBlockData cfg bs = if B.length bs == _blockSize cfg 
 43    then BlockData bs
 44    else error $ "mkBlockData: invalid block size: " ++ show (B.length bs)
 45  
 46  data DataSource
 47    = SlotFile FilePath
 48    | FakeData Seed
 49    deriving Show
 50  
 51  data SlotConfig = MkSlotCfg
 52    { _cellSize  :: Int           -- ^ cell size in bytes (eg. 2048)
 53    , _blockSize :: Int           -- ^ block size in bytes (eg. 65536)
 54    , _nCells    :: Int           -- ^ number of cells per slot (should be power of two)
 55    , _nSamples  :: Int           -- ^ how many cells we sample
 56    , _dataSrc   :: DataSource    -- ^ slot data source
 57    }
 58    deriving Show
 59  
 60  cellsPerBlock :: SlotConfig -> Int
 61  cellsPerBlock cfg = case divMod (_blockSize cfg) (_cellSize cfg) of
 62    (q,0) -> if q>1 then q else error "cells per block must be at least 2"
 63    _     -> error "block size is not divisible by the cell size"
 64  
 65  blocksPerSlot :: SlotConfig -> Int
 66  blocksPerSlot cfg = case divMod (_nCells cfg) (cellsPerBlock cfg) of
 67    (q,0) -> if q>1 then q else error "blocks per slot must be at least 2"
 68    _     -> error "slot size is not divisible by the block size"
 69  
 70  
 71  -- | Example slot configuration
 72  exSlotCfg :: SlotConfig
 73  exSlotCfg = MkSlotCfg
 74    { _cellSize  = 256
 75    , _blockSize = 4096
 76    , _nCells    = 1024
 77    , _nSamples  = 20
 78    , _dataSrc   = FakeData (Seed 12345)
 79    }
 80  
 81  fieldElemsPerCell :: SlotConfig -> Int
 82  fieldElemsPerCell cfg = (_cellSize cfg + 30) `div` 31
 83  
 84  --------------------------------------------------------------------------------
 85  -- * load data
 86  
 87  genFakeCell :: SlotConfig -> Seed -> CellIdx -> CellData
 88  genFakeCell cfg (Seed seed) (CellIdx idx) = (mkCellData cfg $ B.pack list) where
 89    list = go (fromIntegral $ _cellSize cfg) 1 
 90    seed1 = fromIntegral seed + 0xdeadcafe :: Word64
 91    seed2 = fromIntegral idx  + 0x98765432 :: Word64
 92    go :: Word64 -> Word64 -> [Word8]
 93    go 0   _     = []
 94    go cnt state = fromIntegral state'' : go (cnt-1) state'' where
 95      state'  = state*(state + seed1)*(state + seed2) + state*(state `xor` 0x5a5a5a5a) + seed1*state + (seed2 + 17)
 96      state'' = mod state' 1698428844001831
 97  
 98  genFakeBlock :: SlotConfig -> Seed -> BlockIdx -> BlockData
 99  genFakeBlock cfg seed (BlockIdx blockIdx) = (mkBlockData cfg $ B.concat$ map fromCellData cells) where
100    k = cellsPerBlock cfg
101    a =  k *  blockIdx
102    b =  k * (blockIdx + 1) - 1
103    cells = [ genFakeCell cfg seed (CellIdx j) | j<-[a..b] ]
104  
105  loadCellData :: SlotConfig -> CellIdx -> IO CellData
106  loadCellData cfg cellidx@(CellIdx idx) = case _dataSrc cfg of
107    FakeData seed  -> return $ genFakeCell cfg seed cellidx
108    SlotFile fname -> do
109      h <- openBinaryFile fname ReadMode
110      hSeek h AbsoluteSeek (fromIntegral (_cellSize cfg) * fromIntegral idx)
111      bs <- B.hGet h (_cellSize cfg)
112      hClose h
113      return (mkCellData cfg bs)
114  
115  loadBlockData :: SlotConfig -> BlockIdx -> IO BlockData
116  loadBlockData cfg blockidx@(BlockIdx idx) = case _dataSrc cfg of
117    FakeData seed  -> return $ genFakeBlock cfg seed blockidx
118    SlotFile fname -> do
119      h <- openBinaryFile fname ReadMode
120      hSeek h AbsoluteSeek (fromIntegral (_blockSize cfg) * fromIntegral idx)
121      bs <- B.hGet h (_blockSize cfg)
122      hClose h
123      return (mkBlockData cfg bs)
124  
125  --------------------------------------------------------------------------------
126  
127  {-
128  calcSlotTree :: SlotConfig -> IO MerkleTree
129  calcSlotTree cfg = calcMerkleTree <$> calcCellHashes cfg
130  
131  calcCellHashes :: SlotConfig -> IO [Hash]
132  calcCellHashes cfg = do
133    forM [0..(_nCells cfg - 1)] $ \idx -> do
134      cell <- loadCellData cfg idx 
135      return (hashCell cell)
136  -}
137  
138  --------------------------------------------------------------------------------
139  
140  -- | Split bytestring into smaller pieces, no padding
141  splitByteString :: Int -> ByteString -> [ByteString]
142  splitByteString k = go where
143    go bs 
144      | B.null bs  = []
145      | otherwise  = B.take k bs : go (B.drop k bs)
146  
147  splitBlockToCells :: SlotConfig -> BlockData -> [CellData]
148  splitBlockToCells cfg (BlockData blockdata) = 
149    map CellData (splitByteString (_cellSize cfg) blockdata)
150  
151  calcBlockTree :: SlotConfig -> BlockIdx -> IO MerkleTree
152  calcBlockTree cfg idx = do
153    block <- loadBlockData cfg idx
154    let cells = splitBlockToCells cfg block
155    let cellHashes = map (hashCell cfg) cells
156    let tree = calcMerkleTree cellHashes
157    return tree
158  
159  calcAllBlockTrees :: SlotConfig -> IO (Array Int MerkleTree)
160  calcAllBlockTrees cfg 
161    = listArray (0,n-1) <$> (forM [0..n-1] $ \idx -> calcBlockTree cfg (BlockIdx idx))
162    where
163      n = blocksPerSlot cfg
164  
165  --------------------------------------------------------------------------------
166  
167  data SlotTree = MkSlotTree
168    { _miniTrees :: Array Int MerkleTree     -- ^ block trees
169    , _bigTree   :: MerkleTree               -- ^ the tree over the block hashes
170    }
171  
172  slotTreeRoot :: SlotTree -> Hash
173  slotTreeRoot = merkleRootOf . _bigTree
174  
175  calcSlotTree :: SlotConfig -> IO SlotTree
176  calcSlotTree cfg = do
177    minitrees <- calcAllBlockTrees cfg
178    let bigtree = calcMerkleTree $ map merkleRootOf $ elems minitrees
179    return $ MkSlotTree minitrees bigtree
180  
181  extractCellProof :: SlotConfig -> SlotTree -> CellIdx -> [Hash]
182  extractCellProof cfg slotTree (CellIdx cellIdx) = final where
183    (blockIdx, withinBlockIdx) = cellIdx `divMod` (cellsPerBlock cfg)
184    blockTree = (_miniTrees slotTree) ! blockIdx
185    proof1 = extractMerkleProof blockTree           withinBlockIdx
186    proof2 = extractMerkleProof (_bigTree slotTree) blockIdx
187    final  = _merklePath proof1 ++ _merklePath proof2
188  
189  checkCellProof :: SlotConfig -> SlotTree -> CellIdx -> Hash -> [Hash] -> Bool
190  checkCellProof cfg slotTree (CellIdx cellIdx) cellHash path 
191    | logK + logM /= length path  = error "checkCellProof: incorrect Merkle path length"
192    | 2^logK /= k                 = error "checkCellProof: non-power-of-two number of cells per blocks"
193    | otherwise                   = reSlotHash == slotTreeRoot slotTree
194    where
195      k = cellsPerBlock cfg
196      m = blocksPerSlot cfg
197      logK = ceilingLog2 (fromIntegral k)
198      logM = ceilingLog2 (fromIntegral m)
199  
200      blockIdx       = shiftR cellIdx logK
201      inBlockCellIdx = cellIdx .&. (k-1)
202  
203      smallProof = MkMerkleProof
204        { _leafIndex   = inBlockCellIdx
205        , _leafData    = cellHash
206        , _merklePath  = take logK path
207        , _dataSize    = k
208        }
209      bigProof = MkMerkleProof
210        { _leafIndex   = blockIdx
211        , _leafData    = blockHash
212        , _merklePath  = drop logK path
213        , _dataSize    = m
214        }
215  
216      blockHash  = reconstructMerkleRoot smallProof  
217      reSlotHash = reconstructMerkleRoot bigProof
218  
219  --------------------------------------------------------------------------------
220  
221  -- | Hash a cell
222  hashCell :: SlotConfig -> CellData -> Hash
223  hashCell cfg (CellData rawdata) 
224    | B.length rawdata /= _cellSize cfg  = error "hashCell: invalid cell data size"
225    | otherwise                          = hashCell_ rawdata
226  
227  hashCell_ :: ByteString -> Hash
228  hashCell_ rawdata = sponge2 (cellDataToFieldElements $ CellData rawdata) 
229  
230  --------------------------------------------------------------------------------
231  
232  -- | A 31-byte long chunk
233  newtype Chunk 
234    = Chunk ByteString 
235    deriving Show
236  
237  -- | Split bytestring into samller pieces, applying the @10*@ padding strategy.
238  --
239  -- That is, always add a single @0x01@ byte, and then add the necessary
240  -- number (in the interval @[0..k-1]@) of @0x00@ bytes to be a multiple of the 
241  -- given chunk length
242  --
243  padAndSplitByteString :: Int -> ByteString -> [Chunk]
244  padAndSplitByteString k orig = go (B.snoc orig 0x01) where
245    go bs 
246      | m == 0      = []
247      | m < k       = [Chunk $ B.append bs (B.replicate (k-m) 0x00)]
248      | otherwise   = (Chunk $ B.take k bs) : go (B.drop k bs)
249      where
250        m = B.length bs
251  
252  -- | Chunk a ByteString into a sequence of field elements
253  cellDataToFieldElements :: CellData -> [Fr]
254  cellDataToFieldElements (CellData rawdata) = map chunkToField pieces where
255    chunkSize = 31
256    pieces = padAndSplitByteString chunkSize rawdata
257  
258  chunkToField :: Chunk -> Fr
259  chunkToField chunk@(Chunk bs)
260    | l == 31  = fromInteger (chunkToIntegerLE chunk)
261    | l <  31  = error "chunkToField: chunk is too small (expecting exactly 31 bytes)"
262    | l >  31  = error "chunkToField: chunk is too big (expecting exactly 31 bytes)"
263    where 
264      l = B.length bs
265  
266  -- | Interpret a ByteString as an integer (little-endian)
267  chunkToIntegerLE :: Chunk -> Integer
268  chunkToIntegerLE (Chunk chunk) = go (B.unpack chunk) where
269    go []     = 0
270    go (w:ws) = fromIntegral w + shiftL (go ws) 8
271  
272  --------------------------------------------------------------------------------