packages feed

merkle-log-0.2.0: bench/Main.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PackageImports #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}

{-# OPTIONS_GHC -fno-warn-orphans #-}

-- |
-- Module: Main
-- Copyright: Copyright © 2019 Kadena LLC.
-- License: MIT
-- Maintainer: Lars Kuhtz <lars@kadena.io>
-- Stability: experimental
--
-- TODO
--
module Main
( main
) where

import Control.DeepSeq

import Criterion
import Criterion.Main

import "crypton" Crypto.Hash
import qualified "cryptonite" Crypto.Hash as CR

import qualified Data.ByteArray as BA
import qualified Data.ByteString as B
import Data.ByteString.Random.MWC
import qualified Data.HashTree as HT
import Data.Maybe

import GHC.Generics

import Numeric.Natural

import System.Random
import qualified System.Random.MWC as MWC

-- internal modules

import qualified Data.MerkleLog as ML

-- -------------------------------------------------------------------------- --
-- Main

main :: IO ()
main = defaultMain
    [ env globalEnv $ \ ~e -> bgroup "main"
        [ bgroup "create tree"
            [ bgroup "SHA512t_256"
                [ createBench @(ML SHA512t_256) e
                , createBench @(HT CR.SHA512t_256) e
                ]
            , bgroup "SHA256"
                [ createBench @(ML SHA256) e
                , createBench @(HT CR.SHA256) e
                ]
            , bgroup "SHA3_256"
                [ createBench @(ML SHA3_256) e
                , createBench @(HT CR.SHA3_256) e
                ]
            , bgroup "BLAKE2b_256"
                [ createBench @(ML Blake2b_256) e
                ]
            ]
        , bgroup "create inclusion proof"
            [ bgroup "SHA512t_256"
                [ proofBench @(ML SHA512t_256) e
                , proofBench @(HT CR.SHA512t_256) e
                ]
            , bgroup "SHA256"
                [ proofBench @(ML SHA256) e
                , proofBench @(HT CR.SHA256) e
                ]
            , bgroup "SHA3_256"
                [ proofBench @(ML SHA3_256) e
                , proofBench @(HT CR.SHA3_256) e
                ]
            , bgroup "BLAKE2b_256"
                [ proofBench @(ML Blake2b_256) e
                ]
            ]
        , bgroup "verify inclusion proof"
            [ bgroup "SHA512t_256"
                [ verifyBench @(ML SHA512t_256) e
                , verifyBench @(HT CR.SHA512t_256) e
                ]
            , bgroup "SHA256"
                [ verifyBench @(ML SHA256) e
                , verifyBench @(HT CR.SHA256) e
                ]
            , bgroup "SHA3_256"
                [ verifyBench @(ML SHA3_256) e
                , verifyBench @(HT CR.SHA3_256) e
                ]
            , bgroup "BLAKE2b_256"
                [ verifyBench @(ML Blake2b_256) e
                ]
            ]
        ]
    ]

-- -------------------------------------------------------------------------- --
-- Merkle Tree Implementations
-- -------------------------------------------------------------------------- --

-- -------------------------------------------------------------------------- --
-- Global Environment

leafCount :: Int
leafCount = 10000

leafMaxSize :: Int
leafMaxSize = 1000

type GlobalEnv = [B.ByteString]

globalEnv :: IO GlobalEnv
globalEnv = do
    gen <- MWC.create
    traverse (randomGen gen) (randomNats leafCount)
  where

randomNats :: Int -> [Natural]
randomNats i = fmap fromIntegral $ take i $ randomRs @Int (0,leafMaxSize) $ mkStdGen 1

-- -------------------------------------------------------------------------- --
-- Create Benchmark

createBench :: forall a . Impl a => GlobalEnv -> Benchmark
createBench = bench (label @a) . nf (tree @a)

-- -------------------------------------------------------------------------- --
-- Proof Benchmark

type ProofEnv a = (Tree a, B.ByteString, Int)

proofEnv :: forall a . Impl a => GlobalEnv -> IO (ProofEnv a)
proofEnv e = return (tree @a e, e !! 277, 277)

-- | Note that this also includes verification of the proof, because that's the
-- only way we can ensure that the resulting proofs are in normal form.
--
proofBench
    :: forall a
    . Impl a
    => GlobalEnv
    -> Benchmark
proofBench e = env (proofEnv @a e)
    $ bench (label @a) . nf (\(t, ix, i) -> proof @a t ix i)

-- -------------------------------------------------------------------------- --
-- Verify Benchmark

type VerifyEnv a = Proof a

verifyEnv :: forall a . Impl a => GlobalEnv -> IO (VerifyEnv a)
verifyEnv e = return $ proof @a (tree @a e) (e !! 277) 277

verifyBench
    :: forall a
    . Impl a
    => GlobalEnv
    -> Benchmark
verifyBench e = env (verifyEnv @a e) $ bench (label @a) . nf verifyThrow
  where
    verifyThrow p
        | verify @a p = ()
        | otherwise = error "benchmark failure"

-- -------------------------------------------------------------------------- --
-- Merkle Tree Implementations
-- -------------------------------------------------------------------------- --

-- -------------------------------------------------------------------------- --
-- Merkle Tree Implementation Class

class (NFData (Tree a), NFData (Root a), NFData (Proof a)) => Impl a where
    type Tree a
    type Proof a
    type Root a

    label :: String
    tree :: [B.ByteString] -> Tree a
    root :: Tree a -> Root a
    proof :: Tree a -> B.ByteString -> Int -> Proof a
    verify :: Proof a -> Bool

-- -------------------------------------------------------------------------- --
-- merkle-log

data MLProof a = MLProof
    {-# UNPACK #-} !(ML.MerkleProof a)
    {-# UNPACK #-} !(ML.MerkleRoot a)
        -- ^ Root of the Tree
    deriving (Generic)

instance NFData (MLProof a)

data ML a

instance HashAlgorithm a => Impl (ML a) where
    type Tree (ML a) = ML.MerkleTree a
    type Proof (ML a) = MLProof a
    type Root (ML a) = ML.MerkleRoot a

    label = "merkle-log"
    tree = ML.merkleTree @a . fmap ML.InputNode
    root = ML.merkleRoot
    proof t ix i =
        let p = case ML.merkleProof (ML.InputNode ix) i t of
                Right x -> x
                Left e -> error $ "proof creation failed in benchmark: " <> show e
        in MLProof p (ML.merkleRoot t)
    verify (MLProof p r) = ML.runMerkleProof p == r

    {-# INLINE label #-}
    {-# INLINE tree #-}
    {-# INLINE root #-}
    {-# INLINE proof #-}
    {-# INLINE verify #-}

-- -------------------------------------------------------------------------- --
-- hash-tree package

data HTProof a = HTProof
    {-# UNPACK #-} !(HT.InclusionProof a)
    {-# UNPACK #-} !B.ByteString
        -- ^ Proof subject (leaf)
    {-# UNPACK #-} !(CR.Digest a)
        -- ^ Root of the Tree
    deriving (Generic)

instance NFData (HTProof a)

instance NFData (HT.MerkleHashTrees B.ByteString a) where
    rnf t = rnf $ HT.digest (HT.size t) t
    {-# INLINE rnf #-}

instance NFData (HT.InclusionProof a) where
    rnf p = rnf (HT.leafIndex p)
        `seq` rnf (HT.treeSize p)
        `seq` rnf (HT.inclusion p)
    {-# INLINE rnf #-}

data HT a

htSettings :: forall a . CR.HashAlgorithm a => HT.Settings B.ByteString a
htSettings = HT.defaultSettings
    { HT.hash0 = CR.hash @B.ByteString @a mempty
    , HT.hash1 = \x -> CR.hash @_ @a (B.singleton 0x00 `B.append` x)
    , HT.hash2 = \x y -> CR.hash @_ @a $ B.concat [B.singleton 0x01, BA.convert x, BA.convert y]
    }

instance (CR.HashAlgorithm a) => Impl (HT a) where
    type Tree (HT a) = HT.MerkleHashTrees B.ByteString a
    type Proof (HT a) = HTProof a
    type Root (HT a) = CR.Digest a

    label = "hash-tree"
    tree = HT.fromList htSettings
    root t = fromJust $ HT.digest (HT.size t) t
    proof t ix _ = HTProof
        (fromJust $ HT.generateInclusionProof (HT.hash1 (htSettings @a) ix) (HT.size t) t)
        ix
        (root @(HT a) t)
    verify (HTProof p subj r) = HT.verifyInclusionProof
        (htSettings @a) (HT.hash1 (htSettings @a) subj) r p

    {-# INLINE label #-}
    {-# INLINE tree #-}
    {-# INLINE root #-}
    {-# INLINE proof #-}
    {-# INLINE verify #-}