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

commit 140b0a32804a0b23068c3656f90dcc89fa8317fc
parent c7feb7f5551d91b7f0962d1aaa9a8065d3be478f
Author: Jared Tobin <jared@jtobin.io>
Date:   Fri, 14 Mar 2025 14:04:08 +0400

test: test actual ecdh function

Diffstat:
Mppad-secp256k1.cabal | 1+
Mtest/Main.hs | 8++++----
Mtest/WycheproofEcdh.hs | 55+++++++++++++++++++++++++++++++++++++------------------
3 files changed, 42 insertions(+), 22 deletions(-)

diff --git a/ppad-secp256k1.cabal b/ppad-secp256k1.cabal @@ -53,6 +53,7 @@ test-suite secp256k1-tests , base16-bytestring , bytestring , ppad-secp256k1 + , ppad-sha256 , tasty , tasty-hunit , text diff --git a/test/Main.hs b/test/Main.hs @@ -44,11 +44,11 @@ main = do Nothing -> error "couldn't parse wycheproof vectors" Just (w0, w1, w2, no, ip) -> defaultMain $ testGroup "ppad-secp256k1" [ units - -- , wycheproof_ecdsa_verify_tests tex "(ecdsa, sha256)" Unrestricted w0 - -- , wycheproof_ecdsa_verify_tests tex "(ecdsa, sha256, low-s)" LowS w1 + , wycheproof_ecdsa_verify_tests tex "(ecdsa, sha256)" Unrestricted w0 + , wycheproof_ecdsa_verify_tests tex "(ecdsa, sha256, low-s)" LowS w1 , wycheproof_ecdh_tests "(ecdh)" w2 - -- , N.execute_ecdsa tex no - -- , testGroup "bip0340 vectors (schnorr)" (fmap (BIP340.execute tex) ip) + , N.execute_ecdsa tex no + , testGroup "bip0340 vectors (schnorr)" (fmap (BIP340.execute tex) ip) ] wycheproof_ecdsa_verify_tests diff --git a/test/WycheproofEcdh.hs b/test/WycheproofEcdh.hs @@ -9,15 +9,15 @@ module WycheproofEcdh ( ) where import Crypto.Curve.Secp256k1 +import qualified Crypto.Hash.SHA256 as SHA256 import Data.Aeson ((.:)) import qualified Data.Aeson as A import qualified Data.Attoparsec.ByteString as AT -import Data.Bits ((.<<.), (.|.)) +import Data.Bits ((.<<.), (.>>.), (.|.)) import qualified Data.ByteString as BS import qualified Data.ByteString.Base16 as B16 import qualified Data.Text as T import qualified Data.Text.Encoding as TE -import qualified GHC.Num.Integer as I import Test.Tasty (TestTree, testGroup) import qualified Test.Tasty.HUnit as H (assertBool, assertEqual, testCase) @@ -42,10 +42,11 @@ execute EcdhTest {..} = H.testCase report $ do -- H.assertBool "invalid" (t_result `elem` ["invalid", "acceptable"]) Right pub -> do - let sec = parse_bigint t_private - sar = parse_bigint t_shared - Affine x_out _ = affine (mul_unsafe pub sec) -- faster - H.assertEqual mempty sar x_out + let sec = parse_bigint t_private + sar = parse_bigint t_shared + h_sar = SHA256.hash (unroll32 sar) + out = ecdh pub sec + H.assertEqual mempty h_sar out where report = "wycheproof ecdh " <> show t_tcId @@ -61,14 +62,14 @@ execute EcdhTest {..} = H.testCase report $ do parse_der_pub :: AT.Parser Projective parse_der_pub = do _ <- AT.word8 0x30 -- SEQUENCE - len <- fmap fi AT.anyWord8 + _ <- AT.anyWord8 _ <- parse_der_algo parse_der_subjectpubkey parse_der_algo :: AT.Parser () parse_der_algo = do _ <- AT.word8 0x30 -- SEQUENCE - _ <- fmap fi AT.anyWord8 -- XX check + _ <- AT.anyWord8 _ <- parse_der_ecpubkey _ <- parse_der_secp256k1 pure () @@ -127,6 +128,34 @@ parse_der_subjectpubkey = do Nothing -> fail "invalid content" Just pt -> pure pt +der_to_pub :: T.Text -> Either String Projective +der_to_pub (B16.decodeLenient . TE.encodeUtf8 -> bs) = + AT.parseOnly parse_der_pub bs + +parse_bigint :: T.Text -> Integer +parse_bigint (B16.decodeLenient . TE.encodeUtf8 -> bs) = roll bs where + roll :: BS.ByteString -> Integer + roll = BS.foldl' alg 0 where + alg !a (fi -> !b) = (a .<<. 8) .|. b + +-- big-endian bytestring encoding +unroll :: Integer -> BS.ByteString +unroll i = case i of + 0 -> BS.singleton 0 + _ -> BS.reverse $ BS.unfoldr step i + where + step 0 = Nothing + step m = Just (fi m, m .>>. 8) + +-- big-endian bytestring encoding for 256-bit ints, left-padding with +-- zeros if necessary. the size of the integer is not checked. +unroll32 :: Integer -> BS.ByteString +unroll32 (unroll -> u) + | l < 32 = BS.replicate (32 - l) 0 <> u + | otherwise = u + where + l = BS.length u + data Wycheproof = Wycheproof { wp_testGroups :: ![EcdhTestGroup] } deriving Show @@ -143,16 +172,6 @@ instance A.FromJSON EcdhTestGroup where parseJSON = A.withObject "EcdhTestGroup" $ \m -> EcdhTestGroup <$> m .: "tests" -der_to_pub :: T.Text -> Either String Projective -der_to_pub (B16.decodeLenient . TE.encodeUtf8 -> bs) = - AT.parseOnly parse_der_pub bs - -parse_bigint :: T.Text -> Integer -parse_bigint (B16.decodeLenient . TE.encodeUtf8 -> bs) = roll bs where - roll :: BS.ByteString -> Integer - roll = BS.foldl' alg 0 where - alg !a (fi -> !b) = (a .<<. 8) .|. b - data EcdhTest = EcdhTest { t_tcId :: !Int , t_public :: !T.Text