secp256k1-haskell 1.2.0 → 1.3.0
raw patch · 7 files changed
+239/−1 lines, 7 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Crypto.Secp256k1: data Bip340Sig
+ Crypto.Secp256k1: data Rand32
+ Crypto.Secp256k1: data XOnlyPubKey
+ Crypto.Secp256k1: deriveXOnlyPubKey :: Ctx -> PubKey -> XOnlyPubKey
+ Crypto.Secp256k1: mkRand32 :: ByteString -> Maybe Rand32
+ Crypto.Secp256k1: signBip340 :: Ctx -> SecKey -> Msg -> Maybe Rand32 -> Maybe Bip340Sig
+ Crypto.Secp256k1: verifyBip340 :: Ctx -> XOnlyPubKey -> Msg -> Bip340Sig -> Bool
+ Crypto.Secp256k1.Internal.Base: Bip340Sig :: ByteString -> Bip340Sig
+ Crypto.Secp256k1.Internal.Base: Rand32 :: ByteString -> Rand32
+ Crypto.Secp256k1.Internal.Base: XOnlyPubKey :: ByteString -> XOnlyPubKey
+ Crypto.Secp256k1.Internal.Base: [$sel:get:Bip340Sig] :: Bip340Sig -> ByteString
+ Crypto.Secp256k1.Internal.Base: [$sel:get:Rand32] :: Rand32 -> ByteString
+ Crypto.Secp256k1.Internal.Base: [$sel:get:XOnlyPubKey] :: XOnlyPubKey -> ByteString
+ Crypto.Secp256k1.Internal.Base: deriveXOnlyPubKey :: Ctx -> PubKey -> XOnlyPubKey
+ Crypto.Secp256k1.Internal.Base: exportXOnlyPubKey :: Ctx -> XOnlyPubKey -> ByteString
+ Crypto.Secp256k1.Internal.Base: importXOnlyPubKey :: Ctx -> ByteString -> Maybe XOnlyPubKey
+ Crypto.Secp256k1.Internal.Base: instance Data.Hashable.Class.Hashable Crypto.Secp256k1.Internal.Base.XOnlyPubKey
+ Crypto.Secp256k1.Internal.Base: instance GHC.Classes.Eq Crypto.Secp256k1.Internal.Base.Bip340Sig
+ Crypto.Secp256k1.Internal.Base: instance GHC.Classes.Eq Crypto.Secp256k1.Internal.Base.XOnlyPubKey
+ Crypto.Secp256k1.Internal.Base: instance GHC.Show.Show Crypto.Secp256k1.Internal.Base.Bip340Sig
+ Crypto.Secp256k1.Internal.Base: instance GHC.Show.Show Crypto.Secp256k1.Internal.Base.XOnlyPubKey
+ Crypto.Secp256k1.Internal.Base: mkRand32 :: ByteString -> Maybe Rand32
+ Crypto.Secp256k1.Internal.Base: newtype Bip340Sig
+ Crypto.Secp256k1.Internal.Base: newtype Rand32
+ Crypto.Secp256k1.Internal.Base: newtype XOnlyPubKey
+ Crypto.Secp256k1.Internal.Base: signBip340 :: Ctx -> SecKey -> Msg -> Maybe Rand32 -> Maybe Bip340Sig
+ Crypto.Secp256k1.Internal.Base: verifyBip340 :: Ctx -> XOnlyPubKey -> Msg -> Bip340Sig -> Bool
+ Crypto.Secp256k1.Internal.BaseOps: keyPairCreate :: Ptr LCtx -> Ptr KeyPair -> Ptr SecKey32 -> IO CInt
+ Crypto.Secp256k1.Internal.BaseOps: schnorrSign :: Ptr LCtx -> Ptr Sig64 -> Ptr Msg32 -> Ptr KeyPair -> Ptr CUChar -> IO CInt
+ Crypto.Secp256k1.Internal.BaseOps: schnorrVerify :: Ptr LCtx -> Ptr Sig64 -> Ptr Msg32 -> CSize -> Ptr XOPubKey32 -> IO CInt
+ Crypto.Secp256k1.Internal.BaseOps: xOnlyPubKeyFromPubKey :: Ptr LCtx -> Ptr XOPubKey32 -> Ptr CInt -> Ptr PubKey64 -> IO CInt
+ Crypto.Secp256k1.Internal.BaseOps: xOnlyPubKeyParse :: Ptr LCtx -> Ptr XOPubKey32 -> Ptr CUChar -> IO CInt
+ Crypto.Secp256k1.Internal.BaseOps: xOnlyPubKeySerialize :: Ptr LCtx -> Ptr CUChar -> Ptr XOPubKey32 -> IO CInt
+ Crypto.Secp256k1.Internal.ForeignTypes: data KeyPair
+ Crypto.Secp256k1.Internal.ForeignTypes: data XOPubKey32
Files
- CHANGELOG.md +6/−0
- secp256k1-haskell.cabal +1/−1
- src/Crypto/Secp256k1.hs +10/−0
- src/Crypto/Secp256k1/Internal/Base.hs +126/−0
- src/Crypto/Secp256k1/Internal/BaseOps.hs +66/−0
- src/Crypto/Secp256k1/Internal/ForeignTypes.hs +4/−0
- test/Crypto/Secp256k1Spec.hs +26/−0
CHANGELOG.md view
@@ -4,6 +4,12 @@ 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.2.0] - 2023-03-14++### Changed++- Make compatible with latest upstream LTS Haskell.+ ## [1.1.0] - 2023-11-01 ### Changed
secp256k1-haskell.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: secp256k1-haskell-version: 1.2.0+version: 1.3.0 synopsis: Bindings for secp256k1 description: Sign and verify signatures using the secp256k1 library. category: Crypto
src/Crypto/Secp256k1.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE DuplicateRecordFields #-}+ -- | -- Module : Crypto.Secp256k1 -- License : UNLICENSE@@ -57,6 +58,15 @@ tweakMulPubKey, combinePubKeys, tweakNegate,++ -- * BIP340 Support+ XOnlyPubKey,+ deriveXOnlyPubKey,+ Rand32,+ mkRand32,+ Bip340Sig,+ signBip340,+ verifyBip340, ) where
src/Crypto/Secp256k1/Internal/Base.hs view
@@ -40,6 +40,12 @@ ecdsaSignatureSerializeCompact, ecdsaSignatureSerializeDer, ecdsaVerify,+ keyPairCreate,+ schnorrSign,+ schnorrVerify,+ xOnlyPubKeyFromPubKey,+ xOnlyPubKeyParse,+ xOnlyPubKeySerialize, ) import Crypto.Secp256k1.Internal.Context (Ctx (..)) import Crypto.Secp256k1.Internal.ForeignTypes@@ -57,6 +63,7 @@ ) import Data.ByteString (ByteString) import Data.ByteString qualified as BS+import Data.ByteString.Unsafe qualified as BU import Data.Hashable (Hashable (..)) import Data.Maybe (fromJust, fromMaybe, isJust) import Data.String (IsString (..))@@ -445,3 +452,122 @@ valid_bs = bs_gen `suchThat` isJust bs_gen = secKey . BS.pack <$> replicateM 32 arbitraryBoundedRandom gen_key = fromJust <$> valid_bs++-- | An x-only pubkey corresponds to the keys @(x,y)@ and @(x, -y)@. The+-- equality test only checks the x-coordinate. An x-only pubkey serializes to 32+-- bytes.+--+-- @since 1.3.0+newtype XOnlyPubKey = XOnlyPubKey {get :: ByteString}+ deriving (Eq, Hashable)++instance Show XOnlyPubKey where+ showsPrec _ = showsHex . (.get)++newtype Bip340Sig = Bip340Sig {get :: ByteString}+ deriving (Eq)++instance Show Bip340Sig where+ showsPrec _ = showsHex . (.get)++newtype Rand32 = Rand32 {get :: ByteString}++-- | Create a 'Rand32' value from a 32-byte 'ByteString'+mkRand32 :: ByteString -> Maybe Rand32+mkRand32 bytes+ | BS.length bytes == 32 = Just $ Rand32 bytes+ | otherwise = Nothing++-- | Encode an x-only pubkey to bytes+--+-- @since 1.3.0+exportXOnlyPubKey :: Ctx -> XOnlyPubKey -> ByteString+exportXOnlyPubKey (Ctx fctx) pubKey = unsafePerformIO $+ withForeignPtr fctx $ \ctx ->+ unsafeUseByteString pubKey.get $ \(pub_key, _) ->+ allocaBytes 32 $ \out_ptr -> do+ result <- xOnlyPubKeySerialize ctx out_ptr pub_key+ if isSuccess result+ then packByteString (out_ptr, 32)+ else free out_ptr >> error "Unable to serialize x-only pubkey"++-- | Import an x-only pubkey from bytes+--+-- @since 1.3.0+importXOnlyPubKey :: Ctx -> ByteString -> Maybe XOnlyPubKey+importXOnlyPubKey (Ctx fctx) inputBytes+ | BS.length inputBytes == 32 = unsafePerformIO $+ withForeignPtr fctx $ \ctx ->+ unsafeUseByteString inputBytes $ \(input, _) -> do+ pub_key <- mallocBytes 64+ result <- xOnlyPubKeyParse ctx pub_key input+ if isSuccess result+ then Just . XOnlyPubKey <$> unsafePackByteString (pub_key, 64)+ else pure Nothing+ | otherwise = Nothing++-- | Derive an x-only key from a pub key+--+-- @since 1.3.0+deriveXOnlyPubKey :: Ctx -> PubKey -> XOnlyPubKey+deriveXOnlyPubKey (Ctx fctx) pubKey = unsafePerformIO $+ withForeignPtr fctx $ \ctx ->+ unsafeUseByteString pubKey.get $ \(pub_key, _) -> do+ xonly_pub_key <- mallocBytes 64+ result <- xOnlyPubKeyFromPubKey ctx xonly_pub_key nullPtr pub_key+ if isSuccess result+ then XOnlyPubKey <$> unsafePackByteString (xonly_pub_key, 64)+ else free xonly_pub_key >> error "Unable to derive x-only pubkey"++-- | Sign a message according to BIP 340+--+-- @since 1.3.0+signBip340 ::+ Ctx ->+ -- | Secret key+ SecKey ->+ -- | Message+ Msg ->+ -- | Randomness+ Maybe Rand32 ->+ -- | Signature+ Maybe Bip340Sig+signBip340 (Ctx fctx) theSecKey message rand32 = unsafePerformIO $+ withForeignPtr fctx $ \ctx ->+ unsafeUseByteString theSecKey.get $ \(sec_key, _) ->+ allocaBytes 96 $ \key_pair -> do+ -- The 'SecKey' API guarantees that the following call will succeed+ _ <- keyPairCreate ctx key_pair sec_key+ unsafeUseByteString message.get $ \(msg_ptr, _) -> do+ withRand32 $ \(rand_ptr, _) -> do+ sig_ptr <- mallocBytes 64+ result <- schnorrSign ctx sig_ptr msg_ptr key_pair rand_ptr+ if isSuccess result+ then Just . Bip340Sig <$> unsafePackByteString (sig_ptr, 64)+ else Nothing <$ free sig_ptr+ where+ withRand32 = maybe ($ (nullPtr, 0)) (unsafeUseByteString . (.get)) rand32++-- | Verify a message according to BIP 340+--+-- @since 1.3.0+verifyBip340 ::+ Ctx ->+ XOnlyPubKey ->+ -- | Message+ Msg ->+ -- | Signature+ Bip340Sig ->+ Bool+verifyBip340 (Ctx fctx) xOnlyPubKey message sig = unsafePerformIO $+ withForeignPtr fctx $ \ctx ->+ unsafeUseByteString xOnlyPubKey.get $ \(pub_key, _) ->+ unsafeUseByteString message.get $ \(msg_ptr, messageSize) ->+ unsafeUseByteString sig.get $ \(sig_ptr, _) ->+ isSuccess+ <$> schnorrVerify+ ctx+ sig_ptr+ msg_ptr+ (fromIntegral messageSize)+ pub_key
src/Crypto/Secp256k1/Internal/BaseOps.hs view
@@ -11,6 +11,7 @@ import Crypto.Secp256k1.Internal.ForeignTypes ( Compact64,+ KeyPair, LCtx, Msg32, NonceFun,@@ -21,6 +22,7 @@ SerFlags, Sig64, Tweak32,+ XOPubKey32, ) import Foreign (FunPtr, Ptr) import Foreign.C@@ -170,3 +172,67 @@ -- | number of public keys CInt -> IO Ret++foreign import ccall unsafe "secp256k1.h secp256k1_schnorrsig_sign"+ schnorrSign ::+ Ptr LCtx ->+ -- | Signature+ Ptr Sig64 ->+ -- | Message+ Ptr Msg32 ->+ -- | Keypair+ Ptr KeyPair ->+ -- | Randomness+ Ptr CUChar ->+ IO CInt++foreign import ccall unsafe "secp256k1.h secp256k1_schnorrsig_verify"+ schnorrVerify ::+ Ptr LCtx ->+ -- | Signature+ Ptr Sig64 ->+ -- | Message+ Ptr Msg32 ->+ -- | Message length+ CSize ->+ -- | X-Only pubkey+ Ptr XOPubKey32 ->+ IO CInt++foreign import ccall unsafe "secp256k1.h secp256k1_keypair_create"+ keyPairCreate ::+ Ptr LCtx ->+ -- | Keypair+ Ptr KeyPair ->+ -- | Secret key+ Ptr SecKey32 ->+ IO CInt++foreign import ccall unsafe "secp256k1.h secp256k1_xonly_pubkey_parse"+ xOnlyPubKeyParse ::+ Ptr LCtx ->+ -- | Parsed X-Only pubkey+ Ptr XOPubKey32 ->+ -- | Input buffer+ Ptr CUChar ->+ IO CInt++foreign import ccall unsafe "secp256k1.h secp256k1_xonly_pubkey_serialize"+ xOnlyPubKeySerialize ::+ Ptr LCtx ->+ -- | Output buffer+ Ptr CUChar ->+ -- | X-Only pubkey+ Ptr XOPubKey32 ->+ IO CInt++foreign import ccall unsafe "secp256k1.h secp256k1_xonly_pubkey_from_pubkey"+ xOnlyPubKeyFromPubKey ::+ Ptr LCtx ->+ -- | X-only pubkey+ Ptr XOPubKey32 ->+ -- | Parity+ Ptr CInt ->+ -- | Pubkey+ Ptr PubKey64 ->+ IO CInt
src/Crypto/Secp256k1/Internal/ForeignTypes.hs view
@@ -16,6 +16,10 @@ data PubKey64 +data XOPubKey32++data KeyPair+ data Msg32 data Sig64
test/Crypto/Secp256k1Spec.hs view
@@ -72,6 +72,11 @@ property $ combinePubKeyEmptyListTest ctx it "negates tweak" $ \ctx -> property $ negateTweakTest ctx+ describe "BIP 340" $ do+ it "verifies a signed message (null rand32)" $ \ctx ->+ property $ bip340SigTestNull ctx+ it "verifies a signed message (not-null rand32)" $ \ctx ->+ property $ bip340SigTest ctx hexToBytes :: String -> BS.ByteString hexToBytes = decodeBase16 . assertBase16 . B8.pack@@ -310,3 +315,24 @@ Just minusOneTwk = tweakNegate ctx oneTwk Just twoKey = tweakAddSecKey ctx oneKey oneTwk Just subtracted = tweakAddSecKey ctx twoKey minusOneTwk++bip340SigTestNull :: Ctx -> Assertion+bip340SigTestNull ctx =+ assertBool "verifies signature" $+ verifyBip340 ctx pk theMsg sig+ where+ Just sk = secKey $ BS.replicate 32 0x01+ pk = deriveXOnlyPubKey ctx $ derivePubKey ctx sk+ Just theMsg = msg $ BS.replicate 32 0x02+ Just sig = signBip340 ctx sk theMsg Nothing++bip340SigTest :: Ctx -> Assertion+bip340SigTest ctx =+ assertBool "verifies signature" $+ verifyBip340 ctx pk theMsg sig+ where+ Just sk = secKey $ BS.replicate 32 0x01+ pk = deriveXOnlyPubKey ctx $ derivePubKey ctx sk+ Just theMsg = msg $ BS.replicate 32 0x02+ Just r = mkRand32 $ BS.replicate 32 0x03+ Just sig = signBip340 ctx sk theMsg (Just r)