csecp256k1

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

commit d0d57454fc76ecaa554356c9d583345bc49c197f
parent cbfd7d94632389309cc89921602d6d09bb85a151
Author: Jared Tobin <jared@jtobin.io>
Date:   Fri, 23 Feb 2024 15:29:01 +0400

Flesh out tests a bit.

Diffstat:
Msecp256k1-sys/test/Main.hs | 239++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 236 insertions(+), 3 deletions(-)

diff --git a/secp256k1-sys/test/Main.hs b/secp256k1-sys/test/Main.hs @@ -1,14 +1,48 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE ViewPatterns #-} + module Main where -import Control.Exception (bracket) +import Control.Monad (when) +import Control.Exception (Exception, bracket, throwIO) import Crypto.Secp256k1.Internal import qualified Data.ByteString as BS +import Foreign.C.Types (CUInt) import Foreign.Ptr (Ptr) import qualified Foreign.Ptr as F (nullPtr, castPtr) +import qualified Foreign.Marshal.Alloc as A (alloca, allocaBytes) +import qualified Foreign.Storable as S (poke, peek) import qualified System.Entropy as E import Test.Tasty import Test.Tasty.HUnit +data Secp256k1Error = Secp256k1Error + deriving Show + +instance Exception Secp256k1Error + +_DER_BYTES :: Int +_DER_BYTES = 72 + +_PUB_BYTES_COMPRESSED :: Int +_PUB_BYTES_COMPRESSED = 33 + +_PUB_BYTES_UNCOMPRESSED :: Int +_PUB_BYTES_UNCOMPRESSED = 65 + +_PUB_BYTES_INTERNAL :: Int +_PUB_BYTES_INTERNAL = 64 + +_SIG_BYTES :: Int +_SIG_BYTES = 64 + +_COMPRESSED_FLAG :: CUInt +_COMPRESSED_FLAG = 0x0102 + +_UNCOMPRESSED_FLAG :: CUInt +_UNCOMPRESSED_FLAG = 0x0002 + main :: IO () main = defaultMain units @@ -16,6 +50,14 @@ units :: TestTree units = testGroup "unit tests" [ context_create , context_randomize + , ec_pubkey_parse + , ec_pubkey_serialize_compressed + , ec_pubkey_serialize_uncompressed + , ecdsa_signature_parse_der + , ecdsa_signature_serialize_der + , ecdsa_sign + , ecdsa_verify_compressed + , ecdsa_verify_uncompressed ] wcontext :: (Ptr Context -> IO a) -> IO a @@ -27,8 +69,7 @@ wcontext = wentropy :: (Ptr Seed32 -> IO a) -> IO a wentropy c = do bs <- E.getEntropy 32 - BS.useAsCStringLen bs $ \(b, _) -> - c (F.castPtr b) + BS.useAsCString bs $ \(F.castPtr -> b) -> c b -- context @@ -42,3 +83,195 @@ context_randomize = testCase "secp256k1_context_randomize (success)" $ suc <- wentropy (secp256k1_context_randomize tex) assertBool "success" (suc == 1) +-- ec + +ec_pubkey_parse :: TestTree +ec_pubkey_parse = testCase "secp256k1_ec_pubkey_parse (success)" $ + wcontext $ \tex -> do + -- throws on failure, so any return implies success + _ <- parse_pubkey tex _PUB_COMPRESSED + assertBool "success" True + +ec_pubkey_serialize_compressed :: TestTree +ec_pubkey_serialize_compressed = + testCase "secp256k1_ec_pubkey_serialize (compressed, success)" $ + wcontext $ \tex -> do + par <- parse_pubkey tex _PUB_COMPRESSED + pub <- serialize_pubkey_compressed tex par + assertEqual "success" pub _PUB_COMPRESSED + +ec_pubkey_serialize_uncompressed :: TestTree +ec_pubkey_serialize_uncompressed = + testCase "secp256k1_ec_pubkey_serialize (uncompressed, success)" $ + wcontext $ \tex -> do + par <- parse_pubkey tex _PUB_UNCOMPRESSED + pub <- serialize_pubkey_uncompressed tex par + assertEqual "success" pub _PUB_UNCOMPRESSED + +-- ecdsa + +ecdsa_signature_parse_der :: TestTree +ecdsa_signature_parse_der = + testCase "secp256k1_ecdsa_signature_parse_der (success)" $ + wcontext $ \tex -> do + -- throws on failure, so any return implies success + _ <- parse_der tex _DER + assertBool "success" True + +ecdsa_signature_serialize_der :: TestTree +ecdsa_signature_serialize_der = + testCase "secp256k1_ecdsa_signature_serialize_der (success)" $ + wcontext $ \tex -> do + par <- parse_der tex _DER + der <- serialize_der tex par + assertEqual "success" der _DER + +ecdsa_sign :: TestTree +ecdsa_sign = testCase "secp256k1_ecdsa_sign (success)" $ + wcontext $ \tex -> do + par <- parse_der tex _DER + sig <- sign_ecdsa tex _SEC _HAS + assertEqual "success" sig par + +ecdsa_verify_compressed :: TestTree +ecdsa_verify_compressed = + testCase "secp256k1_ecdsa_verify (compressed, success)" $ + wcontext $ \tex -> do + suc <- verify_ecdsa tex _PUB_COMPRESSED _HAS _DER + assertBool "success" suc + +ecdsa_verify_uncompressed :: TestTree +ecdsa_verify_uncompressed = + testCase "secp256k1_ecdsa_verify (uncompressed, success)" $ + wcontext $ \tex -> do + suc <- verify_ecdsa tex _PUB_UNCOMPRESSED _HAS _DER + assertBool "success" suc + +-- wrappers + +parse_der :: Ptr Context -> BS.ByteString -> IO BS.ByteString +parse_der tex bs = + BS.useAsCStringLen bs $ \(F.castPtr -> der, fromIntegral -> len) -> + A.allocaBytes _SIG_BYTES $ \out -> do + suc <- secp256k1_ecdsa_signature_parse_der tex out der len + when (suc /= 1) $ throwIO Secp256k1Error + let par = F.castPtr out + BS.packCStringLen (par, _SIG_BYTES) + +parse_pubkey :: Ptr Context -> BS.ByteString -> IO BS.ByteString +parse_pubkey tex bs = + BS.useAsCStringLen bs $ \(F.castPtr -> pub, fromIntegral -> len) -> + A.allocaBytes _PUB_BYTES_INTERNAL $ \out -> do + suc <- secp256k1_ec_pubkey_parse tex out pub len + when (suc /= 1) $ throwIO Secp256k1Error + let par = F.castPtr out + BS.packCStringLen (par, _PUB_BYTES_INTERNAL) + +serialize_der :: Ptr Context -> BS.ByteString -> IO BS.ByteString +serialize_der tex bs = A.alloca $ \len -> + A.allocaBytes _DER_BYTES $ \out -> + BS.useAsCString bs $ \(F.castPtr -> sig) -> do + let siz = fromIntegral _DER_BYTES + S.poke len siz + suc <- secp256k1_ecdsa_signature_serialize_der tex out len sig + when (suc /= 1) $ throwIO Secp256k1Error + pek <- S.peek len + let enc = F.castPtr out + nel = fromIntegral pek + BS.packCStringLen (enc, nel) + +serialize_pubkey_compressed :: Ptr Context -> BS.ByteString -> IO BS.ByteString +serialize_pubkey_compressed tex bs = + BS.useAsCString bs $ \(F.castPtr -> pub) -> + A.alloca $ \len -> + A.allocaBytes _PUB_BYTES_COMPRESSED $ \out -> do + let siz = fromIntegral _PUB_BYTES_COMPRESSED + S.poke len siz + suc <- secp256k1_ec_pubkey_serialize tex out len pub _COMPRESSED_FLAG + when (suc /= 1) $ throwIO Secp256k1Error + pec <- S.peek len + let enc = F.castPtr out + nel = fromIntegral pec + BS.packCStringLen (enc, nel) + +serialize_pubkey_uncompressed + :: Ptr Context + -> BS.ByteString + -> IO BS.ByteString +serialize_pubkey_uncompressed tex bs = + BS.useAsCString bs $ \(F.castPtr -> pub) -> + A.alloca $ \len -> + A.allocaBytes _PUB_BYTES_UNCOMPRESSED $ \out -> do + let siz = fromIntegral _PUB_BYTES_UNCOMPRESSED + S.poke len siz + suc <- secp256k1_ec_pubkey_serialize tex out len pub _UNCOMPRESSED_FLAG + when (suc /= 1) $ throwIO Secp256k1Error + pec <- S.peek len + let enc = F.castPtr out + nel = fromIntegral pec + BS.packCStringLen (enc, nel) + +sign_ecdsa :: Ptr Context -> BS.ByteString -> BS.ByteString -> IO BS.ByteString +sign_ecdsa tex key msg = + A.allocaBytes _SIG_BYTES $ \out -> + BS.useAsCString msg $ \(F.castPtr -> has) -> + BS.useAsCString key $ \(F.castPtr -> sec) -> do + suc <- secp256k1_ecdsa_sign tex out has sec F.nullPtr F.nullPtr + when (suc /= 1) $ throwIO Secp256k1Error + let sig = F.castPtr out + BS.packCStringLen (sig, _SIG_BYTES) + +verify_ecdsa + :: Ptr Context + -> BS.ByteString + -> BS.ByteString + -> BS.ByteString + -> IO Bool +verify_ecdsa tex key msg der = do + sig <- parse_der tex der + pub <- parse_pubkey tex key + suc <- BS.useAsCString msg $ \(F.castPtr -> has) -> + BS.useAsCString pub $ \(F.castPtr -> kep) -> + BS.useAsCString sig $ \(F.castPtr -> sip) -> + secp256k1_ecdsa_verify tex sip has kep + pure (suc == 1) + +-- test inputs + +-- a 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" + , "\NAK\SO7\235n\170\242\200\189\&7\251\"\STX o\EOT\NAK\171\SO\154\151z" + , "\253x\178\194n\243\155\&9R\tm1\159\212\177\SOH\199h\173l\DC3.0E" + ] + +-- a 32-byte message hash +_HAS :: BS.ByteString +_HAS = mconcat [ + "\245\203\231\216\129\130\164\184\228\NUL\249k\ACK\DC2\137!\134J" + , "\CAN\CAN}\DC1L\138\232T\ESCVl\138\206\NUL" + ] + +-- a 32-byte secret key +_SEC :: BS.ByteString +_SEC = mconcat [ + "\246RU\tMws\237\141\212\ETB\186\220\159\192E\193\248\SI\220[-%\ETB" + , "+\ETX\FS\230\147>\ETX\154" + ] + +-- 33-byte (compressed) public key +_PUB_COMPRESSED :: BS.ByteString +_PUB_COMPRESSED = mconcat [ + "\ETX\221\237B\ETX\218\201j~\133\242\195t\163|\227\233\201\161U" + , "\167+d\180U\ESC\v\254w\157\212G\ENQ" + ] + +-- 65-byte (uncompressed) public key +_PUB_UNCOMPRESSED :: BS.ByteString +_PUB_UNCOMPRESSED = mconcat [ + "\EOT\221\237B\ETX\218\201j~\133\242\195t\163|\227\233\201\161U\167" + , "+d\180U\ESC\v\254w\157\212G\ENQ\DC2!=^\215\144R,\EOT-\238\142\133" + , "\196\192\236_\150\128\vr\188Y@\200\188\FS^\DC1\228\252\191" + ] +