Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion botan-bindings/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ None

### New features

None
* Support eXtendable Output Functions (XOF). See PR
[#142](https://github.com/haskell-cryptography/botan/pull/142).

### Minor changes

Expand Down
1 change: 1 addition & 0 deletions botan-bindings/botan-bindings.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ library
Botan.Bindings.Version
Botan.Bindings.View
Botan.Bindings.X509
Botan.Bindings.XOF
Botan.Bindings.ZFEC

build-depends: base >=4.16 && <4.23
Expand Down
2 changes: 1 addition & 1 deletion botan-bindings/src/Botan/Bindings/Hash.hs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ foreign import capi safe "botan/ffi.h botan_hash_block_size"
botan_hash_block_size
:: BotanHash -- ^ __hash__: hash object
-> Ptr CSize -- ^ __block_size__: output buffer to hold the hash function block size
-> IO CInt -- ^ 0 on success, a negative value on failure
-> IO CInt -- ^ returns the hash function block size

-- | Send more input to the hash function
foreign import capi safe "botan/ffi.h botan_hash_update"
Expand Down
118 changes: 118 additions & 0 deletions botan-bindings/src/Botan/Bindings/XOF.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
{-|
Module : Botan.Bindings.XOF
Description : eXtendable Output Functions (XOFs)
Copyright : (c) 2026-2027, Haskell Foundation
License : BSD-3-Clause
Maintainer : joris@well-typed.com, leo@apotheca.io
Stability : experimental
Portability : POSIX

An eXtendable Output Function (XOF) transforms an arbitrary length input message into an indefinite stream of output bits. Typically, it is illegal to call update() after the first call to output().
-}

{-# LANGUAGE CApiFFI #-}
{-# LANGUAGE OverloadedStrings #-}

module Botan.Bindings.XOF (
BotanXOFStruct
, BotanXOF (..)
, botan_xof_init
, botan_xof_copy_state
, botan_xof_block_size
, botan_xof_name
, botan_xof_accepts_input
, botan_xof_clear
, botan_xof_update
, botan_xof_output
, botan_xof_destroy
, pattern BOTAN_XOF_ASCON_XOF128
, pattern BOTAN_XOF_SHAKE_128_XOF
, pattern BOTAN_XOF_SHAKE_256_XOF
) where

import Botan.Bindings.ConstPtr
import Data.String
import Data.Word
import Foreign.C.Types
import Foreign.ForeignPtr
import Foreign.Ptr
import Foreign.Storable

-- | Opaque XOF struct
data {-# CTYPE "botan/ffi.h" "struct botan_xof_struct" #-} BotanXOFStruct

-- | Botan XOF object
newtype {-# CTYPE "botan/ffi.h" "botan_xof_t" #-} BotanXOF
= MkBotanXOF { ptr :: Ptr BotanXOFStruct }
deriving newtype (Eq, Ord, Storable)

-- | Frees all resources of the eXtendable Output Function
foreign import capi safe "botan/ffi.h &botan_xof_destroy"
botan_xof_destroy
:: FinalizerPtr BotanXOFStruct

-- | Initialize an eXtendable Output Function
foreign import capi safe "botan/ffi.h botan_xof_init"
botan_xof_init
:: Ptr BotanXOF -- ^ __xof__: XOF object
-> ConstPtr CChar -- ^ __xof_name__: name of the XOF, e.g., "SHAKE-128"
-> Word32 -- ^ __flags__: should be 0 in current API revision, all other uses are reserved and return BOTAN_FFI_ERROR_BAD_FLAG
-> IO CInt -- ^ 0 on success, a negative value on failure

-- | Copy the state of an eXtendable Output Function
foreign import capi safe "botan/ffi.h botan_xof_copy_state"
botan_xof_copy_state
:: Ptr BotanXOF -- ^ __dest__: destination XOF object
-> BotanXOF -- ^ __source__: source XOF object
-> IO CInt -- ^ 0 on success, a negative value on failure

-- | Writes the block size of the eXtendable Output Function to *block_size
foreign import capi safe "botan/ffi.h botan_xof_block_size"
botan_xof_block_size
:: BotanXOF -- ^ __xof__: XOF object
-> Ptr CSize -- ^ __block_size__: output buffer to hold the XOF's block size
-> IO CInt -- ^ returns the XOF block size

-- | Get the name of this eXtendable Output Function
foreign import capi safe "botan/ffi.h botan_xof_name"
botan_xof_name
:: BotanXOF -- ^ __xof__: XOF object to read
-> Ptr CChar -- ^ __name__: output buffer
-> Ptr CSize -- ^ __name_len__: on input, the length of buffer, on success the number of bytes written
-> IO CInt -- ^ 0 on success, a negative value on buffer preparation failure

-- | Get the input/output state of this eXtendable Output Function. Typically, XOFs don't accept input as soon as the first output bytes were requested.
foreign import capi safe "botan/ffi.h botan_xof_accepts_input"
botan_xof_accepts_input
:: BotanXOF -- ^ __xof__: XOF object
-> IO CInt -- ^ 1 iff the XOF is still accepting input bytes

-- | Reinitializes the state of the eXtendable Output Function
foreign import capi safe "botan/ffi.h botan_xof_clear"
botan_xof_clear
:: BotanXOF -- ^ __xof__: XOF object
-> IO CInt -- ^ 0 on success, a negative value on failure

-- | Send more input to the XOF
foreign import capi safe "botan/ffi.h botan_xof_update"
botan_xof_update
:: BotanXOF -- ^ __xof__: XOF object
-> ConstPtr Word8 -- ^ __in__: input buffer
-> CSize -- ^ __in_len__: number of bytes to read from the input buffer
-> IO CInt -- ^ 0 on success, a negative value on failure

-- | Generate output bytes from the XOF
foreign import capi safe "botan/ffi.h botan_xof_output"
botan_xof_output
:: BotanXOF -- ^ __xof__: XOF object
-> Ptr Word8 -- ^ __out__: output buffer
-> CSize -- ^ __out_len__: number of bytes to write into the output buffer
-> IO CInt -- ^ 0 on success, a negative value on failure

pattern BOTAN_XOF_ASCON_XOF128
, BOTAN_XOF_SHAKE_128_XOF
, BOTAN_XOF_SHAKE_256_XOF
:: (Eq a, IsString a) => a
pattern BOTAN_XOF_ASCON_XOF128 = "Ascon-XOF128"
pattern BOTAN_XOF_SHAKE_128_XOF = "SHAKE-128"
pattern BOTAN_XOF_SHAKE_256_XOF = "SHAKE-256"
2 changes: 2 additions & 0 deletions botan-low/botan-low.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ library
Botan.Low.Version
Botan.Low.View
Botan.Low.X509
Botan.Low.XOF
Botan.Low.ZFEC

other-modules:
Expand Down Expand Up @@ -195,6 +196,7 @@ test-suite test
Test.Botan.Low.Utility
Test.Botan.Low.Version
Test.Botan.Low.X509
Test.Botan.Low.XOF
Test.Botan.Low.ZFEC
Test.Util.ByteString
Test.Util.Hspec
Expand Down
127 changes: 127 additions & 0 deletions botan-low/src/Botan/Low/XOF.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
{-|
Module : Botan.Low.XOF
Description : eXtendable Output Functions (XOFs)
Copyright : (c) 2026-2027, Haskell Foundation
License : BSD-3-Clause
Maintainer : joris@well-typed.com, leo@apotheca.io
Stability : experimental
Portability : POSIX

An eXtendable Output Function (XOF) transforms an arbitrary length input message into an indefinite stream of output bits. Typically, it is illegal to call update() after the first call to output().
-}

module Botan.Low.XOF (

-- * Usage

XOF(..)
, XOFName
, XOFDigest
, withXOF
, xofInit
, xofCopyState
, xofBlockSize
, xofName
, xofAcceptsInput
, xofClear
, xofUpdate
, xofOutput
, xofDestroy

-- * eXtendable Output Function algorithms

, pattern ASCON_XOF128
, pattern SHAKE128
, pattern SHAKE256

-- * Convenience

, allXOFs
) where

import Botan.Bindings.XOF
import Botan.Low.Error.Internal
import Botan.Low.Internal.ByteString
import Botan.Low.Make
import Botan.Low.Remake
import Data.ByteString
import Foreign.C.Types
import Foreign.ForeignPtr
import Foreign.Ptr

newtype XOF = MkXOF { foreignPtr :: ForeignPtr BotanXOFStruct }

withXOF :: XOF -> (BotanXOF -> IO a) -> IO a
xofDestroy :: XOF -> IO ()
createXOF :: (Ptr BotanXOF -> IO CInt) -> IO XOF
(withXOF, xofDestroy, createXOF)
= mkBindings
MkBotanXOF (.ptr)
MkXOF (.foreignPtr)
botan_xof_destroy

type XOFName = ByteString

pattern ASCON_XOF128
, SHAKE128
, SHAKE256
:: XOFName

pattern ASCON_XOF128 = BOTAN_XOF_ASCON_XOF128
pattern SHAKE128 = BOTAN_XOF_SHAKE_256_XOF
pattern SHAKE256 = BOTAN_XOF_SHAKE_256_XOF

type XOFDigest = ByteString

allXOFs :: [XOFName]
allXOFs =
[ ASCON_XOF128
, SHAKE128
, SHAKE256
]

xofInit
:: XOFName -- ^ __xof_name__: name of the XOF, e.g., "SHAKE-128"
-> IO XOF -- ^ __xof__: XOF object
xofInit = mkCreateObjectCString createXOF $ \ out name ->
botan_xof_init out name 0

xofCopyState
:: XOF -- ^ __source__: source XOF object
-> IO XOF -- ^ __dest__: destination XOF object
xofCopyState source = withXOF source $ \ sourcePtr -> do
createXOF $ \ dest -> botan_xof_copy_state dest sourcePtr

xofBlockSize
:: XOF -- ^ __xof__: XOF object
-> IO Int -- ^ __block_size__: output buffer to hold the XOF's block size
xofBlockSize = mkGetSize withXOF botan_xof_block_size

xofName
:: XOF -- ^ __xof__: XOF object to read
-> IO XOFDigest -- ^ __name__: output buffer
xofName = mkGetCString withXOF botan_xof_name

xofAcceptsInput
:: XOF -- ^ __xof__: XOF object
-> IO Bool -- ^ __accepts_input__: true iff the XOF is still accepting input bytes
xofAcceptsInput = mkGetBoolCode withXOF botan_xof_accepts_input

xofClear
:: XOF -- ^ __xof__: XOF object
-> IO ()
xofClear = mkAction withXOF botan_xof_clear

xofUpdate
:: XOF -- ^ __xof__: XOF object
-> ByteString -- ^ __in__: input buffer
-> IO ()
xofUpdate = mkWithObjectSetterCBytesLen withXOF botan_xof_update

xofOutput
:: XOF -- ^ __xof__: XOF object
-> Int -- ^ __out_len__: number of bytes to write into the output buffer
-> IO XOFDigest -- ^ __out__: output buffer
xofOutput xof sz = withXOF xof $ \ xofPtr -> do
allocBytes sz $ \ digestPtr -> do
throwBotanIfNegative_ $ botan_xof_output xofPtr digestPtr $ fromIntegral sz
3 changes: 3 additions & 0 deletions botan-low/test/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import qualified Test.Botan.Low.TOTP
import qualified Test.Botan.Low.Utility
import qualified Test.Botan.Low.Version
import qualified Test.Botan.Low.X509
import qualified Test.Botan.Low.XOF
import qualified Test.Botan.Low.ZFEC
import Test.Tasty
import Test.Tasty.HUnit
Expand Down Expand Up @@ -76,6 +77,7 @@ tests = do
totpTests <- Test.Botan.Low.TOTP.tests
utilityTests <- Test.Botan.Low.Utility.tests
x509Tests <- Test.Botan.Low.X509.tests
xofTests <- Test.Botan.Low.XOF.tests
zfecTests <- Test.Botan.Low.ZFEC.tests
pure $ testGroup "botan-low" [
bcryptTests
Expand Down Expand Up @@ -117,5 +119,6 @@ tests = do
, utilityTests
, Test.Botan.Low.Version.tests
, x509Tests
, xofTests
, zfecTests
]
23 changes: 19 additions & 4 deletions botan-low/test/Test/Botan/Low/Hash.hs
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,28 @@ spec_hash = testSuite allHashes chars $ \ h -> do
pass
it "can copy the internal state" $ do
ctx <- hashInit h
-- TODO: Populate with state and actually prove
_ctx' <- hashCopyState ctx
pass
-- Populate with state
hashUpdate ctx message
-- Copy and further populate the state
ctx' <- hashCopyState ctx
hashUpdate ctx' message
d' <- hashFinal ctx'
-- Further populate the original state equally
hashUpdate ctx message
d <- hashFinal ctx
-- Check if both states match by comparing their digests
d' `shouldBe` d
it "can clear all internal state" $ do
ctx <- hashInit h
-- TODO: Populate with state and actually prove
-- Populate with state
hashUpdate ctx message
d <- hashFinal ctx
-- Clear and repopulate with state
hashClear ctx
hashUpdate ctx message
d' <- hashFinal ctx
-- Check if states match after clearing
d' `shouldBe` d
pass
it "can update the internal state with a single message block" $ do
ctx <- hashInit h
Expand Down
Loading