packages feed

secp256k1-0.1.0: test/Crypto/Secp256k1/Internal/Tests.hs

{-# LANGUAGE OverloadedStrings #-}
module Crypto.Secp256k1.Internal.Tests (tests) where

import           Control.Monad
import           Control.Monad.Trans
import           Crypto.Secp256k1.Internal
import           Data.ByteString                (packCStringLen,
                                                 useAsCStringLen)
import qualified Data.ByteString.Base16         as B16
import           Foreign
import           System.Entropy
import           Test.Framework                 (Test, testGroup)
import           Test.Framework.Providers.HUnit (testCase)
import           Test.HUnit                     (Assertion, assertBool,
                                                 assertEqual)

tests :: [Test]
tests =
    [ testGroup "Housekeeping"
        [ testCase "Create context"           create_context_test
        , testCase "Randomize context"        randomize_context_test
        , testCase "Clone context"            clone_context_test
        -- TODO:
        -- , testCase "Set illegal callback" set_illegal_callback_test
        -- TODO:
        -- , testCase "Set error callback" set_error_callback_test
        ]
    , testGroup "Serialization"
        [ testCase "Parse public key"         ec_pubkey_parse_test
        , testCase "Serialize public key"     ec_pubkey_serialize_test
        , testCase "Storable public key"      pubkey_storable_test
        , testCase "Parse DER signature"      ecdsa_signature_parse_der_test
        , testCase "Serialize DER signature"  ecdsa_signature_serialize_der_test
        ]
    , testGroup "Signatures"
        [ testCase "ECDSA verify"             ecdsa_verify_test
        -- TODO:
        -- , testCase "RFC6979 nonce function"   nonce_function_rfc6979_test
        , testCase "ECDSA sign"               ecdsa_sign_test
        ]
    , testGroup "Secret keys"
        [ testCase "Verify secret key"        ec_seckey_verify_test
        , testCase "Create public key"        ec_pubkey_create_test
        , testCase "Serialize BER secret key" ec_privkey_export_test
        , testCase "Import BER secret key"    ec_privkey_import_test
        , testCase "Tweak add secret key"     ec_privkey_tweak_add_test
        , testCase "Tweak mult. secret key"   ec_privkey_tweak_mul_test
        ]
    , testGroup "Public keys"
        [ testCase "Tweak add public key"     ec_pubkey_tweak_add_test
        , testCase "Tweak mult. public key"   ec_pubkey_tweak_mul_test
        , testCase "Combine public keys"      ec_pubkey_combine_test
        ]
    ]

withEntropy :: (Ptr Seed32 -> IO a) -> IO a
withEntropy f = getEntropy 32 >>= \e -> alloca $ \s -> poke s (Seed32 e) >> f s

create_context_test :: Assertion
create_context_test = do
    context_ptr <- liftIO $ context_create signVerify
    assertBool "context not null" $ context_ptr /= nullPtr

randomize_context_test :: Assertion
randomize_context_test = do
    ret <- liftIO $ context_create sign >>= \x ->
        withEntropy (context_randomize x)
    assertBool "context randomized" $ isSuccess ret

clone_context_test :: Assertion
clone_context_test = do
    (x1, x2) <- liftIO $ do
        x1 <- context_create signVerify
        ret <- withEntropy $ context_randomize x1
        unless (isSuccess ret) $ error "failed to randomize context"
        x2 <- context_clone(x1)
        return (x1, x2)
    assertBool "original context not null" $ x1 /= nullPtr
    assertBool "cloned context not null" $ x2 /= nullPtr
    assertBool "context ptrs different" $ x1 /= x2

ec_pubkey_parse_test :: Assertion
ec_pubkey_parse_test = do
    ret <- liftIO $ useAsCStringLen der $ \(i, il) -> do
        x <- context_create verify
        alloca $ \pubkey ->
            ec_pubkey_parse x pubkey (castPtr i) (fromIntegral il)
    assertBool "parsed public key" (isSuccess ret)
  where
    der = fst $ B16.decode
        "03dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd44705"

ec_pubkey_serialize_test :: Assertion
ec_pubkey_serialize_test = do
    (ret, dec) <- liftIO $ useAsCStringLen der $ \(i, il) ->
        alloca $ \k -> alloca $ \ol -> allocaBytes 72 $ \o -> do
        poke ol 72
        x <- context_create verify
        ret1 <-
            ec_pubkey_parse x k (castPtr i) (fromIntegral il)
        unless (isSuccess ret1) $ error "failed to parse pubkey"
        ret2 <- ec_pubkey_serialize
            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"

pubkey_storable_test :: Assertion
pubkey_storable_test = do
    (pk1, pk2, dec) <- liftIO $ useAsCStringLen der $ \(i, il) -> do
        x <- context_create verify
        pk1 <- alloca $ \pk -> do
            ret <-
                ec_pubkey_parse x pk (castPtr i) (fromIntegral il)
            unless (isSuccess ret) $ error "failed to parse pubkey"
            peek pk
        (pk2, dec) <- alloca $ \pk -> alloca $ \ol -> allocaBytes 72 $ \o -> do
            poke ol 72
            poke pk pk1
            ret <-
                ec_pubkey_serialize x o ol pk compressed
            unless (isSuccess ret) $ error "failed to serialize pubkey"
            len <- fromIntegral <$> peek ol
            dec <- packCStringLen (castPtr o, len)
            pk2 <- peek pk
            return (pk2, dec)
        return (pk1, pk2, dec)
    assertEqual "poke/peek public key" pk1 pk2
    assertEqual "public key matches" der dec
  where
    der = fst $ B16.decode
        "03dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd44705"

ecdsa_signature_parse_der_test :: Assertion
ecdsa_signature_parse_der_test = do
    ret <- liftIO $ useAsCStringLen der $ \(d, dl) -> alloca $ \s -> do
        x <- context_create verify
        ecdsa_signature_parse_der x s (castPtr d) (fromIntegral dl)
    assertBool "parsed signature successfully" $ isSuccess ret
  where
    der = fst $ B16.decode $
        "3045022100f502bfa07af43e7ef265618b0d929a7619ee01d6150e37eb6eaaf2c8bd37\
        \fb2202206f0415ab0e9a977afd78b2c26ef39b3952096d319fd4b101c768ad6c132e30\
        \45"

ecdsa_signature_serialize_der_test :: Assertion
ecdsa_signature_serialize_der_test = do
    (ret, enc) <- liftIO $ do
        x <- context_create verify
        sig <- useAsCStringLen der $ \(d, dl) -> alloca $ \s -> do
            ret <-
                ecdsa_signature_parse_der x s (castPtr d) (fromIntegral dl)
            unless (isSuccess ret) $ error "could not parse DER"
            peek s
        alloca $ \s -> alloca $ \ol -> allocaBytes 72 $ \o -> do
            poke ol 72
            poke s sig
            ret <-
                ecdsa_signature_serialize_der 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"

ecdsa_verify_test :: Assertion
ecdsa_verify_test = do
    ret <- liftIO $ do
        x <- context_create verify
        sig <- useAsCStringLen der $ \(d, dl) -> alloca $ \s -> do
            ret <-
                ecdsa_signature_parse_der x s (castPtr d) (fromIntegral dl)
            unless (isSuccess ret) $ error "could not parse DER"
            peek s
        pk <- useAsCStringLen pub $ \(p, pl) -> alloca $ \k -> do
            ret <-
                ec_pubkey_parse x k (castPtr p) (fromIntegral pl)
            unless (isSuccess ret) $ error "could not parse public key"
            peek k
        alloca $ \m -> alloca $ \k -> alloca $ \s -> do
            poke m msg
            poke k pk
            poke s sig
            ecdsa_verify x s m k
    assertBool "signature valid" $ isSuccess ret
  where
    der = fst $ B16.decode $
        "3045022100f502bfa07af43e7ef265618b0d929a7619ee01d6150e37eb6eaaf2c8bd37\
        \fb2202206f0415ab0e9a977afd78b2c26ef39b3952096d319fd4b101c768ad6c132e30\
        \45"
    pub = fst $ B16.decode $
        "04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd447051221\
        \3d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"
    msg = Msg32 $ fst $ B16.decode $
        "f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"

-- TODO:
-- nonce_function_rfc6979_test :: Assertion
-- nonce_function_rfc6979_test = do
--     (ret, nonce) <- alloca $ \n -> alloca $ \m -> alloca $ \k -> do
--             poke m msg
--             poke k key
--             let ret = mkNonceFunction
--                     nonce_function_rfc6979 n m k nullPtr nullPtr 0
--             nonce <- peek n
--             return (ret, nonce)
--     assertBool "nonce calculated" $ isSuccess ret
--     assertEqual "nonce correct" expected nonce
--   where
--     msg = Msg32 $ fst $ B16.decode $
--         "f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
--     key = SecKey32 $ fst $ B16.decode $
--         "f65255094d7773ed8dd417badc9fc045c1f80fdc5b2d25172b031ce6933e039a"
--     expected = Nonce $ fst $ B16.decode $
--         "8f30c2bf5a3e1199b19a6703fa578376e5225d6ecfc7ecf817aa8b8b16203d64"

ecdsa_sign_test :: Assertion
ecdsa_sign_test = do
    (ret, sig) <- liftIO $ do
        x <- context_create sign
        retr <- withEntropy $ context_randomize x
        unless (isSuccess retr) $ error "failed to randomize context"
        alloca $ \s -> alloca $ \m -> alloca $ \k -> alloca $ \ol ->
            allocaBytes 72 $ \o -> do
                poke ol 72
                poke m msg
                poke k key
                ret1 <-
                    -- TODO:
                    -- ecdsa_sign x s m k nonce_function_default nullPtr
                    ecdsa_sign x s m k nullFunPtr nullPtr
                ret2 <- ecdsa_signature_serialize_der x o ol s
                unless (isSuccess ret2) $error "could not serialize signature"
                len <- peek ol
                sig <- packCStringLen (castPtr o, fromIntegral len)
                return (ret1, sig)
    assertBool "successful signing" $ isSuccess ret
    assertEqual "signature matches" sig der
  where
    msg = Msg32 $ fst $ B16.decode $
        "f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
    key = SecKey32 $ fst $ B16.decode $
        "f65255094d7773ed8dd417badc9fc045c1f80fdc5b2d25172b031ce6933e039a"
    der = fst $ B16.decode $
        "3045022100f502bfa07af43e7ef265618b0d929a7619ee01d6150e37eb6eaaf2c8bd37\
        \fb2202206f0415ab0e9a977afd78b2c26ef39b3952096d319fd4b101c768ad6c132e30\
        \45"

ec_seckey_verify_test :: Assertion
ec_seckey_verify_test = do
    ret <- liftIO $ alloca $ \k -> do
        poke k key
        x <- context_create sign
        retr <- withEntropy $ context_randomize x
        unless (isSuccess retr) $ error "failed to randomize context"
        ec_seckey_verify x k
    assertBool "valid secret key" $ isSuccess ret
  where
    key = SecKey32 $ fst $ B16.decode $
        "f65255094d7773ed8dd417badc9fc045c1f80fdc5b2d25172b031ce6933e039a"

ec_pubkey_create_test :: Assertion
ec_pubkey_create_test = do
    (ret, pk) <- liftIO $ alloca $ \p -> alloca $ \k -> do
        poke k key
        x <- context_create sign
        retr <- withEntropy $ context_randomize x
        unless (isSuccess retr) $ error "failed to randomize context"
        ret <- ec_pubkey_create x p k
        allocaBytes 65 $ \o -> alloca $ \ol -> do
            poke ol 65
            rets <- ec_pubkey_serialize
                x o ol p uncompressed
            unless (isSuccess rets) $ error "failed to serialive public key"
            len <- fromIntegral <$> peek ol
            pk <- packCStringLen (castPtr o, len)
            return (ret, pk)
    assertBool "successful pubkey creation" $ isSuccess ret
    assertEqual "public key matches" pub pk
  where
    key = SecKey32 $ fst $ B16.decode $
        "f65255094d7773ed8dd417badc9fc045c1f80fdc5b2d25172b031ce6933e039a"
    pub = fst $ B16.decode $
        "04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd447051221\
        \3d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"

ec_privkey_export_test :: Assertion
ec_privkey_export_test = do
    ret <- liftIO $ alloca $ \k ->
        allocaBytes 279 $ \o -> alloca $ \ol -> do
            poke ol 279
            poke k key
            x <- context_create sign
            retr <- withEntropy $ context_randomize x
            unless (isSuccess retr) $ error "failed to randomize context"
            ec_privkey_export x o ol k uncompressed
    assertBool "successful secret key BER serialization" $ isSuccess ret
  where
    key = SecKey32 $ fst $ B16.decode $
        "f65255094d7773ed8dd417badc9fc045c1f80fdc5b2d25172b031ce6933e039a"

ec_privkey_import_test :: Assertion
ec_privkey_import_test = do
    (ret, dec) <- liftIO $ do
        x <- context_create sign
        retr <- withEntropy $ context_randomize x
        unless (isSuccess retr) $ error "failed to randomize context"
        ber <- allocaBytes 279 $ \o -> alloca $ \ol -> alloca $ \k -> do
            poke ol 279
            poke k key
            rets <- ec_privkey_export x o ol k uncompressed
            unless (isSuccess rets) $ error "failed to serialize key"
            len <- fromIntegral <$> peek ol
            packCStringLen (castPtr o, len)
        useAsCStringLen ber $ \(b, bl) -> alloca $ \k -> do
            ret <- ec_privkey_import x k (castPtr b) (fromIntegral bl)
            dec <- peek k
            return (ret, dec)
    assertBool "successful secret key BER deserialization" $ isSuccess ret
    assertEqual "keys match" key dec
  where
    key = SecKey32 $ fst $ B16.decode $
        "f65255094d7773ed8dd417badc9fc045c1f80fdc5b2d25172b031ce6933e039a"

ec_privkey_tweak_add_test :: Assertion
ec_privkey_tweak_add_test = do
    (ret, tweaked) <- liftIO $ do
        x <- context_create sign
        retr <- withEntropy $ context_randomize x
        unless (isSuccess retr) $ error "failed to randomize context"
        alloca $ \w -> alloca $ \k -> do
            poke w tweak
            poke k key
            ret <- ec_privkey_tweak_add x k w
            tweaked <- peek k
            return (ret, tweaked)
    assertBool "successful secret key tweak" $ isSuccess ret
    assertEqual "tweaked keys match" expected tweaked
  where
    key = SecKey32 $ fst $ B16.decode $
        "f65255094d7773ed8dd417badc9fc045c1f80fdc5b2d25172b031ce6933e039a"
    tweak = Tweak32 $ fst $ B16.decode $
        "f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
    expected = SecKey32 $ fst $ B16.decode $
        "ec1e3ce1cefa18a671d51125e2b249688d934b0e28f5d1665384d9b02f929059"

ec_privkey_tweak_mul_test :: Assertion
ec_privkey_tweak_mul_test = do
    (ret, tweaked) <- liftIO $ do
        x <- context_create sign
        retr <- withEntropy $ context_randomize x
        unless (isSuccess retr) $ error "failed to randomize context"
        alloca $ \w -> alloca $ \k -> do
            poke w tweak
            poke k key
            ret <- ec_privkey_tweak_mul x k w
            tweaked <- peek k
            return (ret, tweaked)
    assertBool "successful secret key tweak" $ isSuccess ret
    assertEqual "tweaked keys match" expected tweaked
  where
    key = SecKey32 $ fst $ B16.decode $
        "f65255094d7773ed8dd417badc9fc045c1f80fdc5b2d25172b031ce6933e039a"
    tweak = Tweak32 $ fst $ B16.decode $
        "f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
    expected = SecKey32 $ fst $ B16.decode $
        "a96f5962493acb179f60a86a9785fc7a30e0c39b64c09d24fe064d9aef15e4c0"

ec_pubkey_tweak_add_test :: Assertion
ec_pubkey_tweak_add_test = do
    (ret, tweaked) <- liftIO $ do
        x <- context_create verify
        pk <- alloca $ \p -> useAsCStringLen pub $ \(d, dl) -> do
            ret <- ec_pubkey_parse x p (castPtr d) (fromIntegral dl)
            unless (isSuccess ret) $ error "could not parse public key"
            peek p
        alloca $ \w -> alloca $ \p -> allocaBytes 72 $ \d -> alloca $ \dl -> do
            poke w tweak
            poke p pk
            poke dl 72
            ret <- ec_pubkey_tweak_add x p w
            rets <- ec_pubkey_serialize x d dl p uncompressed
            unless (isSuccess rets) $ error "could not serialize public key"
            len <- peek dl
            tweaked <- packCStringLen (castPtr d, fromIntegral len)
            return (ret, tweaked)
    assertBool "successful secret key tweak" $ isSuccess ret
    assertEqual "tweaked keys match" expected tweaked
  where
    pub = fst $ B16.decode $
        "04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd447051221\
        \3d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"
    tweak = Tweak32 $ fst $ B16.decode $
        "f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
    expected = fst $ B16.decode $
        "04441c3982b97576646e0df0c96736063df6b42f2ee566d13b9f6424302d1379e518fd\
        \c87a14c5435bff7a5db4552042cb4120c6b86a4bbd3d0643f3c14ad01368"

ec_pubkey_tweak_mul_test :: Assertion
ec_pubkey_tweak_mul_test = do
    (ret, tweaked) <- liftIO $ do
        x <- context_create verify
        pk <- alloca $ \p -> useAsCStringLen pub $ \(d, dl) -> do
            ret <- ec_pubkey_parse x p (castPtr d) (fromIntegral dl)
            unless (isSuccess ret) $ error "could not parse public key"
            peek p
        alloca $ \w -> alloca $ \p -> allocaBytes 72 $ \d -> alloca $ \dl -> do
            poke w tweak
            poke p pk
            poke dl 72
            ret <- ec_pubkey_tweak_mul x p w
            rets <- ec_pubkey_serialize x d dl p uncompressed
            unless (isSuccess rets) $ error "could not serialize public key"
            len <- peek dl
            tweaked <- packCStringLen (castPtr d, fromIntegral len)
            return (ret, tweaked)
    assertBool "successful secret key tweak" $ isSuccess ret
    assertEqual "tweaked keys match" expected tweaked
  where
    pub = fst $ B16.decode $
        "04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd447051221\
        \3d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"
    tweak = Tweak32 $ fst $ B16.decode $
        "f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
    expected = fst $ B16.decode $
        "04f379dc99cdf5c83e433defa267fbb3377d61d6b779c06a0e4ce29ae3ff5353b12ae4\
        \9c9d07e7368f2ba5a446c203255ce912322991a2d6a9d5d5761c61ed1845"

ec_pubkey_combine_test :: Assertion
ec_pubkey_combine_test = do
    (ret, com) <- liftIO $ alloca $ \p1 -> alloca $ \p2 -> alloca $ \p3 ->
        allocaBytes 72 $ \d -> alloca $ \dl -> allocaArray 3 $ \a ->
            alloca $ \p -> do
                x <- context_create verify
                parse x pub1 p1
                parse x pub2 p2
                parse x pub3 p3
                pokeArray a [p1, p2, p3]
                poke dl 72
                ret <- ec_pubkey_combine x p a 3
                rets <- ec_pubkey_serialize
                    x d dl p uncompressed
                unless (isSuccess rets) $ error "could not serialize public key"
                len <- peek dl
                com <- packCStringLen (castPtr d, fromIntegral len)
                return (ret, com)
    assertBool "successful key combination" $ isSuccess ret
    assertEqual "combined keys match" expected com
  where
    parse x pub p = useAsCStringLen pub $ \(d, dl) -> do
        ret <- ec_pubkey_parse x p (castPtr d) (fromIntegral 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"