packages feed

libsecp256k1-0.0.1: src/Crypto/Secp256k1.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}

-- |
-- Module      : Crypto.Secp256k1
-- License     : UNLICENSE
-- Maintainer  : Keagan McClelland <keagan.mcclelland@gmail.com>
-- Stability   : experimental
-- Portability : POSIX
--
-- Crytpographic functions from Bitcoin’s secp256k1 library.
module Crypto.Secp256k1 (
    -- * Messages
    Msg,
    msg,
    getMsg,

    -- * Secret Keys
    SecKey,
    secKey,
    getSecKey,
    derivePubKey,

    -- * Public Keys
    PubKey,
    importPubKey,
    exportPubKey,

    -- * Signatures
    Sig,
    signMsg,
    verifySig,
    normalizeSig,

    -- ** DER
    importSig,
    exportSig,

    -- ** Compact
    CompactSig,
    getCompactSig,
    compactSig,
    exportCompactSig,
    importCompactSig,

    -- * Addition & Multiplication
    Tweak,
    tweak,
    getTweak,
    tweakAddSecKey,
    tweakMulSecKey,
    tweakAddPubKey,
    tweakMulPubKey,
    combinePubKeys,
) where

import Control.DeepSeq (NFData)
import Control.Monad (replicateM, unless, (<=<))
import qualified Crypto.Secp256k1.Prim as Prim
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Base16 as B16
import Data.Hashable (Hashable (..))
import Data.Maybe (fromJust, fromMaybe, isJust)
import Data.Serialize (
    Serialize (..),
    getByteString,
    putByteString,
 )
import Data.String (IsString (..))
import Data.String.Conversions (ConvertibleStrings, cs)
import Foreign (
    alloca,
    allocaArray,
    allocaBytes,
    free,
    mallocBytes,
    nullFunPtr,
    nullPtr,
    peek,
    poke,
    pokeArray,
 )
import GHC.Generics (Generic)
import System.IO.Unsafe (unsafePerformIO)
import Test.QuickCheck (
    Arbitrary (..),
    arbitraryBoundedRandom,
    suchThat,
 )
import Text.Read (
    Lexeme (String),
    lexP,
    parens,
    pfail,
    readPrec,
 )


newtype PubKey = PubKey {getPubKey :: ByteString}
    deriving (Eq, Generic, NFData)
newtype Msg = Msg {getMsg :: ByteString}
    deriving (Eq, Generic, NFData)
newtype Sig = Sig {getSig :: ByteString}
    deriving (Eq, Generic, NFData)
newtype SecKey = SecKey {getSecKey :: ByteString}
    deriving (Eq, Generic, NFData)
newtype Tweak = Tweak {getTweak :: ByteString}
    deriving (Eq, Generic, NFData)
newtype CompactSig = CompactSig {getCompactSig :: ByteString}
    deriving (Eq, Generic, NFData)


instance Serialize PubKey where
    put (PubKey bs) = putByteString bs
    get = PubKey <$> getByteString 64


instance Serialize Msg where
    put (Msg m) = putByteString m
    get = Msg <$> getByteString 32


instance Serialize Sig where
    put (Sig bs) = putByteString bs
    get = Sig <$> getByteString 64


instance Serialize SecKey where
    put (SecKey bs) = putByteString bs
    get = SecKey <$> getByteString 32


instance Serialize Tweak where
    put (Tweak bs) = putByteString bs
    get = Tweak <$> getByteString 32


instance Serialize CompactSig where
    put (CompactSig bs) = putByteString bs
    get = CompactSig <$> getByteString 64


decodeHex :: ConvertibleStrings a ByteString => a -> Maybe ByteString
decodeHex str = case B16.decodeBase16 $ cs str of
    Right bs -> Just bs
    Left _ -> Nothing


instance Read PubKey where
    readPrec = do
        String str <- lexP
        maybe pfail return $ importPubKey =<< decodeHex str


instance Hashable PubKey where
    i `hashWithSalt` k = i `hashWithSalt` exportPubKey True k


instance IsString PubKey where
    fromString = fromMaybe e . (importPubKey <=< decodeHex)
        where
            e = error "Could not decode public key from hex string"


instance Show PubKey where
    showsPrec _ = shows . B16.encodeBase16 . exportPubKey True


instance Read Msg where
    readPrec = parens $ do
        String str <- lexP
        maybe pfail return $ msg =<< decodeHex str


instance Hashable Msg where
    i `hashWithSalt` m = i `hashWithSalt` getMsg m


instance IsString Msg where
    fromString = fromMaybe e . (msg <=< decodeHex)
        where
            e = error "Could not decode message from hex string"


instance Show Msg where
    showsPrec _ = shows . B16.encodeBase16 . getMsg


instance Read Sig where
    readPrec = parens $ do
        String str <- lexP
        maybe pfail return $ importSig =<< decodeHex str


instance IsString Sig where
    fromString = fromMaybe e . (importSig <=< decodeHex)
        where
            e = error "Could not decode signature from hex string"


instance Hashable Sig where
    i `hashWithSalt` s = i `hashWithSalt` exportSig s


instance Show Sig where
    showsPrec _ = shows . B16.encodeBase16 . exportSig


instance Read SecKey where
    readPrec = parens $ do
        String str <- lexP
        maybe pfail return $ secKey =<< decodeHex str


instance Hashable SecKey where
    i `hashWithSalt` k = i `hashWithSalt` getSecKey k


instance IsString SecKey where
    fromString = fromMaybe e . (secKey <=< decodeHex)
        where
            e = error "Colud not decode secret key from hex string"


instance Show SecKey where
    showsPrec _ = shows . B16.encodeBase16 . getSecKey


instance Hashable Tweak where
    i `hashWithSalt` t = i `hashWithSalt` getTweak t


instance Read Tweak where
    readPrec = parens $ do
        String str <- lexP
        maybe pfail return $ tweak =<< decodeHex str


instance IsString Tweak where
    fromString = fromMaybe e . (tweak <=< decodeHex)
        where
            e = error "Could not decode tweak from hex string"


instance Show Tweak where
    showsPrec _ = shows . B16.encodeBase16 . getTweak


-- | Import 32-byte 'ByteString' as 'Msg'.
msg :: ByteString -> Maybe Msg
msg bs
    | BS.length bs == 32 = Just (Msg bs)
    | otherwise = Nothing


-- | Import 32-byte 'ByteString' as 'SecKey'.
secKey :: ByteString -> Maybe SecKey
secKey bs
    | BS.length bs == 32 = Just (SecKey bs)
    | otherwise = Nothing


compactSig :: ByteString -> Maybe CompactSig
compactSig bs
    | BS.length bs == 64 = Just (CompactSig bs)
    | otherwise = Nothing


-- | Convert signature to a normalized lower-S form. 'Nothing' indicates that it
-- was already normal.
normalizeSig :: Sig -> Maybe Sig
normalizeSig (Sig sig) = unsafePerformIO $
    Prim.unsafeUseByteString sig $ \(sig_in, _) -> do
        sig_out <- mallocBytes 64
        ret <- Prim.ecdsaSignatureNormalize Prim.ctx sig_out sig_in
        if Prim.isSuccess ret
            then do
                bs <- Prim.unsafePackByteString (sig_out, 64)
                return (Just (Sig bs))
            else do
                free sig_out
                return Nothing


-- | 32-Byte 'ByteString' as 'Tweak'.
tweak :: ByteString -> Maybe Tweak
tweak bs
    | BS.length bs == 32 = Just (Tweak bs)
    | otherwise = Nothing


-- | Import DER-encoded public key.
importPubKey :: ByteString -> Maybe PubKey
importPubKey bs
    | BS.null bs = Nothing
    | otherwise = unsafePerformIO $
        Prim.unsafeUseByteString bs $ \(input, len) -> do
            pub_key <- mallocBytes 64
            ret <- Prim.ecPubkeyParse Prim.ctx pub_key input len
            if Prim.isSuccess ret
                then do
                    out <- Prim.unsafePackByteString (pub_key, 64)
                    return (Just (PubKey out))
                else do
                    free pub_key
                    return Nothing


-- | Encode public key as DER. First argument 'True' for compressed output.
exportPubKey :: Bool -> PubKey -> ByteString
exportPubKey compress (PubKey in_bs) = unsafePerformIO $
    Prim.unsafeUseByteString in_bs $ \(in_ptr, _) ->
        alloca $ \len_ptr ->
            allocaBytes len $ \out_ptr -> do
                poke len_ptr $ fromIntegral len
                ret <- Prim.ecPubkeySerialize Prim.ctx out_ptr len_ptr in_ptr flags
                unless (Prim.isSuccess ret) $ error "could not serialize public key"
                final_len <- peek len_ptr
                Prim.packByteString (out_ptr, final_len)
    where
        len = if compress then 33 else 65
        flags = if compress then Prim.compressed else Prim.uncompressed


exportCompactSig :: Sig -> CompactSig
exportCompactSig (Sig sig_bs) = unsafePerformIO $
    Prim.unsafeUseByteString sig_bs $ \(sig_ptr, _) -> do
        out_ptr <- mallocBytes 64
        ret <- Prim.ecdsaSignatureSerializeCompact Prim.ctx out_ptr sig_ptr
        unless (Prim.isSuccess ret) $ do
            free out_ptr
            error "Could not obtain compact signature"
        out_bs <- Prim.unsafePackByteString (out_ptr, 64)
        return $ CompactSig out_bs


importCompactSig :: CompactSig -> Maybe Sig
importCompactSig (CompactSig compact_sig) = unsafePerformIO $
    Prim.unsafeUseByteString compact_sig $ \(compact_ptr, _) -> do
        out_sig <- mallocBytes 64
        ret <- Prim.ecdsaSignatureParseCompact Prim.ctx out_sig compact_ptr
        if Prim.isSuccess ret
            then do
                out_bs <- Prim.unsafePackByteString (out_sig, 64)
                return (Just (Sig out_bs))
            else do
                free out_sig
                return Nothing


-- | Import DER-encoded signature.
importSig :: ByteString -> Maybe Sig
importSig bs
    | BS.null bs = Nothing
    | otherwise = unsafePerformIO $
        Prim.unsafeUseByteString bs $ \(in_ptr, in_len) -> do
            out_sig <- mallocBytes 64
            ret <- Prim.ecdsaSignatureParseDer Prim.ctx out_sig in_ptr in_len
            if Prim.isSuccess ret
                then do
                    out_bs <- Prim.unsafePackByteString (out_sig, 64)
                    return (Just (Sig out_bs))
                else do
                    free out_sig
                    return Nothing


-- | Encode signature as strict DER.
exportSig :: Sig -> ByteString
exportSig (Sig in_sig) = unsafePerformIO $
    Prim.unsafeUseByteString in_sig $ \(in_ptr, _) ->
        alloca $ \out_len ->
            allocaBytes 72 $ \out_ptr -> do
                poke out_len 72
                ret <- Prim.ecdsaSignatureSerializeDer Prim.ctx out_ptr out_len in_ptr
                unless (Prim.isSuccess ret) $ error "could not serialize signature"
                final_len <- peek out_len
                Prim.packByteString (out_ptr, final_len)


-- | Verify message signature. 'True' means that the signature is correct.
verifySig :: PubKey -> Sig -> Msg -> Bool
verifySig (PubKey pub_key) (Sig sig) (Msg m) = unsafePerformIO $
    Prim.unsafeUseByteString pub_key $ \(pub_key_ptr, _) ->
        Prim.unsafeUseByteString sig $ \(sig_ptr, _) ->
            Prim.unsafeUseByteString m $ \(msg_ptr, _) ->
                Prim.isSuccess <$> Prim.ecdsaVerify Prim.ctx sig_ptr msg_ptr pub_key_ptr


signMsg :: SecKey -> Msg -> Sig
signMsg (SecKey sec_key) (Msg m) = unsafePerformIO $
    Prim.unsafeUseByteString sec_key $ \(sec_key_ptr, _) ->
        Prim.unsafeUseByteString m $ \(msg_ptr, _) -> do
            sig_ptr <- mallocBytes 64
            ret <- Prim.ecdsaSign Prim.ctx sig_ptr msg_ptr sec_key_ptr nullFunPtr nullPtr
            unless (Prim.isSuccess ret) $ do
                free sig_ptr
                error "could not sign message"
            Sig <$> Prim.unsafePackByteString (sig_ptr, 64)


derivePubKey :: SecKey -> PubKey
derivePubKey (SecKey sec_key) = unsafePerformIO $
    Prim.unsafeUseByteString sec_key $ \(sec_key_ptr, _) -> do
        pub_key_ptr <- mallocBytes 64
        ret <- Prim.ecPubkeyCreate Prim.ctx pub_key_ptr sec_key_ptr
        unless (Prim.isSuccess ret) $ do
            free pub_key_ptr
            error "could not compute public key"
        PubKey <$> Prim.unsafePackByteString (pub_key_ptr, 64)


-- | Add tweak to secret key.
tweakAddSecKey :: SecKey -> Tweak -> Maybe SecKey
tweakAddSecKey (SecKey sec_key) (Tweak t) = unsafePerformIO $
    Prim.unsafeUseByteString new_bs $ \(sec_key_ptr, _) ->
        Prim.unsafeUseByteString t $ \(tweak_ptr, _) -> do
            ret <- Prim.ecSeckeyTweakAdd Prim.ctx sec_key_ptr tweak_ptr
            if Prim.isSuccess ret
                then return (Just (SecKey new_bs))
                else return Nothing
    where
        new_bs = BS.copy sec_key


-- | Multiply secret key by tweak.
tweakMulSecKey :: SecKey -> Tweak -> Maybe SecKey
tweakMulSecKey (SecKey sec_key) (Tweak t) = unsafePerformIO $
    Prim.unsafeUseByteString new_bs $ \(sec_key_ptr, _) ->
        Prim.unsafeUseByteString t $ \(tweak_ptr, _) -> do
            ret <- Prim.ecSeckeyTweakMul Prim.ctx sec_key_ptr tweak_ptr
            if Prim.isSuccess ret
                then return (Just (SecKey new_bs))
                else return Nothing
    where
        new_bs = BS.copy sec_key


-- | Add tweak to public key. Tweak is multiplied first by G to obtain a point.
tweakAddPubKey :: PubKey -> Tweak -> Maybe PubKey
tweakAddPubKey (PubKey pub_key) (Tweak t) = unsafePerformIO $
    Prim.unsafeUseByteString new_bs $ \(pub_key_ptr, _) ->
        Prim.unsafeUseByteString t $ \(tweak_ptr, _) -> do
            ret <- Prim.ecPubkeyTweakAdd Prim.ctx pub_key_ptr tweak_ptr
            if Prim.isSuccess ret
                then return (Just (PubKey new_bs))
                else return Nothing
    where
        new_bs = BS.copy pub_key


-- | Multiply public key by tweak. Tweak is multiplied first by G to obtain a
-- point.
tweakMulPubKey :: PubKey -> Tweak -> Maybe PubKey
tweakMulPubKey (PubKey pub_key) (Tweak t) = unsafePerformIO $
    Prim.unsafeUseByteString new_bs $ \(pub_key_ptr, _) ->
        Prim.unsafeUseByteString t $ \(tweak_ptr, _) -> do
            ret <- Prim.ecPubkeyTweakMul Prim.ctx pub_key_ptr tweak_ptr
            if Prim.isSuccess ret
                then return (Just (PubKey new_bs))
                else return Nothing
    where
        new_bs = BS.copy pub_key


-- | Add multiple public keys together.
combinePubKeys :: [PubKey] -> Maybe PubKey
combinePubKeys [] = Nothing
combinePubKeys pubs = unsafePerformIO $
    pointers [] pubs $ \ps ->
        allocaArray (length ps) $ \a -> do
            out <- mallocBytes 64
            pokeArray a ps
            ret <- Prim.ecPubkeyCombine Prim.ctx out a (fromIntegral $ length ps)
            if Prim.isSuccess ret
                then do
                    bs <- Prim.unsafePackByteString (out, 64)
                    return (Just (PubKey bs))
                else do
                    free out
                    return Nothing
    where
        pointers ps [] f = f ps
        pointers ps (PubKey pub_key : pub_keys) f =
            Prim.unsafeUseByteString pub_key $ \(p, _) ->
                pointers (p : ps) pub_keys f


instance Arbitrary Msg where
    arbitrary = gen_msg
        where
            valid_bs = bs_gen `suchThat` isJust
            bs_gen = msg . BS.pack <$> replicateM 32 arbitraryBoundedRandom
            gen_msg = fromJust <$> valid_bs


instance Arbitrary SecKey where
    arbitrary = gen_key
        where
            valid_bs = bs_gen `suchThat` isJust
            bs_gen = secKey . BS.pack <$> replicateM 32 arbitraryBoundedRandom
            gen_key = fromJust <$> valid_bs


instance Arbitrary PubKey where
    arbitrary = derivePubKey <$> arbitrary