secp256k1-haskell-1.0.0: test/Crypto/Secp256k1/InternalSpec.hs
{-# LANGUAGE OverloadedStrings #-}
module Crypto.Secp256k1.InternalSpec (spec) where
import Control.Exception
import Control.Monad
import Control.Monad.Trans
import Crypto.Secp256k1.Internal
import Data.Base16.Types (assertBase16)
import Data.ByteString
( ByteString,
copy,
packCStringLen,
useAsCStringLen,
)
import Data.ByteString.Base16 (decodeBase16)
import Data.Either (fromRight)
import Foreign
import System.Entropy
import Test.HUnit (Assertion, assertBool, assertEqual)
import Test.Hspec
spec :: Spec
spec = do
describe "housekeeping" $ do
it "creates context" createContextTest
it "randomizes context" randomizeContextTest
it "clones context" cloneContextTest
describe "serialization" $ do
it "parses public key" ecPubkeyParseTest
it "serializes public key" ecPubKeySerializeTest
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
withCtxPtr :: CtxFlags -> (Ptr LCtx -> IO a) -> IO a
withCtxPtr f = bracket (contextCreate f) contextDestroy
withEntropy :: (Ptr Seed32 -> IO a) -> IO a
withEntropy f =
getEntropy 32 >>= \e ->
useByteString e $ \(s, _) -> f s
createContextTest :: Assertion
createContextTest = withCtxPtr signVerify $ \ctx -> do
assertBool "context not null" $ ctx /= nullPtr
randomizeContextTest :: Assertion
randomizeContextTest = withCtxPtr sign $ \ctx -> do
ret <- withEntropy (contextRandomize ctx)
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
allocaBytes 64 $ \pubkey ->
ecPubKeyParse x pubkey (castPtr i) (fromIntegral il)
assertBool "parsed public key" (isSuccess ret)
where
der =
decodeBase16 $
assertBase16
"03dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd44705"
ecPubKeySerializeTest :: Assertion
ecPubKeySerializeTest = do
(ret, dec) <- liftIO $
useByteString der $ \(i, il) ->
allocaBytes 64 $ \k ->
alloca $ \ol ->
allocaBytes 72 $ \o -> do
poke ol 72
x <- contextCreate verify
ret1 <- ecPubKeyParse x k i 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 =
decodeBase16 $
assertBase16
"03dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd44705"
ecdsaSignatureParseDerTest :: Assertion
ecdsaSignatureParseDerTest = do
ret <- liftIO $ useAsCStringLen der $ \(d, dl) -> allocaBytes 64 $ \s -> do
x <- contextCreate verify
ecdsaSignatureParseDer x s (castPtr d) (fromIntegral dl)
assertBool "parsed signature successfully" $ isSuccess ret
where
der =
decodeBase16 $
assertBase16
"3045022100f502bfa07af43e7ef265618b0d929a7619ee01d6150e37eb6eaaf2c8bd37\
\fb2202206f0415ab0e9a977afd78b2c26ef39b3952096d319fd4b101c768ad6c132e30\
\45"
parseDer :: Ptr LCtx -> ByteString -> IO ByteString
parseDer x bs =
useAsCStringLen bs $ \(d, dl) ->
allocaBytes 64 $ \s -> do
ret <- ecdsaSignatureParseDer x s (castPtr d) (fromIntegral dl)
unless (isSuccess ret) $ error "could not parse DER"
packByteString (s, 64)
ecdsaSignatureSerializeDerTest :: Assertion
ecdsaSignatureSerializeDerTest = do
(ret, enc) <- liftIO . bracket (contextCreate verify) contextDestroy $ \x -> do
sig <- parseDer x der
(ret, enc) <- alloca $ \ol ->
allocaBytes 72 $ \o ->
useByteString sig $ \(s, _) -> do
poke ol 72
ret <- ecdsaSignatureSerializeDer x o ol s
len <- fromIntegral <$> peek ol
enc <- packCStringLen (castPtr o, len)
return (ret, enc)
return (ret, enc)
assertBool "serialization successful" $ isSuccess ret
assertEqual "signatures match" der enc
where
der =
decodeBase16 $
assertBase16
"3045022100f502bfa07af43e7ef265618b0d929a7619ee01d6150e37eb6eaaf2c8bd37\
\fb2202206f0415ab0e9a977afd78b2c26ef39b3952096d319fd4b101c768ad6c132e30\
\45"
ecdsaVerifyTest :: Assertion
ecdsaVerifyTest = withCtxPtr verify $ \ctx -> do
ret <- liftIO $ do
sig <- parseDer ctx der
pk <- useByteString pub $ \(p, pl) ->
allocaBytes 64 $ \k -> do
ret <- ecPubKeyParse ctx k p (fromIntegral pl)
unless (isSuccess ret) $ error "could not parse public key"
packByteString (k, 64)
useByteString msg $ \(m, _) ->
useByteString pk $ \(k, _) ->
useByteString sig $ \(s, _) ->
ecdsaVerify ctx s m k
assertBool "signature valid" $ isSuccess ret
where
der =
decodeBase16 $
assertBase16
"3045022100f502bfa07af43e7ef265618b0d929a7619ee01d6150e37eb6eaaf2c8bd37\
\fb2202206f0415ab0e9a977afd78b2c26ef39b3952096d319fd4b101c768ad6c132e30\
\45"
pub =
decodeBase16 $
assertBase16
"04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd447051221\
\3d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"
msg =
decodeBase16 $
assertBase16
"f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
signCtx :: IO (Ptr LCtx)
signCtx =
contextCreate sign >>= \c ->
withEntropy (contextRandomize c) >>= \r ->
unless (isSuccess r) (error "failed to randomize context") >> return c
withSignCtxPtr :: (Ptr LCtx -> IO a) -> IO a
withSignCtxPtr = bracket signCtx contextDestroy
createPubKey :: Ptr LCtx -> Ptr SecKey32 -> Ptr PubKey64 -> IO ()
createPubKey ctx k p = do
ret <- ecPubKeyCreate ctx p k
unless (isSuccess ret) $ error "failed to create public key"
ecdsaSignTest :: Assertion
ecdsaSignTest = do
der <- liftIO $ withSignCtxPtr $ \ctx -> do
allocaBytes 64 $ \s ->
useByteString msg $ \(m, _) ->
useByteString key $ \(k, _) ->
alloca $ \ol ->
allocaBytes 72 $ \o -> do
poke ol 72
ret1 <- ecdsaSign ctx s m k nullFunPtr nullPtr
unless (isSuccess ret1) $ error "could not sign message"
ret2 <- ecdsaSignatureSerializeDer ctx o ol s
unless (isSuccess ret2) $ error "could not serialize signature"
len <- peek ol
packCStringLen (castPtr o, fromIntegral len)
ret <- liftIO $ do
pub <- withSignCtxPtr $ \ctx -> allocaBytes 64 $ \p ->
useByteString key $ \(s, _) -> do
createPubKey ctx s p
packByteString (p, 64)
withCtxPtr verify $ \ctx -> useByteString msg $ \(m, _) ->
useByteString pub $ \(p, _) -> do
s' <- parseDer ctx der
useByteString s' $ \(s, _) -> ecdsaVerify ctx s m p
assertBool "signature matches" (isSuccess ret)
where
msg =
decodeBase16 $
assertBase16
"f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
key =
decodeBase16 $
assertBase16
"f65255094d7773ed8dd417badc9fc045c1f80fdc5b2d25172b031ce6933e039a"
ecSecKeyVerifyTest :: Assertion
ecSecKeyVerifyTest = withSignCtxPtr $ \ctx -> do
ret <- liftIO $ useByteString key $ \(k, _) -> do
ecSecKeyVerify ctx k
assertBool "valid secret key" $ isSuccess ret
where
key =
decodeBase16 $
assertBase16
"f65255094d7773ed8dd417badc9fc045c1f80fdc5b2d25172b031ce6933e039a"
ecPubkeyCreateTest :: Assertion
ecPubkeyCreateTest = withSignCtxPtr $ \ctx -> do
pk <- liftIO $
useByteString key $ \(s, _) ->
allocaBytes 64 $ \k -> do
createPubKey ctx s k
allocaBytes 65 $ \o ->
alloca $ \ol -> do
poke ol 65
rets <- ecPubKeySerialize ctx o ol k uncompressed
unless (isSuccess rets) $ error "failed to serialize public key"
len <- fromIntegral <$> peek ol
packCStringLen (castPtr o, len)
assertEqual "public key matches" pub pk
where
key =
decodeBase16 $
assertBase16
"f65255094d7773ed8dd417badc9fc045c1f80fdc5b2d25172b031ce6933e039a"
pub =
decodeBase16 $
assertBase16
"04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd447051221\
\3d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"
ecSecKeyTweakAddTest :: Assertion
ecSecKeyTweakAddTest = do
(ret, tweaked) <-
liftIO . withSignCtxPtr $ \ctx ->
useByteString tweak $ \(w, _) ->
useByteString key $ \(k, _) -> do
ret <- ecSecKeyTweakAdd ctx k w
tweaked <- packByteString (k, 32)
return (ret, tweaked)
assertBool "successful secret key tweak" $ isSuccess ret
assertEqual "tweaked keys match" expected tweaked
where
key =
decodeBase16 $
assertBase16
"f65255094d7773ed8dd417badc9fc045c1f80fdc5b2d25172b031ce6933e039a"
tweak =
decodeBase16 $
assertBase16
"f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
expected =
decodeBase16 $
assertBase16
"ec1e3ce1cefa18a671d51125e2b249688d934b0e28f5d1665384d9b02f929059"
ecSecKeyTweakMulTest :: Assertion
ecSecKeyTweakMulTest = do
(ret, tweaked) <- liftIO $ withSignCtxPtr $ \ctx -> do
useByteString tweak $ \(w, _) -> useByteString key $ \(k, _) -> do
ret <- ecSecKeyTweakMul ctx k w
tweaked <- packByteString (k, 32)
return (ret, tweaked)
assertBool "successful secret key tweak" $ isSuccess ret
assertEqual "tweaked keys match" expected tweaked
where
key =
decodeBase16 $
assertBase16
"f65255094d7773ed8dd417badc9fc045c1f80fdc5b2d25172b031ce6933e039a"
tweak =
decodeBase16 $
assertBase16
"f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
expected =
decodeBase16 $
assertBase16
"a96f5962493acb179f60a86a9785fc7a30e0c39b64c09d24fe064d9aef15e4c0"
serializeKey :: Ptr LCtx -> 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 LCtx -> ByteString -> IO ByteString
parseKey x bs =
allocaBytes 64 $ \p ->
useByteString bs $ \(d, dl) -> do
ret <- ecPubKeyParse x p d dl
unless (isSuccess ret) $ error "could not parse public key"
packByteString (p, 64)
ecPubKeyTweakAddTest :: Assertion
ecPubKeyTweakAddTest = do
(ret, tweaked) <- liftIO $ do
x <- contextCreate verify
pk <- copy <$> parseKey x pub
useByteString tweak $ \(w, _) ->
useByteString pk $ \(p, _) -> do
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 =
decodeBase16 $
assertBase16
"04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd447051221\
\3d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"
tweak =
decodeBase16 $
assertBase16
"f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
expected =
decodeBase16 $
assertBase16
"04441c3982b97576646e0df0c96736063df6b42f2ee566d13b9f6424302d1379e518fd\
\c87a14c5435bff7a5db4552042cb4120c6b86a4bbd3d0643f3c14ad01368"
ecPubKeyTweakMulTest :: Assertion
ecPubKeyTweakMulTest = do
(ret, tweaked) <- liftIO $ do
x <- contextCreate verify
pk <- copy <$> parseKey x pub
useByteString tweak $ \(w, _) ->
useByteString pk $ \(p, _) -> do
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 =
decodeBase16 $
assertBase16
"04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd447051221\
\3d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"
tweak =
decodeBase16 $
assertBase16
"f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
expected =
decodeBase16 $
assertBase16
"04f379dc99cdf5c83e433defa267fbb3377d61d6b779c06a0e4ce29ae3ff5353b12ae4\
\9c9d07e7368f2ba5a446c203255ce912322991a2d6a9d5d5761c61ed1845"
ecPubKeyCombineTest :: Assertion
ecPubKeyCombineTest = do
(ret, com) <- liftIO $
allocaBytes 64 $ \p1 ->
allocaBytes 64 $ \p2 ->
allocaBytes 64 $ \p3 ->
allocaArray 3 $ \a ->
allocaBytes 64 $ \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 = useByteString pub $ \(d, dl) -> do
ret <- ecPubKeyParse x p d dl
unless (isSuccess ret) $ error "could not parse public key"
pub1 =
decodeBase16 $
assertBase16
"04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd447051221\
\3d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"
pub2 =
decodeBase16 $
assertBase16
"0487d82042d93447008dfe2af762068a1e53ff394a5bf8f68a045fa642b99ea5d153f5\
\77dd2dba6c7ae4cfd7b6622409d7edd2d76dd13a8092cd3af97b77bd2c77"
pub3 =
decodeBase16 $
assertBase16
"049b101edcbe1ee37ff6b2318526a425b629e823d7d8d9154417880595a28000ee3feb\
\d908754b8ce4e491aa6fe488b41fb5d4bb3788e33c9ff95a7a9229166d59"
expected =
decodeBase16 $
assertBase16
"043d9a7ec70011efc23c33a7e62d2ea73cca87797e3b659d93bea6aa871aebde56c3bc\
\6134ca82e324b0ab9c0e601a6d2933afe7fb5d9f3aae900f5c5dc6e362c8"