secp256k1

Pure Haskell Schnorr, ECDSA on the elliptic curve secp256k1 (docs.ppad.tech/secp256k1).
git clone git://git.ppad.tech/secp256k1.git
Log | Files | Refs | README | LICENSE

Wycheproof.hs (5499B)


      1 {-# LANGUAGE BangPatterns #-}
      2 {-# LANGUAGE OverloadedStrings #-}
      3 {-# LANGUAGE RecordWildCards #-}
      4 {-# LANGUAGE ViewPatterns #-}
      5 
      6 module Wycheproof (
      7     Wycheproof(..)
      8   , execute_group
      9   ) where
     10 
     11 import Crypto.Curve.Secp256k1
     12 import Data.Aeson ((.:))
     13 import qualified Data.Aeson as A
     14 import qualified Data.Attoparsec.ByteString as AT
     15 import qualified Data.Bits as B
     16 import qualified Data.ByteString as BS
     17 import qualified Data.ByteString.Base16 as B16
     18 import qualified Data.Text as T
     19 import qualified Data.Text.Encoding as TE
     20 import Test.Tasty (TestTree, testGroup)
     21 import Test.Tasty.HUnit (assertBool, testCase)
     22 
     23 decodeLenient :: BS.ByteString -> BS.ByteString
     24 decodeLenient bs = case B16.decode bs of
     25   Nothing -> error "bang"
     26   Just b -> b
     27 
     28 fi :: (Integral a, Num b) => a -> b
     29 fi = fromIntegral
     30 {-# INLINE fi #-}
     31 
     32 execute_group :: Context -> SigType -> EcdsaTestGroup -> TestTree
     33 execute_group tex ty EcdsaTestGroup {..} =
     34     testGroup msg (fmap (execute tex ty pk_uncompressed) etg_tests)
     35   where
     36     msg = "wycheproof (" <> T.unpack etg_type <> ", " <> T.unpack etg_sha <> ")"
     37     PublicKey {..} = etg_publicKey
     38 
     39 execute :: Context -> SigType -> Projective -> EcdsaVerifyTest -> TestTree
     40 execute tex ty pub EcdsaVerifyTest {..} = testCase report $ do
     41     let msg = decodeLenient (TE.encodeUtf8 t_msg)
     42         sig = toEcdsa t_sig
     43     case sig of
     44       Left _  -> assertBool mempty (t_result == "invalid")
     45       Right s -> do
     46         let ver = case ty of
     47               LowS -> verify_ecdsa msg pub s && verify_ecdsa' tex msg pub s
     48               Unrestricted -> verify_ecdsa_unrestricted msg pub s
     49                            && verify_ecdsa_unrestricted' tex msg pub s
     50         if   t_result == "invalid"
     51         then assertBool mempty (not ver)
     52         else assertBool mempty ver
     53   where
     54     report = "wycheproof (" <> show ty <> ") " <> show t_tcId
     55 
     56 parse_der_sig :: AT.Parser ECDSA
     57 parse_der_sig = do
     58     _ <- AT.word8 0x30
     59     len <- fmap fi AT.anyWord8
     60     content <- AT.take len
     61     etc <- AT.takeByteString
     62     if   BS.length content /= len || etc /= mempty
     63     then fail "invalid content"
     64     else case AT.parseOnly (meat len) content of
     65       Left _  -> fail "invalid content"
     66       Right v -> pure v
     67   where
     68     meat len = do
     69       (lr, bs_r) <- parseAsnInt
     70       (ls, bs_s) <- parseAsnInt
     71       let rs = do
     72             r <- roll32 bs_r
     73             s <- roll32 bs_s
     74             pure (r, s)
     75       case rs of
     76         Nothing -> fail "signature components too large"
     77         Just (r, s) -> do
     78           let checks = lr + ls == len
     79           rest <- AT.takeByteString
     80           if   rest == mempty && checks
     81           then pure (ECDSA r s)
     82           else fail "input remaining or length mismatch"
     83 
     84 parseAsnInt :: AT.Parser (Int, BS.ByteString)
     85 parseAsnInt = do
     86   _       <- AT.word8 0x02
     87   len     <- fmap fi AT.anyWord8
     88   content <- AT.take len
     89   if   BS.length content /= len
     90   then fail "invalid length"
     91   else if   len == 1
     92        then pure (len + 2, content) -- + tag byt + len byt
     93        else case BS.uncons content of
     94          Nothing -> fail "invalid content"
     95          Just (h0, t0)
     96            | B.testBit h0 7 -> fail "negative value"
     97            | otherwise -> case BS.uncons t0 of
     98                Nothing -> fail "invalid content"
     99                Just (h1, _)
    100                  | h0 == 0x00 && not (B.testBit h1 7) -> fail "invalid padding"
    101                  | otherwise -> case BS.unsnoc content of
    102                      Nothing -> fail "invalid content"
    103                      Just (_, tn)
    104                        | tn == 0x00 -> fail "invalid padding"
    105                        | otherwise  -> pure (len + 2, content)
    106 
    107 data Wycheproof = Wycheproof {
    108     wp_algorithm        :: !T.Text
    109   , wp_generatorVersion :: !T.Text
    110   , wp_numberOfTests    :: !Int
    111   , wp_testGroups       :: ![EcdsaTestGroup]
    112   } deriving Show
    113 
    114 instance A.FromJSON Wycheproof where
    115   parseJSON = A.withObject "Wycheproof" $ \m -> Wycheproof
    116     <$> m .: "algorithm"
    117     <*> m .: "generatorVersion"
    118     <*> m .: "numberOfTests"
    119     <*> m .: "testGroups"
    120 
    121 data EcdsaTestGroup = EcdsaTestGroup {
    122     etg_type      :: !T.Text
    123   , etg_publicKey :: !PublicKey
    124   , etg_sha       :: !T.Text
    125   , etg_tests     :: ![EcdsaVerifyTest]
    126   } deriving Show
    127 
    128 instance A.FromJSON EcdsaTestGroup where
    129   parseJSON = A.withObject "EcdsaTestGroup" $ \m -> EcdsaTestGroup
    130     <$> m .: "type"
    131     <*> m .: "publicKey"
    132     <*> m .: "sha"
    133     <*> m .: "tests"
    134 
    135 data PublicKey = PublicKey {
    136     pk_type         :: !T.Text
    137   , pk_curve        :: !T.Text
    138   , pk_keySize      :: !Int
    139   , pk_uncompressed :: !Projective
    140   } deriving Show
    141 
    142 toProjective :: T.Text -> Projective
    143 toProjective (decodeLenient . TE.encodeUtf8 -> bs) = case parse_point bs of
    144   Nothing -> error "wycheproof: couldn't parse pubkey"
    145   Just p -> p
    146 
    147 instance A.FromJSON PublicKey where
    148   parseJSON = A.withObject "PublicKey" $ \m -> PublicKey
    149     <$> m .: "type"
    150     <*> m .: "curve"
    151     <*> m .: "keySize"
    152     <*> fmap toProjective (m .: "uncompressed")
    153 
    154 toEcdsa :: T.Text -> Either String ECDSA
    155 toEcdsa (decodeLenient . TE.encodeUtf8 -> bs) =
    156   AT.parseOnly parse_der_sig bs
    157 
    158 data EcdsaVerifyTest = EcdsaVerifyTest {
    159     t_tcId    :: !Int
    160   , t_comment :: !T.Text
    161   , t_msg     :: !T.Text
    162   , t_sig     :: !T.Text
    163   , t_result  :: !T.Text
    164   } deriving Show
    165 
    166 instance A.FromJSON EcdsaVerifyTest where
    167   parseJSON = A.withObject "EcdsaVerifyTest" $ \m -> EcdsaVerifyTest
    168     <$> m .: "tcId"
    169     <*> m .: "comment"
    170     <*> m .: "msg"
    171     <*> m .: "sig"
    172     <*> m .: "result"
    173