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 (5420B)


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