secp256k1-haskell-0.4.0: test/Crypto/Secp256k1/InternalSpec.hs
{-# 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, copy)
import qualified Data.ByteString.Base16 as B16
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 "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
withEntropy :: (Ptr Seed32 -> IO a) -> IO a
withEntropy f =
getEntropy 32 >>= \e ->
useByteString e $ \(s, _) -> 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
allocaBytes 64 $ \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 $
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 = fst $ B16.decode
"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 = fst $ B16.decode
"3045022100f502bfa07af43e7ef265618b0d929a7619ee01d6150e37eb6eaaf2c8bd37\
\fb2202206f0415ab0e9a977afd78b2c26ef39b3952096d319fd4b101c768ad6c132e30\
\45"
parseDer :: Ctx -> 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 $ do
x <- contextCreate verify
sig <- parseDer x der
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)
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 <- useByteString pub $ \(p, pl) ->
allocaBytes 64 $ \k -> do
ret <- ecPubKeyParse x 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 x s m k
assertBool "signature valid" $ isSuccess ret
where
der = fst $ B16.decode
"3045022100f502bfa07af43e7ef265618b0d929a7619ee01d6150e37eb6eaaf2c8bd37\
\fb2202206f0415ab0e9a977afd78b2c26ef39b3952096d319fd4b101c768ad6c132e30\
\45"
pub = fst $ B16.decode
"04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd447051221\
\3d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"
msg = fst $ B16.decode
"f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
signCtx :: IO Ctx
signCtx = contextCreate sign >>= \c ->
withEntropy (contextRandomize c) >>= \r ->
unless (isSuccess r) (error "failed to randomize context") >> return c
createPubKey :: Ctx -> Ptr SecKey32 -> Ptr PubKey64 -> IO ()
createPubKey x k p = do
ret <- ecPubKeyCreate x p k
unless (isSuccess ret) $ error "failed to create public key"
ecdsaSignTest :: Assertion
ecdsaSignTest = do
der <- liftIO $ do
x <- signCtx
allocaBytes 64 $ \s ->
useByteString msg $ \(m, _) ->
useByteString key $ \(k, _) ->
alloca $ \ol ->
allocaBytes 72 $ \o -> do
poke ol 72
ret1 <- ecdsaSign x s m k nullFunPtr nullPtr
unless (isSuccess ret1) $ error "could not sign message"
ret2 <- ecdsaSignatureSerializeDer x o ol s
unless (isSuccess ret2) $ error "could not serialize signature"
len <- peek ol
packCStringLen (castPtr o, fromIntegral len)
ret <- liftIO $ do
pub <- allocaBytes 64 $ \p ->
useByteString key $ \(s, _) -> do
x <- signCtx
createPubKey x s p
packByteString (p, 64)
useByteString msg $ \(m, _) ->
useByteString pub $ \(p, _) -> do
x <- contextCreate verify
s' <- parseDer x der
useByteString s' $ \(s, _) -> ecdsaVerify x s m p
assertBool "signature matches" (isSuccess ret)
where
msg = fst $ B16.decode
"f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
key = fst $ B16.decode
"f65255094d7773ed8dd417badc9fc045c1f80fdc5b2d25172b031ce6933e039a"
ecSecKeyVerifyTest :: Assertion
ecSecKeyVerifyTest = do
ret <- liftIO $ useByteString key $ \(k, _) -> do
x <- signCtx
ecSecKeyVerify x k
assertBool "valid secret key" $ isSuccess ret
where
key = fst $ B16.decode
"f65255094d7773ed8dd417badc9fc045c1f80fdc5b2d25172b031ce6933e039a"
ecPubkeyCreateTest :: Assertion
ecPubkeyCreateTest = do
pk <- liftIO $
useByteString key $ \(s, _) ->
allocaBytes 64 $ \k -> do
x <- signCtx
createPubKey x s k
allocaBytes 65 $ \o ->
alloca $ \ol -> do
poke ol 65
rets <- ecPubKeySerialize x 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 = fst $ B16.decode
"f65255094d7773ed8dd417badc9fc045c1f80fdc5b2d25172b031ce6933e039a"
pub = fst $ B16.decode
"04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd447051221\
\3d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"
ecSecKeyTweakAddTest :: Assertion
ecSecKeyTweakAddTest = do
(ret, tweaked) <- liftIO $
signCtx >>= \x ->
useByteString tweak $ \(w, _) ->
useByteString key $ \(k, _) -> do
ret <- ecSecKeyTweakAdd x k w
tweaked <- packByteString (k, 32)
return (ret, tweaked)
assertBool "successful secret key tweak" $ isSuccess ret
assertEqual "tweaked keys match" expected tweaked
where
key = fst $ B16.decode
"f65255094d7773ed8dd417badc9fc045c1f80fdc5b2d25172b031ce6933e039a"
tweak = fst $ B16.decode
"f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
expected = 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"
useByteString tweak $ \(w, _) -> useByteString key $ \(k, _) -> do
ret <- ecSecKeyTweakMul x k w
tweaked <- packByteString (k, 32)
return (ret, tweaked)
assertBool "successful secret key tweak" $ isSuccess ret
assertEqual "tweaked keys match" expected tweaked
where
key = fst $ B16.decode
"f65255094d7773ed8dd417badc9fc045c1f80fdc5b2d25172b031ce6933e039a"
tweak = fst $ B16.decode
"f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
expected = fst $ B16.decode
"a96f5962493acb179f60a86a9785fc7a30e0c39b64c09d24fe064d9aef15e4c0"
serializeKey :: 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 :: Ctx -> 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 = fst $ B16.decode
"04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd447051221\
\3d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"
tweak = fst $ B16.decode
"f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
expected = fst $ B16.decode
"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 = fst $ B16.decode
"04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd447051221\
\3d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"
tweak = fst $ B16.decode
"f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
expected = fst $ B16.decode
"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 = fst $ B16.decode
"04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd447051221\
\3d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"
pub2 = fst $ B16.decode
"0487d82042d93447008dfe2af762068a1e53ff394a5bf8f68a045fa642b99ea5d153f5\
\77dd2dba6c7ae4cfd7b6622409d7edd2d76dd13a8092cd3af97b77bd2c77"
pub3 = fst $ B16.decode
"049b101edcbe1ee37ff6b2318526a425b629e823d7d8d9154417880595a28000ee3feb\
\d908754b8ce4e491aa6fe488b41fb5d4bb3788e33c9ff95a7a9229166d59"
expected = fst $ B16.decode
"043d9a7ec70011efc23c33a7e62d2ea73cca87797e3b659d93bea6aa871aebde56c3bc\
\6134ca82e324b0ab9c0e601a6d2933afe7fb5d9f3aae900f5c5dc6e362c8"