packages feed

musig2-0.1.0: test/Util.hs

{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# OPTIONS_GHC -Wno-orphans #-}

module Util (parsePoint, parseScalar, parsePubNonce, extractXOnly, decodeHex, Rand32 (..), Scalar (..)) where

import Crypto.Curve.Secp256k1 (Projective, Pub, mul, parse_point, serialize_point, _CURVE_G, _CURVE_Q, _CURVE_ZERO)
import Crypto.Curve.Secp256k1.MuSig2 (PubNonce (..), SecKey (..), SecNonce (..), SecNonceGenParams (..), Tweak (..))
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Base16 as B16
import Data.Maybe (fromJust)
import Test.Tasty.QuickCheck (Arbitrary (..), Gen, choose, frequency, vectorOf)

{- | Parses a 'ByteString' into a 'Pub'key.

This is a test YOLO function that blows up on your face if you don't
supply proper string representations.
-}
parsePoint :: ByteString -> Pub
parsePoint s = case B16.decode s of
  Left p -> error $ "cannot decode point" <> show p
  Right p -> (fromJust . parse_point) p

-- | Parses a hex 'ByteString' into an 'Integer' scalar.
parseScalar :: ByteString -> Integer
parseScalar = BS.foldl' (\acc b -> acc * 256 + fromIntegral b) 0 . decodeHex

-- | Extracts X-coordinate from compressed point serialization.
extractXOnly :: Pub -> ByteString
extractXOnly = BS.drop 1 . serialize_point

-- | Decodes hex string to 'ByteString'.
decodeHex :: ByteString -> ByteString
decodeHex h = case B16.decode h of
  Right bs -> bs
  Left _ -> error "Invalid hex string in test vector"

{- | 'Arbitrary' instance for 'Projective'.

Generate points as scalar multiples of the generator,
including the identity with low probability (1%).
-}
instance Arbitrary Projective where
  arbitrary :: Gen Projective
  arbitrary =
    frequency
      [ (1, return _CURVE_ZERO) -- Include identity occasionally
      ,
        ( 99
        , do
            scalar <- choose (0, _CURVE_Q)
            return (mul _CURVE_G scalar)
        )
      ]

-- | Custom 'Gen' for 'ByteString' that generates maximum length of 1,024.
arbitraryBS :: Gen ByteString
arbitraryBS = do
  len <- choose (0, 1024) :: Gen Int -- Limit size to avoid excessive memory use
  BS.pack <$> vectorOf len arbitrary

-- | 'Arbitrary' instance for 'ByteString'.
instance Arbitrary ByteString where
  arbitrary = arbitraryBS

-- | Scalar type for testing secret keys.
newtype Scalar = Scalar Integer deriving (Show, Eq)

-- | 'Arbitrary' instance for 'Scalar' to be within curve order.
instance Arbitrary Scalar where
  arbitrary = Scalar <$> choose (1, _CURVE_Q - 1)

-- | 32-byte 'ByteString' for testing hashes.
newtype Rand32 = Rand32 ByteString deriving (Show, Eq)

-- | 'Arbitrary' instance for 'Rand32'.
instance Arbitrary Rand32 where
  arbitrary = Rand32 . BS.pack <$> vectorOf 32 arbitrary

{- | 'Arbitrary' instance for 'SecNonceGenParams'.

Slightly biased towards 'Just' than 'Nothing'.
-}
instance Arbitrary SecNonceGenParams where
  arbitrary = do
    _pk <- arbitrary
    _sk <- frequency [(2, return Nothing), (3, genMaybeSecKey)]
    _aggpk <- frequency [(2, return Nothing), (3, Just <$> arbitrary)]
    _msg <- frequency [(2, return Nothing), (3, Just <$> arbitraryBS)]
    _extraIn <- frequency [(2, return Nothing), (3, Just <$> arbitraryBS)]
    return SecNonceGenParams{..}
   where
    genMaybeSecKey :: Gen (Maybe SecKey)
    genMaybeSecKey = do
      (Scalar i) <- arbitrary
      return $ Just (SecKey i)

{- | 'Show' instance for 'SecNonceGenParams'.

Should only used for testing purposes, hence why it is only defined in this test module.
-}
instance Show SecNonceGenParams where
  show (SecNonceGenParams _pk _sk _aggpk _msg _extraIn) =
    let showMaybeBS mb = case mb of
          Nothing -> "Nothing"
          Just bs -> "Just (ByteString of length " ++ show (BS.length bs) ++ ")"
        showSk msk = case msk of
          Nothing -> "Nothing"
          Just sk -> "Just " ++ show sk
     in "SecNonceGenParams {_pk = "
          ++ show _pk
          ++ ", _sk = "
          ++ showSk _sk
          ++ ", _aggpk = "
          ++ show _aggpk
          ++ ", _msg = "
          ++ showMaybeBS _msg
          ++ ", _extraIn = "
          ++ showMaybeBS _extraIn
          ++ "}"

-- | 'Arbitrary' instance for 'PubNonce'.
instance Arbitrary PubNonce where
  arbitrary :: Gen PubNonce
  arbitrary = do
    r1' <- arbitrary
    r2' <- arbitrary
    return $ PubNonce{r1 = r1', r2 = r2'}

-- | 'Arbitrary' instace of 'SecNonce'.
instance Arbitrary SecNonce where
  arbitrary = do
    k1 <- choose (1, _CURVE_Q - 1) -- Ensure non-zero
    k2 <- choose (1, _CURVE_Q - 1) -- Ensure non-zero
    return SecNonce{k1 = k1, k2 = k2}

{- | 'Show' instance for 'SecNonce'.

Should only used for testing purposes, hence why it is only defined in this test module.
-}
instance Show SecNonce where
  show (SecNonce k1 k2) =
    "SecNonce { k1=" ++ show k1 ++ ", k2=" ++ show k2 ++ "}"

-- | 'Arbitrary' instace of 'SecKey'.
instance Arbitrary SecKey where
  arbitrary = do
    sk <- choose (1, _CURVE_Q - 1) -- Ensure non-zero
    return (SecKey sk)

{- | 'Show' instance for 'SecKey'.

Should only used for testing purposes, hence why it is only defined in this test module.
-}
instance Show SecKey where
  show (SecKey int) = "SecKey " ++ show int

-- | 'Arbitrary' instance for 'Tweak'.
instance Arbitrary Tweak where
  arbitrary = do
    tweakValue <- choose (1, _CURVE_Q - 1) -- Ensure valid tweak value
    isXOnly <- arbitrary
    return $ if isXOnly then XOnlyTweak tweakValue else PlainTweak tweakValue

{- | Parses a 'ByteString' into a 'PubNonce'.

Mostly used to parse BIP327 test vectors.
-}
parsePubNonce :: ByteString -> PubNonce
parsePubNonce bs = PubNonce{r1 = r1', r2 = r2'}
 where
  r1' = parsePoint $ BS.take 66 bs
  r2' = parsePoint $ BS.drop 66 bs