Noble.hs (4246B)
1 {-# LANGUAGE OverloadedStrings #-} 2 {-# LANGUAGE RecordWildCards #-} 3 {-# LANGUAGE ViewPatterns #-} 4 5 module Noble ( 6 Ecdsa(..) 7 , execute_ecdsa 8 ) where 9 10 import Control.Exception 11 import Crypto.Curve.Secp256k1 12 import Data.Aeson ((.:)) 13 import qualified Data.Aeson as A 14 import qualified Data.ByteString as BS 15 import qualified Data.ByteString.Base16 as B16 16 import qualified Data.Text as T 17 import qualified Data.Text.Encoding as TE 18 import qualified GHC.Num.Integer as I 19 import Test.Tasty (TestTree, testGroup) 20 import Test.Tasty.HUnit (assertEqual, assertBool, assertFailure, testCase) 21 22 data Ecdsa = Ecdsa { 23 ec_valid :: ![(Int, ValidTest)] 24 , ec_invalid :: !InvalidTest 25 } deriving Show 26 27 execute_ecdsa :: Ecdsa -> TestTree 28 execute_ecdsa Ecdsa {..} = testGroup "noble_ecdsa" [ 29 testGroup "valid" (fmap execute_valid ec_valid) 30 , testGroup "invalid (sign)" (fmap execute_invalid_sign iv_sign) 31 , testGroup "invalid (verify)" (fmap execute_invalid_verify iv_verify) 32 ] 33 where 34 InvalidTest {..} = ec_invalid 35 36 execute_valid :: (Int, ValidTest) -> TestTree 37 execute_valid (label, ValidTest {..}) = 38 testCase ("noble-secp256k1, valid (" <> show label <> ")") $ do 39 let msg = vt_m 40 x = vt_d 41 pec = parse_compact vt_signature 42 sig = _sign_ecdsa_no_hash x msg 43 assertEqual mempty pec sig 44 45 execute_invalid_sign :: (Int, InvalidSignTest) -> TestTree 46 execute_invalid_sign (label, InvalidSignTest {..}) = 47 testCase ("noble-secp256k1, invalid sign (" <> show label <> ")") $ do 48 let x = ivs_d 49 m = ivs_m 50 err <- catch (pure (_sign_ecdsa_no_hash x m) >> pure False) handler 51 if err 52 then assertFailure "expected error not caught" 53 else pure () 54 where 55 handler :: ErrorCall -> IO Bool 56 handler _ = pure True 57 58 execute_invalid_verify :: (Int, InvalidVerifyTest) -> TestTree 59 execute_invalid_verify (label, InvalidVerifyTest {..}) = 60 testCase ("noble-secp256k1, invalid verify (" <> show label <> ")") $ 61 case parse_point ivv_Q of 62 Nothing -> assertBool "no parse" True 63 Just pub -> do 64 let sig = parse_compact ivv_signature 65 ver = verify_ecdsa ivv_m pub sig 66 assertBool mempty (not ver) 67 68 fi :: (Integral a, Num b) => a -> b 69 fi = fromIntegral 70 {-# INLINE fi #-} 71 72 -- parser helper 73 toBS :: T.Text -> BS.ByteString 74 toBS = B16.decodeLenient . TE.encodeUtf8 75 76 -- parser helper 77 toSecKey :: T.Text -> Integer 78 toSecKey = roll . toBS 79 80 -- big-endian bytestring decoding 81 roll :: BS.ByteString -> Integer 82 roll = BS.foldl' unstep 0 where 83 unstep a (fi -> b) = (a `I.integerShiftL` 8) `I.integerOr` b 84 85 instance A.FromJSON Ecdsa where 86 parseJSON = A.withObject "Ecdsa" $ \m -> Ecdsa 87 <$> fmap (zip [0..]) (m .: "valid") 88 <*> m .: "invalid" 89 90 data ValidTest = ValidTest { 91 vt_d :: !Integer 92 , vt_m :: !BS.ByteString 93 , vt_signature :: !BS.ByteString 94 } deriving Show 95 96 instance A.FromJSON ValidTest where 97 parseJSON = A.withObject "ValidTest" $ \m -> ValidTest 98 <$> fmap toSecKey (m .: "d") 99 <*> fmap toBS (m .: "m") 100 <*> fmap toBS (m .: "signature") 101 102 parse_compact :: BS.ByteString -> ECDSA 103 parse_compact bs = 104 let (roll -> r, roll -> s) = BS.splitAt 32 bs 105 in ECDSA r s 106 107 data InvalidTest = InvalidTest { 108 iv_sign :: ![(Int, InvalidSignTest)] 109 , iv_verify :: ![(Int, InvalidVerifyTest)] 110 } deriving Show 111 112 instance A.FromJSON InvalidTest where 113 parseJSON = A.withObject "InvalidTest" $ \m -> InvalidTest 114 <$> fmap (zip [0..]) (m .: "sign") 115 <*> fmap (zip [0..]) (m .: "verify") 116 117 data InvalidSignTest = InvalidSignTest { 118 ivs_d :: !Integer 119 , ivs_m :: !BS.ByteString 120 } deriving Show 121 122 instance A.FromJSON InvalidSignTest where 123 parseJSON = A.withObject "InvalidSignTest" $ \m -> InvalidSignTest 124 <$> fmap toSecKey (m .: "d") 125 <*> fmap toBS (m .: "m") 126 127 data InvalidVerifyTest = InvalidVerifyTest { 128 ivv_Q :: !BS.ByteString 129 , ivv_m :: !BS.ByteString 130 , ivv_signature :: !BS.ByteString 131 } deriving Show 132 133 instance A.FromJSON InvalidVerifyTest where 134 parseJSON = A.withObject "InvalidVerifyTest" $ \m -> InvalidVerifyTest 135 <$> fmap TE.encodeUtf8 (m .: "Q") 136 <*> fmap toBS (m .: "m") 137 <*> fmap toBS (m .: "signature") 138