musig2-0.3.1: internal/Crypto/Curve/Secp256k1/MuSig2/Internal/Hash.hs
{-# LANGUAGE OverloadedStrings #-}
{- |
Module: Crypto.Curve.Secp256k1.MuSig2.Internal.Hash
Copyright: (c) 2025 Jose Storopoli
License: MIT
Maintainer: Jose Storopoli <jose@storopoli.com>
The BIP-340 tagged hashes used by BIP-327 and BIP-341, with the tag prefix
\(SHA256(tag) \| SHA256(tag)\) computed once per tag.
Each hash takes its input as a list of chunks, hashed as their
concatenation. Inputs shorter than 'streamThreshold' bytes are joined and
hashed with the hardware-accelerated @hash@ of @ppad-sha256@; longer ones
(long messages or extra inputs) are hashed chunk by chunk with @hash_lazy@,
so the library never copies a long message to hash it.
Not part of the public API.
-}
module Crypto.Curve.Secp256k1.MuSig2.Internal.Hash (
taggedHash,
streamThreshold,
hashKeyAggList,
hashKeyAggCoef,
hashNonceCoef,
hashChallenge,
hashAux,
hashNonce,
hashDetNonce,
hashTapTweak,
) where
import Crypto.Hash.SHA256 (hash, hash_lazy)
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BL
{- | @hash_tag(x) = SHA256(SHA256(tag) || SHA256(tag) || x)@, where \(x\) is
the concatenation of the chunks.
Partially apply it to a tag to share the prefix between calls.
-}
taggedHash :: ByteString -> [ByteString] -> ByteString
taggedHash tag = \xs ->
let chunks = prefix : xs
in if sum (map BS.length xs) < streamThreshold
then hash (BS.concat chunks)
else hash_lazy (BL.fromChunks chunks)
where
th = hash tag
prefix = th <> th
{- | Inputs of at least this many bytes (64 KiB) are hashed chunk by chunk
instead of being joined first.
Below it, joining costs less than the slower chunked hash; above it, the
copy would dominate memory use.
-}
streamThreshold :: Int
streamThreshold = 65536
-- | @hash_KeyAgg list@.
hashKeyAggList :: [ByteString] -> ByteString
hashKeyAggList = taggedHash "KeyAgg list"
{-# NOINLINE hashKeyAggList #-}
-- | @hash_KeyAgg coefficient@.
hashKeyAggCoef :: [ByteString] -> ByteString
hashKeyAggCoef = taggedHash "KeyAgg coefficient"
{-# NOINLINE hashKeyAggCoef #-}
-- | @hash_MuSig/noncecoef@.
hashNonceCoef :: [ByteString] -> ByteString
hashNonceCoef = taggedHash "MuSig/noncecoef"
{-# NOINLINE hashNonceCoef #-}
-- | @hash_BIP0340/challenge@.
hashChallenge :: [ByteString] -> ByteString
hashChallenge = taggedHash "BIP0340/challenge"
{-# NOINLINE hashChallenge #-}
-- | @hash_MuSig/aux@.
hashAux :: [ByteString] -> ByteString
hashAux = taggedHash "MuSig/aux"
{-# NOINLINE hashAux #-}
-- | @hash_MuSig/nonce@.
hashNonce :: [ByteString] -> ByteString
hashNonce = taggedHash "MuSig/nonce"
{-# NOINLINE hashNonce #-}
-- | @hash_MuSig/deterministic/nonce@.
hashDetNonce :: [ByteString] -> ByteString
hashDetNonce = taggedHash "MuSig/deterministic/nonce"
{-# NOINLINE hashDetNonce #-}
-- | @hash_TapTweak@ (BIP-341).
hashTapTweak :: [ByteString] -> ByteString
hashTapTweak = taggedHash "TapTweak"
{-# NOINLINE hashTapTweak #-}