content: {-# LANGUAGE ImportQualifiedPost #-}
  {-# LANGUAGE OverloadedRecordDot #-}
  {-# LANGUAGE OverloadedStrings #-}
  
  module Keccak256Spec (spec) where
  
  import Control.Monad (forM_)
  import Crypto.Hash.Keccak256Native (keccak256)
  import Data.ByteString.Base16 qualified as Base16
  import Data.Text (Text)
  import Data.Text qualified as Text
  import Data.Text.Encoding qualified as Text
  import Data.Vector qualified as V
  import Pact.Types.Util (decodeBase64UrlUnpadded, encodeBase64UrlUnpadded)
  import Test.Hspec (Spec, describe, it, shouldBe)
  
  data Reference = Reference
    { input :: Text
    , output :: Text
    }
  
  -- | Test vectors taken from https://keccak.team/archives.html.
  --   "Known-answer and Monte Carlo test results, as of round 3 of the SHA-3 competition":
  --   https://keccak.team/obsolete/KeccakKAT-3.zip
  references :: [Reference]
  references =
    [ Reference
        { input = ""
        , output = "C5D2460186F7233C927E7DB2DCC703C0E500B653CA82273B7BFAD8045D85A470"
        }
    , Reference
        { input = "20FF454369A5D05B81A78F3DB05819FEA9B08C2384F75CB0AB6AA115DD690DA3131874A1CA8F708AD1519EA952C1E249CB540D196392C79E87755424FEE7C890808C562722359EEA52E8A12FBBB969DD7961D2BA52037493755A5FA04F0D50A1AA26C9B44148C0D3B94D1C4A59A31ACA15AE8BD44ACB7833D8E91C4B86FA3135A423387B8151B4133ED23F6D7187B50EC2204AD901AD74D396E44274E0ECAFAAE17B3B9085E22260B35CA53B15CC52ABBA758AF6798FBD04ECEECED648F3AF4FDB3DED7557A9A5CFB7382612A8A8F3F45947D1A29CE29072928EC193CA25D51071BD5E1984ECF402F306EA762F0F25282F5296D997658BE3F983696FFA6D095C6369B4DAF79E9A5D3136229128F8EB63C12B9E9FA78AFF7A3E9E19A62022493CD136DEFBB5BB7BA1B938F367FD2F63EB5CA76C0B0FF21B9E36C3F07230CF3C3074E5DA587040A76975D7E39F4494ACE5486FCBF380AB7558C4FE89656335B82E4DB8659509EAB46A19613126E594042732DD4C411F41AA8CDEAC71C0FB40A94E6DA558C05E77B6182806F26D9AFDF3DA00C69419222C8186A6EFAD600B410E6CE2F2A797E49DC1F135319801FA6F396B06F975E2A190A023E474B618E7"
        , output = "0EC8D9D20DDF0A7B0251E941A7261B557507FF6287B504362A8F1734C5A91012"
        }
    , Reference
        { input = "4FBDC596508D24A2A0010E140980B809FB9C6D55EC75125891DD985D37665BD80F9BEB6A50207588ABF3CEEE8C77CD8A5AD48A9E0AA074ED388738362496D2FB2C87543BB3349EA64997CE3E7B424EA92D122F57DBB0855A803058437FE08AFB0C8B5E7179B9044BBF4D81A7163B3139E30888B536B0F957EFF99A7162F4CA5AA756A4A982DFADBF31EF255083C4B5C6C1B99A107D7D3AFFFDB89147C2CC4C9A2643F478E5E2D393AEA37B4C7CB4B5E97DADCF16B6B50AAE0F3B549ECE47746DB6CE6F67DD4406CD4E75595D5103D13F9DFA79372924D328F8DD1FCBEB5A8E2E8BF4C76DE08E3FC46AA021F989C49329C7ACAC5A688556D7BCBCB2A5D4BE69D3284E9C40EC4838EE8592120CE20A0B635ECADAA84FD5690509F54F77E35A417C584648BC9839B974E07BFAB0038E90295D0B13902530A830D1C2BDD53F1F9C9FAED43CA4EED0A8DD761BC7EDBDDA28A287C60CD42AF5F9C758E5C7250231C09A582563689AFC65E2B79A7A2B68200667752E9101746F03184E2399E4ED8835CB8E9AE90E296AF220AE234259FE0BD0BCC60F7A4A5FF3F70C5ED4DE9C8C519A10E962F673C82C5E9351786A8A3BFD570031857BD4C87F4FCA31ED4D50E14F2107DA02CB5058700B74EA241A8B41D78461658F1B2B90BFD84A4C2C9D6543861AB3C56451757DCFB9BA60333488DBDD02D601B41AAE317CA7474EB6E6DD"
        , output = "0EA33E2E34F572440640244C7F1F5F04697CE97139BDA72A6558D8663C02B388"
        }
    ]
  
  spec :: Spec
  spec = describe "keccak256" $ do
    it "produces correct results" $ do
      forM_ references $ \ref -> do
        hashInputAndCompareToOutput ref.input ref.output
        hashChunksAreEqual ref.input
  
  hash :: Text -> Text
  hash str = hashChunks [str]
  
  hashChunks :: [Text] -> Text
  hashChunks chunks = unwrap go
    where
      go :: Either String Text
      go = do
        vals <- traverse (Base16.decode . Text.encodeUtf8) chunks
        let texts = V.fromList $ map (Text.decodeUtf8 . encodeBase64UrlUnpadded) vals
        case keccak256 texts of
          Left err -> Left (show err)
          Right out -> do
            rawHash <- decodeBase64UrlUnpadded (Text.encodeUtf8 out)
            pure (Text.decodeUtf8 (Base16.encode rawHash))
  
  hashInputAndCompareToOutput :: Text -> Text -> IO ()
  hashInputAndCompareToOutput i o = do
    Text.toLower (hash i) `shouldBe` Text.toLower o
  
  hashChunksAreEqual :: Text -> IO ()
  hashChunksAreEqual original = do
    let chunkSize = 10 -- random number i made up
    let chunks = Text.chunksOf chunkSize original
    Text.toLower (hash original) `shouldBe` Text.toLower (hashChunks chunks)
  
  unwrap :: Either String a -> a
  unwrap e = case e of
    Left err -> error err
    Right a -> a
  