csecp256k1

secp256k1 bindings.
Log | Files | Refs | README | LICENSE

commit 5d155beae1b3e72052e2f3ac3a2131041c04cde9
parent fbe9710becba648676c3994669103f5438dc7fc5
Author: Jared Tobin <jared@jtobin.io>
Date:   Sat, 24 Feb 2024 21:30:10 +0400

Add extrakeys, schnorr module support.

Diffstat:
Acsecp256k1.cabal | 23+++++++++++++++++++++++
Mflake.nix | 2+-
Dhaskell-secp256k1.cabal | 23-----------------------
Msecp256k1-sys/flake.lock | 6+++---
Msecp256k1-sys/lib/Crypto/Secp256k1/Internal.hs | 188+++++++++++++++++++++++++++++++++++++------------------------------------------
Msecp256k1-sys/secp256k1-sys.cabal | 4++++
Msecp256k1-sys/test/Main.hs | 153++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
7 files changed, 272 insertions(+), 127 deletions(-)

diff --git a/csecp256k1.cabal b/csecp256k1.cabal @@ -0,0 +1,23 @@ +cabal-version: 3.0 +name: csecp256k1 +version: 0.1.0 +synopsis: secp256k1 bindings +description: secp256k1 bindings. +license: MIT +license-file: LICENSE +author: Jared Tobin +maintainer: jared@jtobin.io +category: Cryptography +build-type: Simple + +common warnings + ghc-options: -Wall + +library + import: warnings + default-language: Haskell2010 + hs-source-dirs: lib + exposed-modules: + build-depends: + base ^>= 4.18.2.0 + diff --git a/flake.nix b/flake.nix @@ -1,5 +1,5 @@ { - description = "haskell-secp256k1"; + description = "csecp256k1"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; diff --git a/haskell-secp256k1.cabal b/haskell-secp256k1.cabal @@ -1,23 +0,0 @@ -cabal-version: 3.0 -name: haskell-secp256k1 -version: 0.1.0 -synopsis: secp256k1 bindings -description: secp256k1 bindings. -license: MIT -license-file: LICENSE -author: Jared Tobin -maintainer: jared@jtobin.io -category: Cryptography -build-type: Simple - -common warnings - ghc-options: -Wall - -library - import: warnings - default-language: Haskell2010 - hs-source-dirs: lib - exposed-modules: - build-depends: - base ^>= 4.18.2.0 - diff --git a/secp256k1-sys/flake.lock b/secp256k1-sys/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1708247094, - "narHash": "sha256-H2VS7VwesetGDtIaaz4AMsRkPoSLEVzL/Ika8gnbUnE=", + "lastModified": 1708751719, + "narHash": "sha256-0uWOKSpXJXmXswOvDM5Vk3blB74apFB6rNGWV5IjoN0=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "045b51a3ae66f673ed44b5bbd1f4a341d96703bf", + "rev": "f63ce824cd2f036216eb5f637dfef31e1a03ee89", "type": "github" }, "original": { diff --git a/secp256k1-sys/lib/Crypto/Secp256k1/Internal.hs b/secp256k1-sys/lib/Crypto/Secp256k1/Internal.hs @@ -26,6 +26,20 @@ module Crypto.Secp256k1.Internal ( -- ecdh , secp256k1_ecdh + + -- extrakeys + , KeyPair96 + , XOnlyPublicKey64 + , secp256k1_keypair_create + , secp256k1_keypair_sec + , secp256k1_keypair_pub + , secp256k1_xonly_pubkey_parse + , secp256k1_xonly_pubkey_serialize + , secp256k1_xonly_pubkey_from_pubkey + + -- schnorr + , secp256k1_schnorrsig_sign32 + , secp256k1_schnorrsig_verify ) where import Foreign.Ptr (Ptr) @@ -152,7 +166,6 @@ foreign import capi -> Ptr Sig64 -> IO CInt - -- ecdh foreign import capi @@ -166,103 +179,80 @@ foreign import capi -> Ptr b -> IO CInt +-- extrakeys + +data KeyPair96 + +data XOnlyPublicKey64 + +foreign import capi + "secp256k1_extrakeys.h haskellsecp256k1_v0_1_0_keypair_create" + secp256k1_keypair_create + :: Ptr Context + -> Ptr KeyPair96 + -> Ptr SecKey32 + -> IO CInt + +foreign import capi + "secp256k1_extrakeys.h haskellsecp256k1_v0_1_0_keypair_pub" + secp256k1_keypair_pub + :: Ptr Context + -> Ptr PubKey64 + -> Ptr KeyPair96 + -> IO CInt + +foreign import capi + "secp256k1_extrakeys.h haskellsecp256k1_v0_1_0_keypair_sec" + secp256k1_keypair_sec + :: Ptr Context + -> Ptr SecKey32 + -> Ptr KeyPair96 + -> IO CInt + +foreign import capi + "secp256k1_extrakeys.h haskellsecp256k1_v0_1_0_xonly_pubkey_parse" + secp256k1_xonly_pubkey_parse + :: Ptr Context + -> Ptr XOnlyPublicKey64 + -> Ptr CUChar + -> IO CInt + +foreign import capi + "secp256k1_extrakeys.h haskellsecp256k1_v0_1_0_xonly_pubkey_serialize" + secp256k1_xonly_pubkey_serialize + :: Ptr Context + -> Ptr CUChar + -> Ptr XOnlyPublicKey64 + -> IO CInt + +foreign import capi + "secp256k1_extrakeys.h haskellsecp256k1_v0_1_0_xonly_pubkey_from_pubkey" + secp256k1_xonly_pubkey_from_pubkey + :: Ptr Context + -> Ptr XOnlyPublicKey64 + -> Ptr CInt + -> Ptr PubKey64 + -> IO CInt + -- schnorr --- foreign import capi --- "secp256k1_schnorrsig.h haskellsecp256k1_v0_1_0_schnorrsig_sign32" --- secp256k1_schnorrsig_sign32 --- :: Ptr Context --- -> Ptr CUChar --- -> Ptr CUChar --- -> Ptr KeyPair --- -> Ptr CUChar --- -> IO CInt - --- foreign import capi --- "secp256k1_schnorrsig.h haskellsecp256k1_v0_1_0_schnorrsig_verify" --- secp256k1_schnorrsig_verify --- :: Ptr Context --- -> Ptr CUChar --- -> Ptr CUChar --- -> CSize --- -> Ptr XOnlyPublicKey --- -> IO CInt --- --- -- foreign import capi --- -- "secp256k1_extrakeys.h haskellsecp256k1_v0_1_0_keypair_create" --- -- secp256k1_keypair_create --- -- :: Ptr Context --- -- -> Ptr KeyPair --- -- -> Ptr CUChar --- -- -> IO CInt --- -- --- -- foreign import capi --- -- "secp256k1_extrakeys.h haskellsecp256k1_v0_1_0_xonly_pubkey_parse" --- -- secp256k1_xonly_pubkey_parse --- -- :: Ptr Context --- -- -> Ptr XOnlyPublicKey --- -- -> Ptr CUChar --- -- -> IO CInt --- -- --- -- foreign import capi --- -- "secp256k1_extrakeys.h haskellsecp256k1_v0_1_0_xonly_pubkey_serialize" --- -- secp256k1_xonly_pubkey_serialize --- -- :: Ptr Context --- -- -> Ptr CUChar --- -- -> Ptr XOnlyPublicKey --- -- -> IO CInt --- -- --- -- foreign import capi --- -- "secp256k1_extrakeys.h haskellsecp256k1_v0_1_0_xonly_pubkey_from_pubkey" --- -- secp256k1_xonly_pubkey_from_pubkey --- -- :: Ptr Context --- -- -> Ptr XOnlyPublicKey --- -- -> Ptr CInt --- -- -> Ptr PublicKey --- -- -> IO CInt --- -- --- -- foreign import capi --- -- "secp256k1_extrakeys.h haskellsecp256k1_v0_1_0_xonly_pubkey_cmp" --- -- secp256k1_xonly_pubkey_cmp --- -- :: Ptr Context --- -- -> Ptr XOnlyPublicKey --- -- -> Ptr XOnlyPublicKey --- -- -> IO CInt --- -- --- -- foreign import capi --- -- "secp256k1_extrakeys.h haskellsecp256k1_v0_1_0_xonly_pubkey_tweak_add" --- -- secp256k1_xonly_pubkey_tweak_add --- -- :: Ptr Context --- -- -> Ptr PublicKey --- -- -> Ptr XOnlyPublicKey --- -- -> Ptr CUChar --- -- -> IO CInt --- -- --- -- foreign import capi --- -- "secp256k1_extrakeys.h haskellsecp256k1_v0_1_0_keypair_xonly_pub" --- -- secp256k1_keypair_xonly_pub --- -- :: Ptr Context --- -- -> Ptr XOnlyPublicKey --- -- -> Ptr CInt --- -- -> Ptr KeyPair --- -- -> IO CInt --- -- --- -- foreign import capi --- -- "secp256k1_extrakeys.h haskellsecp256k1_v0_1_0_keypair_xonly_tweak_add" --- -- secp256k1_keypair_xonly_tweak_add --- -- :: Ptr Context --- -- -> Ptr KeyPair --- -- -> Ptr CUChar --- -- -> IO CInt --- -- --- -- foreign import capi --- -- "secp256k1_extrakeys.h haskellsecp256k1_v0_1_0_xonly_pubkey_tweak_add_check" --- -- secp256k1_xonly_pubkey_tweak_add_check --- -- :: Ptr Context --- -- -> Ptr CUChar --- -- -> CInt --- -- -> Ptr XOnlyPublicKey --- -- -> Ptr CUChar --- -- -> IO CInt --- -- --- -- +foreign import capi + "secp256k1_schnorrsig.h haskellsecp256k1_v0_1_0_schnorrsig_sign32" + secp256k1_schnorrsig_sign32 + :: Ptr Context + -> Ptr Sig64 + -> Ptr MsgHash32 + -> Ptr KeyPair96 + -> Ptr CUChar + -> IO CInt + +foreign import capi + "secp256k1_schnorrsig.h haskellsecp256k1_v0_1_0_schnorrsig_verify" + secp256k1_schnorrsig_verify + :: Ptr Context + -> Ptr Sig64 + -> Ptr CUChar + -> CSize + -> Ptr XOnlyPublicKey64 + -> IO CInt + diff --git a/secp256k1-sys/secp256k1-sys.cabal b/secp256k1-sys/secp256k1-sys.cabal @@ -32,6 +32,8 @@ library includes: secp256k1.h , secp256k1_ecdh.h + , secp256k1_extrakeys.h + , secp256k1_schnorrsig.h c-sources: depend/secp256k1/src/precomputed_ecmult_gen.c @@ -40,6 +42,8 @@ library cc-options: -DENABLE_MODULE_ECDH + -DENABLE_MODULE_EXTRAKEYS + -DENABLE_MODULE_SCHNORRSIG test-suite tests type: exitcode-stdio-1.0 diff --git a/secp256k1-sys/test/Main.hs b/secp256k1-sys/test/Main.hs @@ -25,6 +25,9 @@ instance Exception Secp256k1Error _DER_BYTES :: Int _DER_BYTES = 72 +_PUB_BYTES_XONLY :: Int +_PUB_BYTES_XONLY = 32 + _PUB_BYTES_COMPRESSED :: Int _PUB_BYTES_COMPRESSED = 33 @@ -40,6 +43,9 @@ _SEC_BYTES = 32 _SIG_BYTES :: Int _SIG_BYTES = 64 +_KEYPAIR_BYTES :: Int +_KEYPAIR_BYTES = 96 + _COMPRESSED_FLAG :: CUInt _COMPRESSED_FLAG = 0x0102 @@ -63,6 +69,11 @@ units = testGroup "unit tests" [ , ecdsa_verify_compressed , ecdsa_verify_uncompressed , ecdh_test + , xonly_pubkey_serialize_test + , xonly_pubkey_parse_test + , keypair_create_test + , schnorr_sign32 + , schnorr_verify ] wcontext :: (Ptr Context -> IO a) -> IO a @@ -168,6 +179,49 @@ ecdh_test = testCase "secp256k1_ecdh (success)" $ _ <- ecdh tex _PUB_COMPRESSED _SEC assertBool "success" True +-- extrakeys + +xonly_pubkey_serialize_test :: TestTree +xonly_pubkey_serialize_test = + testCase "secp256k1_xonly_pubkey_serialize (success)" $ do + pux <- wcontext $ \tex -> do + key <- xonly_pubkey_from_pubkey tex _PUB_COMPRESSED + xonly_pubkey_serialize tex key + assertEqual "success" pux _PUB_XONLY + +xonly_pubkey_parse_test :: TestTree +xonly_pubkey_parse_test = + testCase "secp256k1_xonly_pubkey_parse (success)" $ do + wcontext $ \tex -> do + pux <- xonly_pubkey_parse tex _PUB_XONLY + pub <- xonly_pubkey_serialize tex pux + assertEqual "success" pub _PUB_XONLY + +keypair_create_test :: TestTree +keypair_create_test = + testCase "secp256k1_keypair_create (success)" $ do + wcontext $ \tex -> do + per <- keypair_create tex _SEC + sec <- keypair_sec tex per + pub <- keypair_pub tex per + ser <- serialize_pubkey_compressed tex pub + assertEqual "success" sec _SEC + assertEqual "success" ser _PUB_COMPRESSED + +-- schnorr + +schnorr_sign32 :: TestTree +schnorr_sign32 = testCase "secp256k1_schnorrsig_sign32 (success)" $ do + wcontext $ \tex -> do + sig <- schnorrsig_sign32 tex _HAS _SEC + assertEqual "success" sig _SIG_SCHNORR + +schnorr_verify :: TestTree +schnorr_verify = testCase "secp256k1_schnorrsig_verify (success)" $ do + wcontext $ \tex -> do + suc <- schnorrsig_verify tex _SIG_SCHNORR _HAS _PUB_COMPRESSED + assertBool "success" suc + -- wrappers parse_der :: Ptr Context -> BS.ByteString -> IO BS.ByteString @@ -277,9 +331,91 @@ ecdh tex pub sec = let key = F.castPtr out BS.packCStringLen (key, _SEC_BYTES) +xonly_pubkey_from_pubkey :: Ptr Context -> BS.ByteString -> IO BS.ByteString +xonly_pubkey_from_pubkey tex pub = + A.allocaBytes _PUB_BYTES_INTERNAL $ \out -> do + par <- parse_pubkey tex pub + BS.useAsCString par $ \(F.castPtr -> pab) -> do + -- returns 1 always + _ <- secp256k1_xonly_pubkey_from_pubkey tex out F.nullPtr pab + let key = F.castPtr out + BS.packCStringLen (key, _PUB_BYTES_INTERNAL) + +xonly_pubkey_serialize :: Ptr Context -> BS.ByteString -> IO BS.ByteString +xonly_pubkey_serialize tex pux = + A.allocaBytes _PUB_BYTES_XONLY $ \out -> do + BS.useAsCString pux $ \(F.castPtr -> key) -> do + -- returns 1 always + _ <- secp256k1_xonly_pubkey_serialize tex out key + let kep = F.castPtr out + BS.packCStringLen (kep, _PUB_BYTES_XONLY) + +xonly_pubkey_parse :: Ptr Context -> BS.ByteString -> IO BS.ByteString +xonly_pubkey_parse tex pub = + A.allocaBytes _PUB_BYTES_INTERNAL $ \out -> + BS.useAsCString pub $ \(F.castPtr -> pux) -> do + suc <- secp256k1_xonly_pubkey_parse tex out pux + when (suc /= 1) $ throwIO Secp256k1Error + let key = F.castPtr out + BS.packCStringLen (key, _PUB_BYTES_INTERNAL) + +keypair_create :: Ptr Context -> BS.ByteString -> IO BS.ByteString +keypair_create tex sec = + A.allocaBytes _KEYPAIR_BYTES $ \out -> + BS.useAsCString sec $ \(F.castPtr -> key) -> do + suc <- secp256k1_keypair_create tex out key + when (suc /= 1) $ throwIO Secp256k1Error + let per = F.castPtr out + BS.packCStringLen (per, _KEYPAIR_BYTES) + +keypair_pub :: Ptr Context -> BS.ByteString -> IO BS.ByteString +keypair_pub tex per = + A.allocaBytes _PUB_BYTES_INTERNAL $ \out -> + BS.useAsCString per $ \(F.castPtr -> par) -> do + _ <- secp256k1_keypair_pub tex out par + let enc = F.castPtr out + BS.packCStringLen (enc, _PUB_BYTES_INTERNAL) + +keypair_sec :: Ptr Context -> BS.ByteString -> IO BS.ByteString +keypair_sec tex per = + A.allocaBytes _SEC_BYTES $ \out -> + BS.useAsCString per $ \(F.castPtr -> par) -> do + _ <- secp256k1_keypair_sec tex out par + let enc = F.castPtr out + BS.packCStringLen (enc, _SEC_BYTES) + +schnorrsig_sign32 + :: Ptr Context + -> BS.ByteString + -> BS.ByteString + -> IO BS.ByteString +schnorrsig_sign32 tex msg sec = + A.allocaBytes _SIG_BYTES $ \out -> + BS.useAsCString msg $ \(F.castPtr -> has) -> do + per <- keypair_create tex sec + BS.useAsCString per $ \(F.castPtr -> pur) -> do + suc <- secp256k1_schnorrsig_sign32 tex out has pur F.nullPtr + when (suc /= 1) $ throwIO Secp256k1Error + let enc = F.castPtr out + BS.packCStringLen (enc, _SIG_BYTES) + +schnorrsig_verify + :: Ptr Context + -> BS.ByteString + -> BS.ByteString + -> BS.ByteString + -> IO Bool +schnorrsig_verify tex sig msg pub = + BS.useAsCString sig $ \(F.castPtr -> sip) -> + BS.useAsCStringLen msg $ \(F.castPtr -> has, fromIntegral -> len) -> do + pux <- xonly_pubkey_from_pubkey tex pub + BS.useAsCString pux $ \(F.castPtr -> pax) -> do + suc <- secp256k1_schnorrsig_verify tex sip has len pax + pure (suc == 1) + -- test inputs --- a DER-encoded signature +-- DER-encoded signature _DER :: BS.ByteString _DER = mconcat [ "0E\STX!\NUL\245\STX\191\160z\244>~\242ea\139\r\146\154v\EM\238\SOH\214" @@ -316,3 +452,18 @@ _PUB_UNCOMPRESSED = mconcat [ , "\196\192\236_\150\128\vr\188Y@\200\188\FS^\DC1\228\252\191" ] +-- 32-byte x-only pubkey +_PUB_XONLY :: BS.ByteString +_PUB_XONLY = mconcat [ + "\221\237B\ETX\218\201j~\133\242\195t\163|\227\233\201\161U\167+d" + , "\180U\ESC\v\254w\157\212G\ENQ" + ] + +-- 64-byte schnorr signature +_SIG_SCHNORR :: BS.ByteString +_SIG_SCHNORR = mconcat [ + "\214\185AtJ\189\250Gp\NAK2\221\DC2[\182\209\192j{\140^\222R\NUL~" + , "\139d@<\138\163rh\247\152\r\228\175\236\219\156\151\214~\135\&7" + , "\225\&6\234\220;\164R\191\170\186\243\NAK\147\f\144\156ez" + ] +