secp256k1-haskell (empty) → 0.1.0
raw patch · 10 files changed
+1719/−0 lines, 10 filesdep +HUnitdep +QuickCheckdep +basesetup-changed
Dependencies added: HUnit, QuickCheck, base, base16-bytestring, bytestring, cereal, cryptohash, entropy, hspec, mtl, secp256k1-haskell, string-conversions
Files
- CHANGELOG.md +31/−0
- README.md +3/−0
- Setup.hs +2/−0
- UNLICENSE +24/−0
- secp256k1-haskell.cabal +76/−0
- src/Crypto/Secp256k1.hs +449/−0
- src/Crypto/Secp256k1/Internal.hs +473/−0
- test/Crypto/Secp256k1/InternalSpec.hs +417/−0
- test/Crypto/Secp256k1Spec.hs +243/−0
- test/Spec.hs +1/−0
+ CHANGELOG.md view
@@ -0,0 +1,31 @@+# Changelog+All notable changes to this project will be documented in this file.++The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)+and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).++## 1.1.2+### Changed+- Simplify `Show` and `Read` instances.++## 1.1.1+### Removed+- Old obvious installation instructions from `README.md` file.++### Changed+- Fix arbitrary instances such that unwanted colissions are less likely.++## 1.1.0+### Removed+- Lax parsing for DER.+- DER parsing of secret keys.+- Embedded code for library.++## 1.0.0 [YANKED]+### Added+- Changelog.++### Changed+- `Show`/`Read` instances now isomorphic to Haskell code.+- Semantic versioning.+- `Hspec` tests.
+ README.md view
@@ -0,0 +1,3 @@+# Haskell bindings for secp256k1++This project contains Haskell bindings for the [secp256k1](https://github.com/bitcoin-core/secp256k1) library from the Bitcoin Core project.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ UNLICENSE view
@@ -0,0 +1,24 @@+This is free and unencumbered software released into the public domain.++Anyone is free to copy, modify, publish, use, compile, sell, or+distribute this software, either in source code form or as a compiled+binary, for any purpose, commercial or non-commercial, and by any+means.++In jurisdictions that recognize copyright laws, the author or authors+of this software dedicate any and all copyright interest in the+software to the public domain. We make this dedication for the benefit+of the public at large and to the detriment of our heirs and+successors. We intend this dedication to be an overt act of+relinquishment in perpetuity of all present and future rights to this+software under copyright law.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR+OTHER DEALINGS IN THE SOFTWARE.++For more information, please refer to <http://unlicense.org/>
+ secp256k1-haskell.cabal view
@@ -0,0 +1,76 @@+-- This file has been generated from package.yaml by hpack version 0.28.2.+--+-- see: https://github.com/sol/hpack+--+-- hash: 5fa20b19c25e16beba80f187b074b53c7d0bef071771bc3875c6de4afba11a33++name: secp256k1-haskell+version: 0.1.0+synopsis: Bindings for secp256k1 library from Bitcoin Core+description: Sign and verify signatures using the very fast C secp256k1 library from Pieter Wuille. Has Haskell types and abstractions for keys and signatures.+category: Crypto+homepage: http://github.com/haskoin/secp256k1-haskell#readme+bug-reports: https://github.com/haskoin/secp256k1-haskell.git/issues+author: Jean-Pierre Rupp+maintainer: xenog@protonmail.com+copyright: (c) 2017 Jean-Pierre Rupp+license: PublicDomain+license-file: UNLICENSE+build-type: Simple+cabal-version: >= 1.10+extra-source-files:+ CHANGELOG.md+ README.md++source-repository head+ type: git+ location: https://github.com/haskoin/secp256k1-haskell.git++library+ exposed-modules:+ Crypto.Secp256k1+ Crypto.Secp256k1.Internal+ other-modules:+ Paths_secp256k1_haskell+ hs-source-dirs:+ src+ extra-libraries:+ secp256k1+ build-depends:+ HUnit+ , QuickCheck+ , base >=4.8 && <5+ , base16-bytestring+ , bytestring+ , cereal+ , cryptohash+ , entropy+ , hspec+ , mtl+ , string-conversions+ default-language: Haskell2010++test-suite spec+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ other-modules:+ Crypto.Secp256k1.InternalSpec+ Crypto.Secp256k1Spec+ Paths_secp256k1_haskell+ hs-source-dirs:+ test+ build-depends:+ HUnit+ , QuickCheck+ , base >=4.8 && <5+ , base16-bytestring+ , bytestring+ , cereal+ , cryptohash+ , entropy+ , hspec ==2.*+ , mtl+ , secp256k1-haskell+ , string-conversions+ default-language: Haskell2010+ build-tool-depends: hspec-discover:hspec-discover == 2.*
+ src/Crypto/Secp256k1.hs view
@@ -0,0 +1,449 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-|+Module : Crypto.Secp256k1+License : MIT+Maintainer : Jean-Pierre Rupp <root@haskoin.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(..)+ , exportCompactSig+ , importCompactSig+ -- * Recoverable+ , RecSig+ , CompactRecSig(..)+ , importCompactRecSig+ , exportCompactRecSig+ , convertRecSig+ , signRecMsg+ , recover++ -- * Addition & Multiplication+ , Tweak+ , tweak+ , getTweak+ , tweakAddSecKey+ , tweakMulSecKey+ , tweakAddPubKey+ , tweakMulPubKey+ , combinePubKeys+ ) where++import Control.Monad+import Crypto.Secp256k1.Internal+import Data.Serialize+import Data.ByteString (ByteString)+import qualified Data.ByteString as BS+import qualified Data.ByteString.Base16 as B16+import Data.ByteString.Short (fromShort, toShort)+import Data.Maybe+import Data.String+import Data.String.Conversions+import Foreign+import System.IO.Unsafe+import Test.QuickCheck+import Text.Read++newtype PubKey = PubKey (ForeignPtr PubKey64)+newtype Msg = Msg (ForeignPtr Msg32)+newtype Sig = Sig (ForeignPtr Sig64)+newtype SecKey = SecKey (ForeignPtr SecKey32)+newtype Tweak = Tweak (ForeignPtr Tweak32)+newtype RecSig = RecSig (ForeignPtr RecSig65)++decodeHex :: ConvertibleStrings a ByteString => a -> Maybe ByteString+decodeHex str = if BS.null r then Just bs else Nothing where+ (bs, r) = B16.decode $ cs str++instance Read PubKey where+ readPrec = do+ String str <- lexP+ maybe pfail return $ importPubKey =<< decodeHex str++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.encode . exportPubKey True++instance Read Msg where+ readPrec = parens $ do+ String str <- lexP+ maybe pfail return $ msg =<< decodeHex str++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.encode . 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 Show Sig where+ showsPrec _ = shows . B16.encode . exportSig++recSigFromString :: String -> Maybe RecSig+recSigFromString str = do+ bs <- decodeHex str+ rs <- either (const Nothing) Just $ decode bs+ importCompactRecSig rs++instance Read RecSig where+ readPrec = parens $ do+ String str <- lexP+ maybe pfail return $ recSigFromString str++instance IsString RecSig where+ fromString = fromMaybe e . recSigFromString+ where+ e = error "Could not decode signature from hex string"++instance Show RecSig where+ showsPrec _ = shows . B16.encode . encode . exportCompactRecSig++instance Read SecKey where+ readPrec = parens $ do+ String str <- lexP+ maybe pfail return $ secKey =<< decodeHex str++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.encode . getSecKey++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.encode . getTweak++instance Eq PubKey where+ fp1 == fp2 = getPubKey fp1 == getPubKey fp2++instance Eq Msg where+ fm1 == fm2 = getMsg fm1 == getMsg fm2++instance Eq Sig where+ fg1 == fg2 = exportCompactSig fg1 == exportCompactSig fg2++instance Eq RecSig where+ fg1 == fg2 = exportCompactRecSig fg1 == exportCompactRecSig fg2++instance Eq SecKey where+ fk1 == fk2 = getSecKey fk1 == getSecKey fk2++instance Eq Tweak where+ ft1 == ft2 = getTweak ft1 == getTweak ft2++-- | Import 32-byte 'ByteString' as 'Msg'.+msg :: ByteString -> Maybe Msg+msg bs+ | BS.length bs == 32 = unsafePerformIO $ do+ fp <- mallocForeignPtr+ withForeignPtr fp $ flip poke (Msg32 (toShort bs))+ return $ Just $ Msg fp+ | otherwise = Nothing++-- | Import 32-byte 'ByteString' as 'SecKey'.+secKey :: ByteString -> Maybe SecKey+secKey bs+ | BS.length bs == 32 = withContext $ \ctx -> do+ fp <- mallocForeignPtr+ ret <- withForeignPtr fp $ \p -> do+ poke p (SecKey32 (toShort bs))+ ecSecKeyVerify ctx p+ if isSuccess ret+ then return $ Just $ SecKey fp+ else return Nothing+ | otherwise = Nothing++-- | Convert signature to a normalized lower-S form. Boolean value 'True'+-- indicates that the signature changed, 'False' indicates that it was already+-- normal.+normalizeSig :: Sig -> (Sig, Bool)+normalizeSig (Sig fg) = withContext $ \ctx -> do+ fg' <- mallocForeignPtr+ ret <- withForeignPtr fg $ \pg -> withForeignPtr fg' $ \pg' ->+ ecdsaSignatureNormalize ctx pg' pg+ return (Sig fg', isSuccess ret)++-- | 32-Byte 'ByteString' as 'Tweak'.+tweak :: ByteString -> Maybe Tweak+tweak bs+ | BS.length bs == 32 = unsafePerformIO $ do+ fp <- mallocForeignPtr+ withForeignPtr fp $ flip poke (Tweak32 (toShort bs))+ return $ Just $ Tweak fp+ | otherwise = Nothing++-- | Get 32-byte secret key.+getSecKey :: SecKey -> ByteString+getSecKey (SecKey fk) =+ fromShort $ getSecKey32 $ unsafePerformIO $ withForeignPtr fk peek++-- Get 64-byte public key.+getPubKey :: PubKey -> ByteString+getPubKey (PubKey fp) =+ fromShort $ getPubKey64 $ unsafePerformIO $ withForeignPtr fp peek++-- | Get 32-byte message.+getMsg :: Msg -> ByteString+getMsg (Msg fm) =+ fromShort $ getMsg32 $ unsafePerformIO $ withForeignPtr fm peek++-- | Get 32-byte tweak.+getTweak :: Tweak -> ByteString+getTweak (Tweak ft) =+ fromShort $ getTweak32 $ unsafePerformIO $ withForeignPtr ft peek++-- | Import DER-encoded public key.+importPubKey :: ByteString -> Maybe PubKey+importPubKey bs = withContext $ \ctx -> useByteString bs $ \(b, l) -> do+ fp <- mallocForeignPtr+ ret <- withForeignPtr fp $ \p -> ecPubKeyParse ctx p b l+ if isSuccess ret then return $ Just $ PubKey fp else return Nothing++-- | Encode public key as DER. First argument 'True' for compressed output.+exportPubKey :: Bool -> PubKey -> ByteString+exportPubKey compress (PubKey pub) = withContext $ \ctx ->+ withForeignPtr pub $ \p -> alloca $ \l -> allocaBytes z $ \o -> do+ poke l (fromIntegral z)+ ret <- ecPubKeySerialize ctx o l p c+ unless (isSuccess ret) $ error "could not serialize public key"+ n <- peek l+ packByteString (o, n)+ where+ c = if compress then compressed else uncompressed+ z = if compress then 33 else 65++exportCompactSig :: Sig -> CompactSig+exportCompactSig (Sig fg) = withContext $ \ctx ->+ withForeignPtr fg $ \pg -> alloca $ \pc -> do+ ret <- ecdsaSignatureSerializeCompact ctx pc pg+ unless (isSuccess ret) $ error "Could not obtain compact signature"+ peek pc++importCompactSig :: CompactSig -> Maybe Sig+importCompactSig c = withContext $ \ctx -> alloca $ \pc -> do+ poke pc c+ fg <- mallocForeignPtr+ ret <- withForeignPtr fg $ \pg -> ecdsaSignatureParseCompact ctx pg pc+ if isSuccess ret then return $ Just $ Sig fg else return Nothing++-- | Import DER-encoded signature.+importSig :: ByteString -> Maybe Sig+importSig bs = withContext $ \ctx ->+ useByteString bs $ \(b, l) -> do+ fg <- mallocForeignPtr+ ret <- withForeignPtr fg $ \g -> ecdsaSignatureParseDer ctx g b l+ if isSuccess ret then return $ Just $ Sig fg else return Nothing++-- | Encode signature as strict DER.+exportSig :: Sig -> ByteString+exportSig (Sig fg) = withContext $ \ctx ->+ withForeignPtr fg $ \g -> alloca $ \l -> allocaBytes 72 $ \o -> do+ poke l 72+ ret <- ecdsaSignatureSerializeDer ctx o l g+ unless (isSuccess ret) $ error "could not serialize signature"+ n <- peek l+ packByteString (o, n)++-- | Verify message signature. 'True' means that the signature is correct.+verifySig :: PubKey -> Sig -> Msg -> Bool+verifySig (PubKey fp) (Sig fg) (Msg fm) = withContext $ \ctx ->+ withForeignPtr fp $ \p -> withForeignPtr fg $ \g ->+ withForeignPtr fm $ \m -> isSuccess <$> ecdsaVerify ctx g m p++signMsg :: SecKey -> Msg -> Sig+signMsg (SecKey fk) (Msg fm) = withContext $ \ctx ->+ withForeignPtr fk $ \k -> withForeignPtr fm $ \m -> do+ fg <- mallocForeignPtr+ ret <- withForeignPtr fg $ \g -> ecdsaSign ctx g m k nullFunPtr nullPtr+ unless (isSuccess ret) $ error "could not sign message"+ return $ Sig fg++derivePubKey :: SecKey -> PubKey+derivePubKey (SecKey fk) = withContext $ \ctx -> withForeignPtr fk $ \k -> do+ fp <- mallocForeignPtr+ ret <- withForeignPtr fp $ \p -> ecPubKeyCreate ctx p k+ unless (isSuccess ret) $ error "could not compute public key"+ return $ PubKey fp++-- | Add tweak to secret key.+tweakAddSecKey :: SecKey -> Tweak -> Maybe SecKey+tweakAddSecKey (SecKey fk) (Tweak ft) = withContext $ \ctx ->+ withForeignPtr fk $ \k -> withForeignPtr ft $ \t -> do+ fk' <- mallocForeignPtr+ ret <- withForeignPtr fk' $ \k' -> do+ key <- peek k+ poke k' key+ ecSecKeyTweakAdd ctx k' t+ if isSuccess ret then return $ Just $ SecKey fk' else return Nothing++-- | Multiply secret key by tweak.+tweakMulSecKey :: SecKey -> Tweak -> Maybe SecKey+tweakMulSecKey (SecKey fk) (Tweak ft) = withContext $ \ctx ->+ withForeignPtr fk $ \k -> withForeignPtr ft $ \t -> do+ fk' <- mallocForeignPtr+ ret <- withForeignPtr fk' $ \k' -> do+ key <- peek k+ poke k' key+ ecSecKeyTweakMul ctx k' t+ if isSuccess ret then return $ Just $ SecKey fk' else return Nothing++-- | Add tweak to public key. Tweak is multiplied first by G to obtain a point.+tweakAddPubKey :: PubKey -> Tweak -> Maybe PubKey+tweakAddPubKey (PubKey fp) (Tweak ft) = withContext $ \ctx ->+ withForeignPtr fp $ \p -> withForeignPtr ft $ \t -> do+ fp' <- mallocForeignPtr+ ret <- withForeignPtr fp' $ \p' -> do+ pub <- peek p+ poke p' pub+ ecPubKeyTweakAdd ctx p' t+ if isSuccess ret then return $ Just $ PubKey fp' else return Nothing++-- | Multiply public key by tweak. Tweak is multiplied first by G to obtain a+-- point.+tweakMulPubKey :: PubKey -> Tweak -> Maybe PubKey+tweakMulPubKey (PubKey fp) (Tweak ft) = withContext $ \ctx ->+ withForeignPtr fp $ \p -> withForeignPtr ft $ \t -> do+ fp' <- mallocForeignPtr+ ret <- withForeignPtr fp' $ \p' -> do+ pub <- peek p+ poke p' pub+ ecPubKeyTweakMul ctx p' t+ if isSuccess ret then return $ Just $ PubKey fp' else return Nothing++-- | Add multiple public keys together.+combinePubKeys :: [PubKey] -> Maybe PubKey+combinePubKeys pubs = withContext $ \ctx -> pointers [] pubs $ \ps ->+ allocaArray (length ps) $ \a -> do+ pokeArray a ps+ fp <- mallocForeignPtr+ ret <- withForeignPtr fp $ \p ->+ ecPubKeyCombine ctx p a (fromIntegral $ length ps)+ if isSuccess ret+ then return $ Just $ PubKey fp+ else return Nothing+ where+ pointers ps [] f = f ps+ pointers ps (PubKey fp : pubs') f =+ withForeignPtr fp $ \p -> pointers (p:ps) pubs' f++-- | Parse a compact ECDSA signature (64 bytes + recovery id).+importCompactRecSig :: CompactRecSig -> Maybe RecSig+importCompactRecSig cr =+ if getCompactRecSigV cr `notElem` [0,1,2,3]+ then Nothing+ else withContext $ \ctx -> alloca $ \pc -> do+ let+ c = CompactSig (getCompactRecSigR cr) (getCompactRecSigS cr)+ recid = fromIntegral $ getCompactRecSigV cr+ poke pc c+ fg <- mallocForeignPtr+ ret <- withForeignPtr fg $ \pg ->+ ecdsaRecoverableSignatureParseCompact ctx pg pc recid+ if isSuccess ret then return $ Just $ RecSig fg else return Nothing++-- | Serialize an ECDSA signature in compact format (64 bytes + recovery id).+exportCompactRecSig :: RecSig -> CompactRecSig+exportCompactRecSig (RecSig fg) = withContext $ \ctx ->+ withForeignPtr fg $ \pg -> alloca $ \pc -> alloca $ \pr -> do+ ret <- ecdsaRecoverableSignatureSerializeCompact ctx pc pr pg+ unless (isSuccess ret) $ error "Could not obtain compact signature"+ CompactSig r s <- peek pc+ v <- fromIntegral <$> peek pr+ return $ CompactRecSig r s v++-- | Convert a recoverable signature into a normal signature.+convertRecSig :: RecSig -> Sig+convertRecSig (RecSig frg) = withContext $ \ctx ->+ withForeignPtr frg $ \prg -> do+ fg <- mallocForeignPtr+ ret <- withForeignPtr fg $ \pg ->+ ecdsaRecoverableSignatureConvert ctx pg prg+ unless (isSuccess ret) $+ error "Could not convert a recoverable signature"+ return $ Sig fg++-- | Create a recoverable ECDSA signature.+signRecMsg :: SecKey -> Msg -> RecSig+signRecMsg (SecKey fk) (Msg fm) = withContext $ \ctx ->+ withForeignPtr fk $ \k -> withForeignPtr fm $ \m -> do+ fg <- mallocForeignPtr+ ret <- withForeignPtr fg $ \g ->+ ecdsaSignRecoverable ctx g m k nullFunPtr nullPtr+ unless (isSuccess ret) $ error "could not sign message"+ return $ RecSig fg++-- | Recover an ECDSA public key from a signature.+recover :: RecSig -> Msg -> Maybe PubKey+recover (RecSig frg) (Msg fm) = withContext $ \ctx ->+ withForeignPtr frg $ \prg -> withForeignPtr fm $ \pm -> do+ fp <- mallocForeignPtr+ ret <- withForeignPtr fp $ \pp -> ecdsaRecover ctx pp prg pm+ if isSuccess ret then return $ Just $ PubKey fp else return Nothing++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 = do+ key <- arbitrary+ return $ derivePubKey key
+ src/Crypto/Secp256k1/Internal.hs view
@@ -0,0 +1,473 @@+{-# LANGUAGE RecordWildCards #-}+{-|+Module : Crypto.Secp256k1.Internal+License : MIT+Maintainer : Jean-Pierre Rupp <root@haskoin.com>+Stability : experimental+Portability : POSIX++The API for this module may change at any time. This is an internal module only+exposed for hacking and experimentation.+-}+module Crypto.Secp256k1.Internal where++import Control.Monad+import Data.ByteString (ByteString)+import qualified Data.ByteString as BS+import Data.ByteString.Short (ShortByteString, fromShort, toShort)+import Data.Serialize (Serialize (..))+import qualified Data.Serialize.Get as Get+import qualified Data.Serialize.Put as Put+import Foreign+import Foreign.C+import System.Entropy+import System.IO.Unsafe++data Ctx = Ctx++newtype PubKey64 = PubKey64 { getPubKey64 :: ShortByteString }+ deriving (Read, Show, Eq, Ord)++newtype Msg32 = Msg32 { getMsg32 :: ShortByteString }+ deriving (Read, Show, Eq, Ord)++newtype Sig64 = Sig64 { getSig64 :: ShortByteString }+ deriving (Read, Show, Eq, Ord)++data CompactSig =+ CompactSig+ { getCompactSigR :: !ShortByteString+ , getCompactSigS :: !ShortByteString+ }+ deriving (Show, Eq, Ord)++newtype RecSig65 = RecSig65 { getRecSig65 :: ShortByteString }+ deriving (Read, Show, Eq, Ord)++data CompactRecSig =+ CompactRecSig+ { getCompactRecSigR :: !ShortByteString+ , getCompactRecSigS :: !ShortByteString+ , getCompactRecSigV :: !Word8+ }+ deriving (Show, Eq, Ord)++newtype Seed32 = Seed32 { getSeed32 :: ShortByteString }+ deriving (Read, Show, Eq, Ord)++newtype SecKey32 = SecKey32 { getSecKey32 :: ShortByteString }+ deriving (Read, Show, Eq, Ord)++newtype Tweak32 = Tweak32 { getTweak32 :: ShortByteString }+ deriving (Read, Show, Eq, Ord)++newtype Nonce32 = Nonce32 { getNonce32 :: ShortByteString }+ deriving (Read, Show, Eq, Ord)++newtype Algo16 = Algo16 { getAlgo16 :: ShortByteString }+ deriving (Read, Show, Eq, Ord)++newtype CtxFlags = CtxFlags { getCtxFlags :: CUInt }+ deriving (Read, Show, Eq, Ord)++newtype SerFlags = SerFlags { getSerFlags :: CUInt }+ deriving (Read, Show, Eq, Ord)++newtype Ret = Ret { getRet :: CInt }+ deriving (Read, Show, Eq, Ord)++-- | Nonce32-generating function+type NonceFunction a+ = Ptr Nonce32+ -> Ptr Msg32+ -> Ptr SecKey32+ -> Ptr Algo16+ -> Ptr a -- ^ extra data+ -> CUInt -- ^ attempt+ -> Ret++verify :: CtxFlags+verify = CtxFlags 0x0101++sign :: CtxFlags+sign = CtxFlags 0x0201++signVerify :: CtxFlags+signVerify = CtxFlags 0x0301++compressed :: SerFlags+compressed = SerFlags 0x0102++uncompressed :: SerFlags+uncompressed = SerFlags 0x0002++useByteString :: ByteString -> ((Ptr CUChar, CSize) -> IO a) -> IO a+useByteString bs f =+ BS.useAsCStringLen bs $ \(b, l) -> f (castPtr b, fromIntegral l)++packByteString :: (Ptr CUChar, CSize) -> IO ByteString+packByteString (b, l) = BS.packCStringLen (castPtr b, fromIntegral l)++instance Storable PubKey64 where+ sizeOf _ = 64+ alignment _ = 1+ peek p = PubKey64 . toShort <$> packByteString (castPtr p, 64)+ poke p (PubKey64 k) = useByteString (fromShort k) $+ \(b, _) -> copyArray (castPtr p) b 64++instance Storable Sig64 where+ sizeOf _ = 64+ alignment _ = 1+ peek p = Sig64 . toShort <$> packByteString (castPtr p, 64)+ poke p (Sig64 k) = useByteString (fromShort k) $+ \(b, _) -> copyArray (castPtr p) b 64++instance Storable CompactSig where+ sizeOf _ = 64+ alignment _ = 1+ peek p = do+ bs <- BS.packCStringLen (castPtr p, 64)+ let (s, r) = BS.splitAt 32 bs+ guard $ BS.length s == 32+ guard $ BS.length r == 32+ return CompactSig { getCompactSigR = toShort r+ , getCompactSigS = toShort s+ }+ poke p CompactSig{..} =+ useByteString bs $ \(b, _) -> copyArray (castPtr p) b 64+ where+ bs = fromShort getCompactSigS `BS.append` fromShort getCompactSigR++instance Serialize CompactSig where+ get = do+ s <- Get.getByteString 32+ r <- Get.getByteString 32+ return CompactSig { getCompactSigR = toShort r+ , getCompactSigS = toShort s+ }+ put (CompactSig r s) = do+ Put.putShortByteString s+ Put.putShortByteString r++instance Storable RecSig65 where+ sizeOf _ = 65+ alignment _ = 1+ peek p = RecSig65 . toShort <$> packByteString (castPtr p, 65)+ poke p (RecSig65 k) = useByteString (fromShort k) $+ \(b, _) -> copyArray (castPtr p) b 65++instance Storable CompactRecSig where+ sizeOf _ = 65+ alignment _ = 1+ peek p = do+ bs <- BS.packCStringLen (castPtr p, 65)+ let (s, r) = BS.splitAt 32 $ BS.take 64 bs+ v = BS.last bs+ return CompactRecSig { getCompactRecSigR = toShort r+ , getCompactRecSigS = toShort s+ , getCompactRecSigV = v+ }+ poke p CompactRecSig{..} =+ useByteString bs $ \(b, _) -> copyArray (castPtr p) b 65+ where+ bs = fromShort getCompactRecSigS `BS.append`+ fromShort getCompactRecSigR `BS.snoc`+ getCompactRecSigV++instance Serialize CompactRecSig where+ get = do+ s <- Get.getByteString 32+ r <- Get.getByteString 32+ v <- Get.getWord8+ return CompactRecSig { getCompactRecSigR = toShort r+ , getCompactRecSigS = toShort s+ , getCompactRecSigV = v+ }+ put (CompactRecSig r s v) = do+ Put.putShortByteString s+ Put.putShortByteString r+ Put.putWord8 v++instance Storable Msg32 where+ sizeOf _ = 32+ alignment _ = 1+ peek p = Msg32 . toShort <$> packByteString (castPtr p, 32)+ poke p (Msg32 k) = useByteString (fromShort k) $+ \(b, _) -> copyArray (castPtr p) b 32++instance Storable Seed32 where+ sizeOf _ = 32+ alignment _ = 1+ peek p = Seed32 . toShort <$> packByteString (castPtr p, 32)+ poke p (Seed32 k) = useByteString (fromShort k) $+ \(b, _) -> copyArray (castPtr p) b 32++instance Storable SecKey32 where+ sizeOf _ = 32+ alignment _ = 1+ peek p = SecKey32 . toShort <$> packByteString (castPtr p, 32)+ poke p (SecKey32 k) = useByteString (fromShort k) $+ \(b, _) -> copyArray (castPtr p) b 32++instance Storable Tweak32 where+ sizeOf _ = 32+ alignment _ = 1+ peek p = Tweak32 . toShort <$> packByteString (castPtr p, 32)+ poke p (Tweak32 k) = useByteString (fromShort k) $+ \(b, _) -> copyArray (castPtr p) b 32++instance Storable Nonce32 where+ sizeOf _ = 32+ alignment _ = 1+ peek p = Nonce32 . toShort <$> packByteString (castPtr p, 32)+ poke p (Nonce32 k) = useByteString (fromShort k) $+ \(b, _) -> copyArray (castPtr p) b 32++instance Storable Algo16 where+ sizeOf _ = 16+ alignment _ = 1+ peek p = Algo16 . toShort <$> packByteString (castPtr p, 16)+ poke p (Algo16 k) = useByteString (fromShort k) $+ \(b, _) -> copyArray (castPtr p) b 16++isSuccess :: Ret -> Bool+isSuccess (Ret 0) = False+isSuccess (Ret 1) = True+isSuccess _ = undefined++{-# NOINLINE fctx #-}+fctx :: ForeignPtr Ctx+fctx = unsafePerformIO $ do+ x <- contextCreate signVerify+ e <- getEntropy 32+ ret <- alloca $ \s -> poke s (Seed32 (toShort e)) >> contextRandomize x s+ unless (isSuccess ret) $ error "failed to randomize context"+ newForeignPtr contextDestroy x++{-# INLINE withContext #-}+withContext :: (Ptr Ctx -> IO a) -> a+withContext f = unsafeDupablePerformIO (withForeignPtr fctx f)++foreign import ccall+ "secp256k1.h secp256k1_context_create"+ contextCreate+ :: CtxFlags+ -> IO (Ptr Ctx)++foreign import ccall+ "secp256k1.h secp256k1_context_clone"+ contextClone+ :: Ptr Ctx+ -> IO (Ptr Ctx)++foreign import ccall+ "secp256k1.h &secp256k1_context_destroy"+ contextDestroy+ :: FunPtr (Ptr Ctx -> IO ())++foreign import ccall+ "secp256k1.h secp256k1_context_set_illegal_callback"+ setIllegalCallback+ :: Ptr Ctx+ -> FunPtr (CString -> Ptr a -> IO ()) -- ^ message, data+ -> Ptr a -- ^ data+ -> IO ()++foreign import ccall+ "secp256k1.h secp256k1_context_set_error_callback"+ setErrorCallback+ :: Ptr Ctx+ -> FunPtr (CString -> Ptr a -> IO ()) -- ^ message, data+ -> Ptr a -- ^ data+ -> IO ()++foreign import ccall+ "secp256k1.h secp256k1_ec_pubkey_parse"+ ecPubKeyParse+ :: Ptr Ctx+ -> Ptr PubKey64+ -> Ptr CUChar -- ^ encoded public key array+ -> CSize -- ^ size of encoded public key array+ -> IO Ret++foreign import ccall+ "secp256k1.h secp256k1_ec_pubkey_serialize"+ ecPubKeySerialize+ :: Ptr Ctx+ -> Ptr CUChar -- ^ array for encoded public key, must be large enough+ -> Ptr CSize -- ^ size of encoded public key, will be updated+ -> Ptr PubKey64+ -> SerFlags+ -> IO Ret++foreign import ccall+ "secp256k1.h secp256k1_ecdsa_signature_parse_compact"+ ecdsaSignatureParseCompact+ :: Ptr Ctx+ -> Ptr Sig64+ -> Ptr CompactSig+ -> IO Ret+++foreign import ccall+ "secp256k1.h secp256k1_ecdsa_signature_parse_der"+ ecdsaSignatureParseDer+ :: Ptr Ctx+ -> Ptr Sig64+ -> Ptr CUChar -- ^ encoded DER signature+ -> CSize -- ^ size of encoded signature+ -> IO Ret++foreign import ccall+ "secp256k1.h secp256k1_ecdsa_signature_serialize_der"+ ecdsaSignatureSerializeDer+ :: Ptr Ctx+ -> Ptr CUChar -- ^ array for encoded signature, must be large enough+ -> Ptr CSize -- ^ size of encoded signature, will be updated+ -> Ptr Sig64+ -> IO Ret++foreign import ccall+ "secp256k1.h secp256k1_ecdsa_signature_serialize_compact"+ ecdsaSignatureSerializeCompact+ :: Ptr Ctx+ -> Ptr CompactSig+ -> Ptr Sig64+ -> IO Ret++foreign import ccall+ "secp256k1.h secp256k1_ecdsa_verify"+ ecdsaVerify+ :: Ptr Ctx+ -> Ptr Sig64+ -> Ptr Msg32+ -> Ptr PubKey64+ -> IO Ret++foreign import ccall+ "secp256k1.h secp256k1_ecdsa_signature_normalize"+ ecdsaSignatureNormalize+ :: Ptr Ctx+ -> Ptr Sig64 -- ^ output+ -> Ptr Sig64 -- ^ input+ -> IO Ret++foreign import ccall+ "secp256k1.h secp256k1_ecdsa_sign"+ ecdsaSign+ :: Ptr Ctx+ -> Ptr Sig64+ -> Ptr Msg32+ -> Ptr SecKey32+ -> FunPtr (NonceFunction a)+ -> Ptr a -- ^ nonce data+ -> IO Ret++foreign import ccall+ "secp256k1.h secp256k1_ec_seckey_verify"+ ecSecKeyVerify+ :: Ptr Ctx+ -> Ptr SecKey32+ -> IO Ret++foreign import ccall+ "secp256k1.h secp256k1_ec_pubkey_create"+ ecPubKeyCreate+ :: Ptr Ctx+ -> Ptr PubKey64+ -> Ptr SecKey32+ -> IO Ret++foreign import ccall+ "secp256k1.h secp256k1_ec_privkey_tweak_add"+ ecSecKeyTweakAdd+ :: Ptr Ctx+ -> Ptr SecKey32+ -> Ptr Tweak32+ -> IO Ret++foreign import ccall+ "secp256k1.h secp256k1_ec_pubkey_tweak_add"+ ecPubKeyTweakAdd+ :: Ptr Ctx+ -> Ptr PubKey64+ -> Ptr Tweak32+ -> IO Ret++foreign import ccall+ "secp256k1.h secp256k1_ec_privkey_tweak_mul"+ ecSecKeyTweakMul+ :: Ptr Ctx+ -> Ptr SecKey32+ -> Ptr Tweak32+ -> IO Ret++foreign import ccall+ "secp256k1.h secp256k1_ec_pubkey_tweak_mul"+ ecPubKeyTweakMul+ :: Ptr Ctx+ -> Ptr PubKey64+ -> Ptr Tweak32+ -> IO Ret++foreign import ccall+ "secp256k1.h secp256k1_context_randomize"+ contextRandomize+ :: Ptr Ctx+ -> Ptr Seed32+ -> IO Ret++foreign import ccall+ "secp256k1.h secp256k1_ec_pubkey_combine"+ ecPubKeyCombine+ :: Ptr Ctx+ -> Ptr PubKey64 -- ^ pointer to public key storage+ -> Ptr (Ptr PubKey64) -- ^ pointer to array of public keys+ -> CInt -- ^ number of public keys+ -> IO Ret++foreign import ccall+ "secp256k1_recovery.h secp256k1_ecdsa_recoverable_signature_parse_compact"+ ecdsaRecoverableSignatureParseCompact+ :: Ptr Ctx+ -> Ptr RecSig65+ -> Ptr CompactSig+ -> CInt+ -> IO Ret++foreign import ccall+ "secp256k1_recovery.h secp256k1_ecdsa_recoverable_signature_convert"+ ecdsaRecoverableSignatureConvert+ :: Ptr Ctx+ -> Ptr Sig64+ -> Ptr RecSig65+ -> IO Ret++foreign import ccall+ "secp256k1_recovery.h secp256k1_ecdsa_recoverable_signature_serialize_compact"+ ecdsaRecoverableSignatureSerializeCompact+ :: Ptr Ctx+ -> Ptr CompactSig+ -> Ptr CInt+ -> Ptr RecSig65+ -> IO Ret++foreign import ccall+ "secp256k1_recovery.h secp256k1_ecdsa_sign_recoverable"+ ecdsaSignRecoverable+ :: Ptr Ctx+ -> Ptr RecSig65+ -> Ptr Msg32+ -> Ptr SecKey32+ -> FunPtr (NonceFunction a)+ -> Ptr a -- ^ nonce data+ -> IO Ret++foreign import ccall+ "secp256k1_recovery.h secp256k1_ecdsa_recover"+ ecdsaRecover+ :: Ptr Ctx+ -> Ptr PubKey64+ -> Ptr RecSig65+ -> Ptr Msg32+ -> IO Ret
+ test/Crypto/Secp256k1/InternalSpec.hs view
@@ -0,0 +1,417 @@+{-# LANGUAGE OverloadedStrings #-}+module Crypto.Secp256k1.InternalSpec (spec) where++import Control.Monad+import Control.Monad.Trans+import Crypto.Secp256k1.Internal+import Data.ByteString (ByteString, packCStringLen,+ useAsCStringLen)+import qualified Data.ByteString.Base16 as B16+import Data.ByteString.Short (toShort)+import Foreign+import System.Entropy+import Test.Hspec+import Test.HUnit (Assertion, assertBool, assertEqual)++spec :: Spec+spec = do+ describe "housekeeping" $ do+ it "creates context" createContextTest+ it "randomizez context" randomizeContextTest+ it "clones context" cloneContextTest+ describe "serialization" $ do+ it "parses public key" ecPubkeyParseTest+ it "serializes public key" ecPubKeySerializeTest+ it "handles storable public key" pubkeyStorableTest+ it "handles storable signature" signatureStorableTest+ it "parses DER signature" ecdsaSignatureParseDerTest+ it "serializes DER signature" ecdsaSignatureSerializeDerTest+ describe "signatures" $ do+ it "verifies signature" ecdsaVerifyTest+ -- TODO:+ -- , testCase "RFC6979 nonce function" nonce_function_rfc6979_test+ it "signs message" ecdsaSignTest+ describe "secret keys" $ do+ it "verifies secret key" ecSecKeyVerifyTest+ it "creates public key" ecPubkeyCreateTest+ it "adds secret key" ecSecKeyTweakAddTest+ it "multiplies secret key" ecSecKeyTweakMulTest+ describe "public keys" $ do+ it "adds public key" ecPubKeyTweakAddTest+ it "multiplies public key" ecPubKeyTweakMulTest+ it "combines public keys" ecPubKeyCombineTest++withEntropy :: (Ptr Seed32 -> IO a) -> IO a+withEntropy f =+ getEntropy 32 >>= \e ->+ alloca $ \s ->+ poke s (Seed32 (toShort e)) >> f s++createContextTest :: Assertion+createContextTest = do+ context_ptr <- liftIO $ contextCreate signVerify+ assertBool "context not null" $ context_ptr /= nullPtr++randomizeContextTest :: Assertion+randomizeContextTest = do+ ret <- liftIO $ contextCreate sign >>= \x ->+ withEntropy (contextRandomize x)+ assertBool "context randomized" $ isSuccess ret++cloneContextTest :: Assertion+cloneContextTest = do+ (x1, x2) <- liftIO $ do+ x1 <- contextCreate signVerify+ ret <- withEntropy $ contextRandomize x1+ unless (isSuccess ret) $ error "failed to randomize context"+ x2 <- contextClone x1+ return (x1, x2)+ assertBool "original context not null" $ x1 /= nullPtr+ assertBool "cloned context not null" $ x2 /= nullPtr+ assertBool "context ptrs different" $ x1 /= x2++ecPubkeyParseTest :: Assertion+ecPubkeyParseTest = do+ ret <- liftIO $ useAsCStringLen der $ \(i, il) -> do+ x <- contextCreate verify+ alloca $ \pubkey ->+ ecPubKeyParse x pubkey (castPtr i) (fromIntegral il)+ assertBool "parsed public key" (isSuccess ret)+ where+ der = fst $ B16.decode+ "03dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd44705"++ecPubKeySerializeTest :: Assertion+ecPubKeySerializeTest = do+ (ret, dec) <- liftIO $ useAsCStringLen der $ \(i, il) ->+ alloca $ \k -> alloca $ \ol -> allocaBytes 72 $ \o -> do+ poke ol 72+ x <- contextCreate verify+ ret1 <-+ ecPubKeyParse x k (castPtr i) (fromIntegral il)+ unless (isSuccess ret1) $ error "failed to parse pubkey"+ ret2 <- ecPubKeySerialize+ x o ol k compressed+ len <- fromIntegral <$> peek ol+ decoded <- packCStringLen (castPtr o, len)+ return (ret2, decoded)+ assertBool "serialized public key successfully" $ isSuccess ret+ assertEqual "public key matches" der dec+ where+ der = fst $ B16.decode+ "03dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd44705"++pubkeyStorableTest :: Assertion+pubkeyStorableTest = do+ (pk1, pk2, dec) <- liftIO $ useAsCStringLen der $ \(i, il) -> do+ x <- contextCreate verify+ pk1 <- alloca $ \pk -> do+ ret <-+ ecPubKeyParse x pk (castPtr i) (fromIntegral il)+ unless (isSuccess ret) $ error "failed to parse pubkey"+ peek pk+ (pk2, dec) <- alloca $ \pk -> alloca $ \ol -> allocaBytes 72 $ \o -> do+ poke ol 72+ poke pk pk1+ ret <-+ ecPubKeySerialize x o ol pk compressed+ unless (isSuccess ret) $ error "failed to serialize pubkey"+ len <- fromIntegral <$> peek ol+ dec <- packCStringLen (castPtr o, len)+ pk2 <- peek pk+ return (pk2, dec)+ return (pk1, pk2, dec)+ assertEqual "poke/peek public key" pk1 pk2+ assertEqual "public key matches" der dec+ where+ der = fst $ B16.decode+ "03dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd44705"++signatureStorableTest :: Assertion+signatureStorableTest = do+ (sig, ret) <- liftIO $ do+ x <- contextCreate verify+ g <- alloca $ \pc -> alloca $ \pg -> do+ poke pc cpt+ ret <- ecdsaSignatureParseCompact x pg (castPtr pc)+ unless (isSuccess ret) $ error "failed to parse signature"+ peek pg+ alloca $ \pc -> alloca $ \pg -> do+ poke pg g+ ret <- ecdsaSignatureSerializeCompact x pc pg+ c <- peek pc+ return (c, ret)+ assertBool "successful serialization" (isSuccess ret)+ assertEqual "signatures match" cpt sig+ where+ cpt = CompactSig+ (toShort $ fst $ B16.decode "f502bfa07af43e7ef265618b0d929a7619ee01d6150e37eb6eaaf2c8bd37fb22")+ (toShort $ fst $ B16.decode "6f0415ab0e9a977afd78b2c26ef39b3952096d319fd4b101c768ad6c132e3045")++ecdsaSignatureParseDerTest :: Assertion+ecdsaSignatureParseDerTest = do+ ret <- liftIO $ useAsCStringLen der $ \(d, dl) -> alloca $ \s -> do+ x <- contextCreate verify+ ecdsaSignatureParseDer x s (castPtr d) (fromIntegral dl)+ assertBool "parsed signature successfully" $ isSuccess ret+ where+ der = fst $ B16.decode+ "3045022100f502bfa07af43e7ef265618b0d929a7619ee01d6150e37eb6eaaf2c8bd37\+ \fb2202206f0415ab0e9a977afd78b2c26ef39b3952096d319fd4b101c768ad6c132e30\+ \45"++parseDer :: Ptr Ctx -> ByteString -> IO Sig64+parseDer x bs = useAsCStringLen bs $ \(d, dl) -> alloca $ \s -> do+ ret <- ecdsaSignatureParseDer x s (castPtr d) (fromIntegral dl)+ unless (isSuccess ret) $ error "could not parse DER"+ peek s++ecdsaSignatureSerializeDerTest :: Assertion+ecdsaSignatureSerializeDerTest = do+ (ret, enc) <- liftIO $ do+ x <- contextCreate verify+ sig <- parseDer x der+ alloca $ \s -> alloca $ \ol -> allocaBytes 72 $ \o -> do+ poke ol 72+ poke s sig+ ret <-+ ecdsaSignatureSerializeDer x o ol s+ len <- fromIntegral <$> peek ol+ enc <- packCStringLen (castPtr o, len)+ return (ret, enc)+ assertBool "serialization successful" $ isSuccess ret+ assertEqual "signatures match" der enc+ where+ der = fst $ B16.decode+ "3045022100f502bfa07af43e7ef265618b0d929a7619ee01d6150e37eb6eaaf2c8bd37\+ \fb2202206f0415ab0e9a977afd78b2c26ef39b3952096d319fd4b101c768ad6c132e30\+ \45"++ecdsaVerifyTest :: Assertion+ecdsaVerifyTest = do+ ret <- liftIO $ do+ x <- contextCreate verify+ sig <- parseDer x der+ pk <- useAsCStringLen pub $ \(p, pl) -> alloca $ \k -> do+ ret <-+ ecPubKeyParse x k (castPtr p) (fromIntegral pl)+ unless (isSuccess ret) $ error "could not parse public key"+ peek k+ alloca $ \m -> alloca $ \k -> alloca $ \s -> do+ poke m msg+ poke k pk+ poke s sig+ ecdsaVerify x s m k+ assertBool "signature valid" $ isSuccess ret+ where+ der = fst $ B16.decode+ "3045022100f502bfa07af43e7ef265618b0d929a7619ee01d6150e37eb6eaaf2c8bd37\+ \fb2202206f0415ab0e9a977afd78b2c26ef39b3952096d319fd4b101c768ad6c132e30\+ \45"+ pub = fst $ B16.decode+ "04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd447051221\+ \3d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"+ msg = Msg32 $ toShort $ fst $ B16.decode+ "f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"++signCtx :: IO (Ptr Ctx)+signCtx = contextCreate sign >>= \c ->+ withEntropy (contextRandomize c) >>= \r ->+ unless (isSuccess r) (error "failed to randomize context") >> return c++ecdsaSignTest :: Assertion+ecdsaSignTest = do+ (ret, sig) <- liftIO $ do+ x <- signCtx+ alloca $ \s -> alloca $ \m -> alloca $ \k -> alloca $ \ol ->+ allocaBytes 72 $ \o -> do+ poke ol 72+ poke m msg+ poke k key+ ret1 <-+ -- TODO:+ -- ecdsaSign x s m k nonce_function_default nullPtr+ ecdsaSign x s m k nullFunPtr nullPtr+ ret2 <- ecdsaSignatureSerializeDer x o ol s+ unless (isSuccess ret2) $ error "could not serialize signature"+ len <- peek ol+ sig <- packCStringLen (castPtr o, fromIntegral len)+ return (ret1, sig)+ assertBool "successful signing" $ isSuccess ret+ assertEqual "signature matches" sig der+ where+ msg = Msg32 $ toShort $ fst $ B16.decode+ "f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"+ key = SecKey32 $ toShort $ fst $ B16.decode+ "f65255094d7773ed8dd417badc9fc045c1f80fdc5b2d25172b031ce6933e039a"+ der = fst $ B16.decode+ "3045022100f502bfa07af43e7ef265618b0d929a7619ee01d6150e37eb6eaaf2c8bd37\+ \fb2202206f0415ab0e9a977afd78b2c26ef39b3952096d319fd4b101c768ad6c132e30\+ \45"++ecSecKeyVerifyTest :: Assertion+ecSecKeyVerifyTest = do+ ret <- liftIO $ alloca $ \k -> do+ poke k key+ x <- signCtx+ ecSecKeyVerify x k+ assertBool "valid secret key" $ isSuccess ret+ where+ key = SecKey32 $ toShort $ fst $ B16.decode+ "f65255094d7773ed8dd417badc9fc045c1f80fdc5b2d25172b031ce6933e039a"++ecPubkeyCreateTest :: Assertion+ecPubkeyCreateTest = do+ (ret, pk) <- liftIO $ alloca $ \p -> alloca $ \k -> do+ poke k key+ x <- signCtx+ ret <- ecPubKeyCreate x p k+ allocaBytes 65 $ \o -> alloca $ \ol -> do+ poke ol 65+ rets <- ecPubKeySerialize+ x o ol p uncompressed+ unless (isSuccess rets) $ error "failed to serialive public key"+ len <- fromIntegral <$> peek ol+ pk <- packCStringLen (castPtr o, len)+ return (ret, pk)+ assertBool "successful pubkey creation" $ isSuccess ret+ assertEqual "public key matches" pub pk+ where+ key = SecKey32 $ toShort $ fst $ B16.decode+ "f65255094d7773ed8dd417badc9fc045c1f80fdc5b2d25172b031ce6933e039a"+ pub = fst $ B16.decode+ "04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd447051221\+ \3d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"++ecSecKeyTweakAddTest :: Assertion+ecSecKeyTweakAddTest = do+ (ret, tweaked) <- liftIO $ do+ x <- signCtx+ alloca $ \w -> alloca $ \k -> do+ poke w tweak+ poke k key+ ret <- ecSecKeyTweakAdd x k w+ tweaked <- peek k+ return (ret, tweaked)+ assertBool "successful secret key tweak" $ isSuccess ret+ assertEqual "tweaked keys match" expected tweaked+ where+ key = SecKey32 $ toShort $ fst $ B16.decode+ "f65255094d7773ed8dd417badc9fc045c1f80fdc5b2d25172b031ce6933e039a"+ tweak = Tweak32 $ toShort $ fst $ B16.decode+ "f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"+ expected = SecKey32 $ toShort $ fst $ B16.decode+ "ec1e3ce1cefa18a671d51125e2b249688d934b0e28f5d1665384d9b02f929059"++ecSecKeyTweakMulTest :: Assertion+ecSecKeyTweakMulTest = do+ (ret, tweaked) <- liftIO $ do+ x <- contextCreate sign+ retr <- withEntropy $ contextRandomize x+ unless (isSuccess retr) $ error "failed to randomize context"+ alloca $ \w -> alloca $ \k -> do+ poke w tweak+ poke k key+ ret <- ecSecKeyTweakMul x k w+ tweaked <- peek k+ return (ret, tweaked)+ assertBool "successful secret key tweak" $ isSuccess ret+ assertEqual "tweaked keys match" expected tweaked+ where+ key = SecKey32 $ toShort $ fst $ B16.decode+ "f65255094d7773ed8dd417badc9fc045c1f80fdc5b2d25172b031ce6933e039a"+ tweak = Tweak32 $ toShort $ fst $ B16.decode+ "f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"+ expected = SecKey32 $ toShort $ fst $ B16.decode+ "a96f5962493acb179f60a86a9785fc7a30e0c39b64c09d24fe064d9aef15e4c0"++serializeKey :: Ptr Ctx -> Ptr PubKey64 -> IO ByteString+serializeKey x p = allocaBytes 72 $ \d -> alloca $ \dl -> do+ poke dl 72+ ret <- ecPubKeySerialize x d dl p uncompressed+ unless (isSuccess ret) $ error "could not serialize public key"+ len <- peek dl+ packCStringLen (castPtr d, fromIntegral len)++parseKey :: Ptr Ctx -> ByteString -> IO PubKey64+parseKey x bs = alloca $ \p -> useAsCStringLen bs $ \(d, dl) -> do+ ret <- ecPubKeyParse x p (castPtr d) (fromIntegral dl)+ unless (isSuccess ret) $ error "could not parse public key"+ peek p++ecPubKeyTweakAddTest :: Assertion+ecPubKeyTweakAddTest = do+ (ret, tweaked) <- liftIO $ do+ x <- contextCreate verify+ pk <- parseKey x pub+ alloca $ \w -> alloca $ \p -> do+ poke w tweak+ poke p pk+ ret <- ecPubKeyTweakAdd x p w+ tweaked <- serializeKey x p+ return (ret, tweaked)+ assertBool "successful secret key tweak" $ isSuccess ret+ assertEqual "tweaked keys match" expected tweaked+ where+ pub = fst $ B16.decode+ "04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd447051221\+ \3d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"+ tweak = Tweak32 $ toShort $ fst $ B16.decode+ "f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"+ expected = fst $ B16.decode+ "04441c3982b97576646e0df0c96736063df6b42f2ee566d13b9f6424302d1379e518fd\+ \c87a14c5435bff7a5db4552042cb4120c6b86a4bbd3d0643f3c14ad01368"++ecPubKeyTweakMulTest :: Assertion+ecPubKeyTweakMulTest = do+ (ret, tweaked) <- liftIO $ do+ x <- contextCreate verify+ pk <- parseKey x pub+ alloca $ \w -> alloca $ \p -> do+ poke w tweak+ poke p pk+ ret <- ecPubKeyTweakMul x p w+ tweaked <- serializeKey x p+ return (ret, tweaked)+ assertBool "successful secret key tweak" $ isSuccess ret+ assertEqual "tweaked keys match" expected tweaked+ where+ pub = fst $ B16.decode+ "04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd447051221\+ \3d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"+ tweak = Tweak32 $ toShort $ fst $ B16.decode+ "f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"+ expected = fst $ B16.decode+ "04f379dc99cdf5c83e433defa267fbb3377d61d6b779c06a0e4ce29ae3ff5353b12ae4\+ \9c9d07e7368f2ba5a446c203255ce912322991a2d6a9d5d5761c61ed1845"++ecPubKeyCombineTest :: Assertion+ecPubKeyCombineTest = do+ (ret, com) <- liftIO $ alloca $ \p1 -> alloca $ \p2 -> alloca $ \p3 ->+ allocaArray 3 $ \a -> alloca $ \p -> do+ x <- contextCreate verify+ parse x pub1 p1+ parse x pub2 p2+ parse x pub3 p3+ pokeArray a [p1, p2, p3]+ ret <- ecPubKeyCombine x p a 3+ com <- serializeKey x p+ return (ret, com)+ assertBool "successful key combination" $ isSuccess ret+ assertEqual "combined keys match" expected com+ where+ parse x pub p = useAsCStringLen pub $ \(d, dl) -> do+ ret <- ecPubKeyParse x p (castPtr d) (fromIntegral dl)+ unless (isSuccess ret) $ error "could not parse public key"+ pub1 = fst $ B16.decode+ "04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd447051221\+ \3d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"+ pub2 = fst $ B16.decode+ "0487d82042d93447008dfe2af762068a1e53ff394a5bf8f68a045fa642b99ea5d153f5\+ \77dd2dba6c7ae4cfd7b6622409d7edd2d76dd13a8092cd3af97b77bd2c77"+ pub3 = fst $ B16.decode+ "049b101edcbe1ee37ff6b2318526a425b629e823d7d8d9154417880595a28000ee3feb\+ \d908754b8ce4e491aa6fe488b41fb5d4bb3788e33c9ff95a7a9229166d59"+ expected = fst $ B16.decode+ "043d9a7ec70011efc23c33a7e62d2ea73cca87797e3b659d93bea6aa871aebde56c3bc\+ \6134ca82e324b0ab9c0e601a6d2933afe7fb5d9f3aae900f5c5dc6e362c8"
+ test/Crypto/Secp256k1Spec.hs view
@@ -0,0 +1,243 @@+module Crypto.Secp256k1Spec (spec) where++import Crypto.Secp256k1+import qualified Data.ByteString.Base16 as B16+import qualified Data.ByteString.Char8 as B8+import Data.Maybe (fromMaybe)+import Data.Serialize+import Data.String (fromString)+import Data.String.Conversions (cs)+import Test.Hspec+import Test.HUnit (Assertion, assertEqual)+import Test.QuickCheck (Property, property, (==>))++spec :: Spec+spec = do+ describe "signatures" $ do+ it "signs message" $ property $ signMsgTest+ it "recovers key from signed message" $ property $ signRecMsgTest+ it "detects bad signature" $ property $ badSignatureTest+ it "detects bad recoverable signature" $ property $ badRecSignatureTest+ it "normalizes signatures" $ property $ normalizeSigTest+ it "recovers public keys" $ property $ recoverTest+ it "Bad recover public keys" $ property $ badRecoverTest+ describe "serialization" $ do+ it "serializes public key" $ property $ serializePubKeyTest+ it "serializes DER signature" $ property $ serializeSigTest+ it "serializes compact signature" $ property $ serializeCompactSigTest+ it "serializes compact recoverable signature" $+ property $ serializeCompactRecSigTest+ it "serialize secret key" $ property $ serializeSecKeyTest+ it "shows and reads public key" $+ property $ (showRead :: PubKey -> Bool)+ it "shows and reads secret key" $+ property $ (showRead :: SecKey -> Bool)+ it "shows and reads tweak" $+ property $ (showReadTweak :: SecKey -> Bool)+ it "shows and reads signature" $+ property $ (showReadSig :: (SecKey, Msg) -> Bool)+ it "shows and reads recoverable signature" $+ property $ (showReadRecSig :: (SecKey, Msg) -> Bool)+ it "shows and reads message" $ property $ (showRead :: Msg -> Bool)+ it "reads public key from string" $ property $ isStringPubKey+ it "reads secret key from string" $ property $ isStringSecKey+ it "reads signature from string" $ property $ isStringSig+ it "reads recoverable signature from string" $ property $ isStringRecSig+ it "reads message from string" $ property $ isStringMsg+ it "reads tweak from string" $ property $ isStringTweak+ describe "tweaks" $ do+ it "add secret key" $ property $ tweakAddSecKeyTest+ it "multiply secret key" $ property $ tweakMulSecKeyTest+ it "add public key" $ property $ tweakAddPubKeyTest+ it "multiply public key" $ property $ tweakMulPubKeyTest+ it "combine public keys" $ property $ combinePubKeyTest++isStringPubKey :: (PubKey, Bool) -> Bool+isStringPubKey (k, c) = k == fromString (cs hex) where+ hex = B16.encode $ exportPubKey c k++isStringSig :: (SecKey, Msg) -> Bool+isStringSig (k, m) = g == fromString (cs hex) where+ g = signMsg k m+ hex = B16.encode $ exportSig g++isStringRecSig :: (SecKey, Msg) -> Bool+isStringRecSig (k, m) = g == fromString (cs hex) where+ g = signRecMsg k m+ hex = B16.encode . encode $ exportCompactRecSig g++isStringMsg :: Msg -> Bool+isStringMsg m = m == fromString (cs m') where+ m' = B16.encode $ getMsg m++isStringSecKey :: SecKey -> Bool+isStringSecKey k = k == fromString (cs hex) where+ hex = B16.encode $ getSecKey k++isStringTweak :: SecKey -> Bool+isStringTweak k = t == fromString (cs hex) where+ t = fromMaybe e . tweak $ getSecKey k+ hex = B16.encode $ getTweak t+ e = error "Could not extract tweak from secret key"++showReadTweak :: SecKey -> Bool+showReadTweak k = showRead t where+ t = tweak $ getSecKey k++showReadSig :: (SecKey, Msg) -> Bool+showReadSig (k, m) = showRead sig where+ sig = signMsg k m++showReadRecSig :: (SecKey, Msg) -> Bool+showReadRecSig (k, m) = showRead recSig where+ recSig = signRecMsg k m++showRead :: (Show a, Read a, Eq a) => a -> Bool+showRead x = read (show x) == x++signMsgTest :: (Msg, SecKey) -> Bool+signMsgTest (fm, fk) = verifySig fp fg fm where+ fp = derivePubKey fk+ fg = signMsg fk fm++signRecMsgTest :: (Msg, SecKey) -> Bool+signRecMsgTest (fm, fk) = verifySig fp fg fm where+ fp = derivePubKey fk+ fg = convertRecSig $ signRecMsg fk fm++recoverTest :: (Msg, SecKey) -> Bool+recoverTest (fm, fk) = recover fg fm == Just fp where+ fp = derivePubKey fk+ fg = signRecMsg fk fm++badRecoverTest :: (Msg, SecKey, Msg) -> Property+badRecoverTest (fm, fk, fm') =+ fm' /= fm ==> fp' /= Nothing ==> fp' /= Just fp+ where+ fg = signRecMsg fk fm+ fp = derivePubKey fk+ fp' = recover fg fm'++badSignatureTest :: (Msg, SecKey, PubKey) -> Bool+badSignatureTest (fm, fk, fp) = not $ verifySig fp fg fm where+ fg = signMsg fk fm++badRecSignatureTest :: (Msg, SecKey, PubKey) -> Bool+badRecSignatureTest (fm, fk, fp) = not $ verifySig fp fg fm where+ fg = convertRecSig $ signRecMsg fk fm++normalizeSigTest :: (Msg, SecKey) -> Bool+normalizeSigTest (fm, fk) = not norm && sig == fg where+ fg = signMsg fk fm+ (sig, norm) = normalizeSig fg++serializePubKeyTest :: (PubKey, Bool) -> Bool+serializePubKeyTest (fp, b) =+ case importPubKey $ exportPubKey b fp of+ Just fp' -> fp == fp'+ Nothing -> False++serializeSigTest :: (Msg, SecKey) -> Bool+serializeSigTest (fm, fk) =+ case importSig $ exportSig fg of+ Just fg' -> fg == fg'+ Nothing -> False+ where+ fg = signMsg fk fm++serializeCompactSigTest :: (Msg, SecKey) -> Bool+serializeCompactSigTest (fm, fk) =+ case importCompactSig $ exportCompactSig fg of+ Just fg' -> fg == fg'+ Nothing -> False+ where+ fg = signMsg fk fm++serializeCompactRecSigTest :: (Msg, SecKey) -> Bool+serializeCompactRecSigTest (fm, fk) =+ case importCompactRecSig $ exportCompactRecSig fg of+ Just fg' -> fg == fg'+ Nothing -> False+ where+ fg = signRecMsg fk fm++serializeSecKeyTest :: SecKey -> Bool+serializeSecKeyTest fk =+ case secKey $ getSecKey fk of+ Just fk' -> fk == fk'+ Nothing -> False++tweakAddSecKeyTest :: Assertion+tweakAddSecKeyTest =+ assertEqual "tweaked keys match" expected tweaked+ where+ tweaked = do+ key <- secKey $ fst $ B16.decode $ B8.pack+ "f65255094d7773ed8dd417badc9fc045c1f80fdc5b2d25172b031ce6933e039a"+ twk <- tweak $ fst $ B16.decode $ B8.pack+ "f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"+ tweakAddSecKey key twk+ expected = secKey $ fst $ B16.decode $ B8.pack+ "ec1e3ce1cefa18a671d51125e2b249688d934b0e28f5d1665384d9b02f929059"++tweakMulSecKeyTest :: Assertion+tweakMulSecKeyTest =+ assertEqual "tweaked keys match" expected tweaked+ where+ tweaked = do+ key <- secKey $ fst $ B16.decode $ B8.pack+ "f65255094d7773ed8dd417badc9fc045c1f80fdc5b2d25172b031ce6933e039a"+ twk <- tweak $ fst $ B16.decode $ B8.pack+ "f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"+ tweakMulSecKey key twk+ expected = secKey $ fst $ B16.decode $ B8.pack+ "a96f5962493acb179f60a86a9785fc7a30e0c39b64c09d24fe064d9aef15e4c0"++tweakAddPubKeyTest :: Assertion+tweakAddPubKeyTest =+ assertEqual "tweaked keys match" expected tweaked+ where+ tweaked = do+ pub <- importPubKey $ fst $ B16.decode $ B8.pack+ "04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd44705\+ \12213d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"+ twk <- tweak $ fst $ B16.decode $ B8.pack+ "f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"+ tweakAddPubKey pub twk+ expected = importPubKey $ fst $ B16.decode $ B8.pack+ "04441c3982b97576646e0df0c96736063df6b42f2ee566d13b9f6424302d1379e518fd\+ \c87a14c5435bff7a5db4552042cb4120c6b86a4bbd3d0643f3c14ad01368"++tweakMulPubKeyTest :: Assertion+tweakMulPubKeyTest =+ assertEqual "tweaked keys match" expected tweaked+ where+ tweaked = do+ pub <- importPubKey $ fst $ B16.decode $ B8.pack+ "04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd44705\+ \12213d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"+ twk <- tweak $ fst $ B16.decode $ B8.pack+ "f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"+ tweakMulPubKey pub twk+ expected = importPubKey $ fst $ B16.decode $ B8.pack+ "04f379dc99cdf5c83e433defa267fbb3377d61d6b779c06a0e4ce29ae3ff5353b12ae4\+ \9c9d07e7368f2ba5a446c203255ce912322991a2d6a9d5d5761c61ed1845"++combinePubKeyTest :: Assertion+combinePubKeyTest =+ assertEqual "combined keys match" expected combined+ where+ combined = do+ pub1 <- importPubKey $ fst $ B16.decode $ B8.pack+ "04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd44705\+ \12213d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"+ pub2 <- importPubKey $ fst $ B16.decode $ B8.pack+ "0487d82042d93447008dfe2af762068a1e53ff394a5bf8f68a045fa642b99ea5d1\+ \53f577dd2dba6c7ae4cfd7b6622409d7edd2d76dd13a8092cd3af97b77bd2c77"+ pub3 <- importPubKey $ fst $ B16.decode $ B8.pack+ "049b101edcbe1ee37ff6b2318526a425b629e823d7d8d9154417880595a28000ee\+ \3febd908754b8ce4e491aa6fe488b41fb5d4bb3788e33c9ff95a7a9229166d59"+ combinePubKeys [pub1, pub2, pub3]+ expected = importPubKey $ fst $ B16.decode $ B8.pack+ "043d9a7ec70011efc23c33a7e62d2ea73cca87797e3b659d93bea6aa871aebde56c3bc\+ \6134ca82e324b0ab9c0e601a6d2933afe7fb5d9f3aae900f5c5dc6e362c8"
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}