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

WycheproofEcdh.hs (5426B)


      1 {-# LANGUAGE BangPatterns #-}
      2 {-# LANGUAGE OverloadedStrings #-}
      3 {-# LANGUAGE RecordWildCards #-}
      4 {-# LANGUAGE ViewPatterns #-}
      5 
      6 module WycheproofEcdh (
      7     Wycheproof(..)
      8   , execute_group
      9   ) where
     10 
     11 import Crypto.Curve.Secp256k1
     12 import qualified Crypto.Hash.SHA256 as SHA256
     13 import Data.Aeson ((.:))
     14 import qualified Data.Aeson as A
     15 import qualified Data.Attoparsec.ByteString as AT
     16 import Data.Bits ((.<<.), (.>>.), (.|.))
     17 import qualified Data.ByteString as BS
     18 import qualified Data.ByteString.Base16 as B16
     19 import qualified Data.Text as T
     20 import qualified Data.Text.Encoding as TE
     21 import Test.Tasty (TestTree, testGroup)
     22 import qualified Test.Tasty.HUnit as H (assertBool, assertEqual, testCase)
     23 
     24 fi :: (Integral a, Num b) => a -> b
     25 fi = fromIntegral
     26 {-# INLINE fi #-}
     27 
     28 execute_group :: EcdhTestGroup -> TestTree
     29 execute_group EcdhTestGroup {..} =
     30     testGroup msg (fmap execute etg_tests)
     31   where
     32     msg = "wycheproof ecdh"
     33 
     34 execute :: EcdhTest -> TestTree
     35 execute EcdhTest {..} = H.testCase report $ do
     36     case der_to_pub t_public of
     37       Left _ ->
     38         -- 'acceptable' in wycheproof-speak means that a public key
     39         -- contains a parameter that, whilst invalid, doesn't actually
     40         -- affect the ECDH computation. we work only with valid
     41         -- secp256k1 points, so rule these out as invalid as well.
     42         --
     43         H.assertBool "invalid" (t_result `elem` ["invalid", "acceptable"])
     44       Right pub -> do
     45         let sec   = parse_bigint t_private
     46             sar   = parse_bigint t_shared
     47             h_sar = SHA256.hash (unroll32 sar)
     48             out   = ecdh pub sec
     49         H.assertEqual mempty h_sar out
     50   where
     51     report = "wycheproof ecdh " <> show t_tcId
     52 
     53 -- RFC 5280 ASN.1
     54 --   SubjectPublicKeyInfo  ::=  SEQUENCE  {
     55 --     algorithm         AlgorithmIdentifier,
     56 --     subjectPublicKey  BIT STRING
     57 --   }
     58 --   AlgorithmIdentifier  ::=  SEQUENCE  {
     59 --     algorithm   OBJECT IDENTIFIER,
     60 --     parameters  ANY DEFINED BY algorithm OPTIONAL
     61 --   }
     62 parse_der_pub :: AT.Parser Projective
     63 parse_der_pub = do
     64   _ <- AT.word8 0x30 -- SEQUENCE
     65   _ <- AT.anyWord8
     66   _ <- parse_der_algo
     67   parse_der_subjectpubkey
     68 
     69 parse_der_algo :: AT.Parser ()
     70 parse_der_algo = do
     71   _ <- AT.word8 0x30 -- SEQUENCE
     72   _ <- AT.anyWord8
     73   _ <- parse_der_ecpubkey
     74   _ <- parse_der_secp256k1
     75   pure ()
     76 
     77 -- RFC 5480 2.1.1
     78 --   id-ecPublicKey OBJECT IDENTIFIER ::= {
     79 --      iso(1) member-body(2) us(840) ansi-X9-62(10045) keyType(2) 1 }
     80 --
     81 --   DER encoded -> 06 07 2A 86 48 CE 3D 02 01
     82 parse_der_ecpubkey :: AT.Parser ()
     83 parse_der_ecpubkey = do
     84   _ <- AT.word8 0x06
     85   _ <- AT.word8 0x07
     86   _ <- AT.word8 0x2a
     87   _ <- AT.word8 0x86
     88   _ <- AT.word8 0x48
     89   _ <- AT.word8 0xce
     90   _ <- AT.word8 0x3d
     91   _ <- AT.word8 0x02
     92   _ <- AT.word8 0x01
     93   pure ()
     94 
     95 -- SEC1-v2 A.2
     96 --   certicom-arc OBJECT IDENTIFIER ::= {
     97 --     iso(1) identified-organization(3) certicom(132)
     98 --   }
     99 --
    100 --   ellipticCurve OBJECT IDENTIFIER ::= { certicom-arc curve(0) }
    101 --
    102 --   secp256k1 OBJECT IDENTIFIER ::= { ellipticCurve 10 }
    103 --
    104 --   (i.e., 1.3.132.0.10)
    105 --
    106 --   DER encoded -> 06 05 2B 81 04 00 0A
    107 parse_der_secp256k1 :: AT.Parser ()
    108 parse_der_secp256k1 = do
    109   _ <- AT.word8 0x06
    110   _ <- AT.word8 0x05
    111   _ <- AT.word8 0x2b
    112   _ <- AT.word8 0x81
    113   _ <- AT.word8 0x04
    114   _ <- AT.word8 0x00
    115   _ <- AT.word8 0x0a
    116   pure ()
    117 
    118 parse_der_subjectpubkey :: AT.Parser Projective
    119 parse_der_subjectpubkey = do
    120   _ <- AT.word8 0x03 -- BIT STRING
    121   len <- fmap fi AT.anyWord8
    122   _ <- AT.word8 0x00 -- extra bits (always 0x00 for DER)
    123   content <- AT.take (len - 1) -- len counts 'extra bits' field
    124   etc <- AT.takeByteString
    125   if   BS.length content /= len - 1 || etc /= mempty
    126   then fail "invalid content"
    127   else case parse_point content of
    128         Nothing -> fail "invalid content"
    129         Just pt -> pure pt
    130 
    131 der_to_pub :: T.Text -> Either String Projective
    132 der_to_pub (B16.decodeLenient . TE.encodeUtf8 -> bs) =
    133   AT.parseOnly parse_der_pub bs
    134 
    135 parse_bigint :: T.Text -> Integer
    136 parse_bigint (B16.decodeLenient . TE.encodeUtf8 -> bs) = roll bs where
    137   roll :: BS.ByteString -> Integer
    138   roll = BS.foldl' alg 0 where
    139     alg !a (fi -> !b) = (a .<<. 8) .|. b
    140 
    141 -- big-endian bytestring encoding
    142 unroll :: Integer -> BS.ByteString
    143 unroll i = case i of
    144     0 -> BS.singleton 0
    145     _ -> BS.reverse $ BS.unfoldr step i
    146   where
    147     step 0 = Nothing
    148     step m = Just (fi m, m .>>. 8)
    149 
    150 -- big-endian bytestring encoding for 256-bit ints, left-padding with
    151 -- zeros if necessary. the size of the integer is not checked.
    152 unroll32 :: Integer -> BS.ByteString
    153 unroll32 (unroll -> u)
    154     | l < 32 = BS.replicate (32 - l) 0 <> u
    155     | otherwise = u
    156   where
    157     l = BS.length u
    158 
    159 data Wycheproof = Wycheproof {
    160     wp_testGroups :: ![EcdhTestGroup]
    161   } deriving Show
    162 
    163 instance A.FromJSON Wycheproof where
    164   parseJSON = A.withObject "Wycheproof" $ \m -> Wycheproof
    165     <$> m .: "testGroups"
    166 
    167 data EcdhTestGroup = EcdhTestGroup {
    168     etg_tests     :: ![EcdhTest]
    169   } deriving Show
    170 
    171 instance A.FromJSON EcdhTestGroup where
    172   parseJSON = A.withObject "EcdhTestGroup" $ \m -> EcdhTestGroup
    173     <$> m .: "tests"
    174 
    175 data EcdhTest = EcdhTest {
    176     t_tcId    :: !Int
    177   , t_public  :: !T.Text
    178   , t_private :: !T.Text
    179   , t_shared  :: !T.Text
    180   , t_result  :: !T.Text
    181   } deriving Show
    182 
    183 instance A.FromJSON EcdhTest where
    184   parseJSON = A.withObject "EcdhTest" $ \m -> EcdhTest
    185     <$> m .: "tcId"
    186     <*> m .: "public"
    187     <*> m .: "private"
    188     <*> m .: "shared"
    189     <*> m .: "result"
    190