packages feed

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"