secp256k1

Pure Haskell cryptographic primitives on the secp256k1 elliptic curve.
git clone git://git.ppad.tech/secp256k1.git
Log | Files | Refs | LICENSE

commit 4c2215de62088ad357230d43b17ce7cbe5508d12
parent a7369c62d5a8ce7dbd3d39a67154ff4cf5655a1a
Author: Jared Tobin <jared@jtobin.io>
Date:   Tue, 19 Mar 2024 20:42:02 +0400

lib: large-scale excavation

Miscellaneous broad changes.

Diffstat:
Abench/Main.hs | 4++++
Mflake.nix | 4+++-
Mlib/Crypto/Secp256k1.hs | 136+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Mppad-secp256k1.cabal | 17+++++++++++++++++
4 files changed, 133 insertions(+), 28 deletions(-)

diff --git a/bench/Main.hs b/bench/Main.hs @@ -0,0 +1,4 @@ +module Main where + +main :: IO () +main = pure () diff --git a/flake.nix b/flake.nix @@ -31,7 +31,7 @@ devShells.default = hpkgs.shellFor { packages = p: [ - p.${lib} + (hlib.doBenchmark p.${lib}) ]; buildInputs = [ @@ -41,6 +41,8 @@ inputsFrom = builtins.attrValues self.packages.${system}; + doBenchmark = true; + shellHook = '' PS1="[${lib}] \w$ " echo "entering ${system} shell, using" diff --git a/lib/Crypto/Secp256k1.hs b/lib/Crypto/Secp256k1.hs @@ -15,13 +15,16 @@ import Data.STRef -- XX seems Point should have a reference to the curve it's on; probably a lazy -- one. otherwise hard to implement Eq. -- --- XX then again, we're exclusively concerned with a single curve here, so +-- then again, we're exclusively concerned with a single curve here, so -- who cares. bake everything in. -- -- only counterargument is that we may want to reuse the same skeleton for -- other libraries after the fact. so we have a single library implementing -- modular arithmetic, curves, etc., and then implement other stuff on top -- of that +-- +-- i think i like the idea of abstracting quickly and baking types and utils +-- and such into an internal library, e.g. secp256k1-sys; later extract that -- modular arithmetic utilities @@ -78,12 +81,12 @@ modinv' a m = case modinv a m of -- elliptic curve data Curve a = Curve { - curve_p :: !a -- ^ field prime - , curve_n :: !a -- ^ group order - , curve_a :: !a -- ^ /a/ coefficient, weierstrass form - , curve_b :: !a -- ^ /b/ coefficient, weierstrass form - , curve_gx :: !a -- ^ base point x - , curve_gy :: !a -- ^ base point y + curve_p :: a -- ^ field prime + , curve_n :: a -- ^ group order + , curve_a :: a -- ^ /a/ coefficient, weierstrass form + , curve_b :: a -- ^ /b/ coefficient, weierstrass form + , curve_gx :: a -- ^ base point x + , curve_gy :: a -- ^ base point y } deriving Show @@ -95,6 +98,7 @@ secp256k1 = Curve p n 0 7 gx gy where gx = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 +-- XX not general weierstrass; only for j-invariant 0 (i.e. a == 0) weierstrass :: Integral a => a -> a weierstrass x = mods (mods (x * x) * x + curve_b secp256k1) @@ -131,32 +135,36 @@ _GROUP_BYTELENGTH = 32 data Affine a = Affine !a !a deriving Show -data Point a = Point { +-- XX rename Projective? +data Projective a = Projective { px :: !a , py :: !a , pz :: !a } deriving Show -instance Integral a => Eq (Point a) where - Point ax ay az == Point bx by bz = +instance Integral a => Eq (Projective a) where + Projective ax ay az == Projective bx by bz = let x1z2 = mods (ax * bz) x2z1 = mods (bx * az) y1z2 = mods (ay * bz) y2z1 = mods (by * az) in x1z2 == x2z1 && y1z2 == y2z1 -_ZERO :: Integral a => Point a -_ZERO = Point 0 1 0 +_ZERO :: Integral a => Projective a +_ZERO = Projective 0 1 0 -_BASE :: Integral a => Point a -_BASE = Point (curve_gx secp256k1) (curve_gy secp256k1) 1 +_BASE :: Integral a => Projective a +_BASE = Projective (curve_gx secp256k1) (curve_gy secp256k1) 1 -neg :: (Integral a, Num a) => Point a -> Point a -neg (Point x y z) = Point x (mods (negate y)) z +-- negate point +neg :: (Integral a, Num a) => Projective a -> Projective a +neg (Projective x y z) = Projective x (mods (negate y)) z -add :: (Integral a, Num a) => Point a -> Point a -> Point a -add (Point x1 y1 z1) (Point x2 y2 z2) = runST $ do +-- algo 1, "complete addition formulas for prime order elliptic curves," +-- renes et al, 2015 +add :: (Integral a, Num a) => Projective a -> Projective a -> Projective a +add (Projective x1 y1 z1) (Projective x2 y2 z2) = runST $ do let a = curve_a secp256k1 b = curve_b secp256k1 x3 <- newSTRef 0 @@ -239,14 +247,87 @@ add (Point x1 y1 z1) (Point x2 y2 z2) = runST $ do modifySTRef' z3 (\rz3 -> mods (r5 * rz3)) readSTRef t0 >>= \r0 -> modifySTRef' z3 (\rz3 -> mods (rz3 + r0)) - Point <$> readSTRef x3 <*> readSTRef y3 <*> readSTRef z3 + Projective <$> readSTRef x3 <*> readSTRef y3 <*> readSTRef z3 + +-- algo 7, "complete addition formulas for prime order elliptic curves," +-- renes et al, 2015 +add' :: (Integral a, Num a) => Projective a -> Projective a -> Projective a +add' (Projective x1 y1 z1) (Projective x2 y2 z2) = runST $ do + let b = curve_b secp256k1 + x3 <- newSTRef 0 + y3 <- newSTRef 0 + z3 <- newSTRef 0 + let b3 = mods (b * 3) + t0 <- newSTRef (mods (x1 * x2)) -- 1 + t1 <- newSTRef (mods (y1 * y2)) + t2 <- newSTRef (mods (z2 * z2)) + t3 <- newSTRef (mods (x1 + y1)) -- 4 + t4 <- newSTRef (mods (x2 + y2)) + readSTRef t4 >>= \r4 -> + modifySTRef' t3 (\r3 -> mods (r3 * r4)) + readSTRef t0 >>= \r0 -> + readSTRef t1 >>= \r1 -> + writeSTRef t4 (mods (r0 + r1)) + readSTRef t4 >>= \r4 -> + modifySTRef' t3 (\r3 -> mods (r3 - r4)) -- 8 + writeSTRef t4 (mods (y1 + z1)) + writeSTRef x3 (mods (y2 + z2)) + readSTRef x3 >>= \rx3 -> + modifySTRef' t4 (\r4 -> mods (r4 * rx3)) + readSTRef t1 >>= \r1 -> + readSTRef t2 >>= \r2 -> + writeSTRef x3 (mods (r1 + r2)) -- 12 + readSTRef x3 >>= \rx3 -> + modifySTRef' t4 (\r4 -> mods (r4 - rx3)) + writeSTRef x3 (mods (x1 + z1)) + writeSTRef y3 (mods (x2 + z2)) + readSTRef y3 >>= \ry3 -> + modifySTRef' x3 (\rx3 -> mods (rx3 * ry3)) -- 16 + readSTRef t0 >>= \r0 -> + readSTRef t2 >>= \r2 -> + writeSTRef y3 (mods (r0 + r2)) + readSTRef x3 >>= \rx3 -> + modifySTRef' y3 (\ry3 -> mods (rx3 - ry3)) + readSTRef t0 >>= \r0 -> + writeSTRef x3 (mods (r0 + r0)) + readSTRef x3 >>= \rx3 -> + modifySTRef t0 (\r0 -> mods (rx3 + r0)) -- 20 + modifySTRef' t2 (\r2 -> mods (b3 * r2)) + readSTRef t1 >>= \r1 -> + readSTRef t2 >>= \r2 -> + writeSTRef z3 (mods (r1 + r2)) + readSTRef t2 >>= \r2 -> + modifySTRef' t1 (\r1 -> mods (r1 - r2)) + modifySTRef' y3 (\ry3 -> mods (b3 * ry3)) -- 24 + readSTRef t4 >>= \r4 -> + readSTRef y3 >>= \ry3 -> + writeSTRef x3 (mods (r4 * ry3)) + readSTRef t3 >>= \r3 -> + readSTRef t1 >>= \r1 -> + writeSTRef t2 (mods (r3 * r1)) + readSTRef t2 >>= \r2 -> + modifySTRef' x3 (\rx3 -> mods (r2 - rx3)) + readSTRef t0 >>= \r0 -> + modifySTRef' y3 (\ry3 -> mods (ry3 * r0)) -- 28 + readSTRef z3 >>= \rz3 -> + modifySTRef' t1 (\r1 -> mods (r1 * rz3)) + readSTRef t1 >>= \r1 -> + modifySTRef' y3 (\ry3 -> mods (r1 + ry3)) + readSTRef t3 >>= \r3 -> + modifySTRef' t0 (\r0 -> mods (r0 * r3)) + readSTRef t4 >>= \r4 -> + modifySTRef' z3 (\rz3 -> mods (rz3 * r4)) -- 32 + readSTRef t0 >>= \r0 -> + modifySTRef' z3 (\rz3 -> mods (rz3 + r0)) + Projective <$> readSTRef x3 <*> readSTRef y3 <*> readSTRef z3 -double :: (Integral a, Num a) => Point a -> Point a +-- double a point +double :: (Integral a, Num a) => Projective a -> Projective a double p = add p p -- to affine coordinates -affine :: Integral a => Point a -> Maybe (Affine a) -affine p@(Point x y z) +affine :: Integral a => Projective a -> Maybe (Affine a) +affine p@(Projective x y z) | p == _ZERO = pure (Affine 0 0) | z == 1 = pure (Affine x y) | otherwise = do @@ -256,7 +337,7 @@ affine p@(Point x y z) else pure (Affine (mods (x * iz)) (mods (y * iz))) -- point is valid -valid :: Integral a => Point a -> Bool +valid :: Integral a => Projective a -> Bool valid p = case affine p of Nothing -> False Just (Affine x y) @@ -264,7 +345,8 @@ valid p = case affine p of | mods (y * y) /= weierstrass x -> False | otherwise -> True -parse_point :: (Bits a, Integral a) => BS.ByteString -> Maybe (Point a) +-- parse hex-encoded +parse_point :: (Bits a, Integral a) => BS.ByteString -> Maybe (Projective a) parse_point (B16.decode -> ebs) = case ebs of Left _ -> Nothing Right bs -> case BS.uncons bs of @@ -281,11 +363,11 @@ parse_point (B16.decode -> ebs) = case ebs of hodd = h .&. 1 == 1 pure $ if hodd /= yodd - then Point x (mods (negate y)) 1 - else Point x y 1 + then Projective x (mods (negate y)) 1 + else Projective x y 1 else if len == 65 && h == 0x04 -- uncompressed then let (roll -> y, _) = BS.splitAt _GROUP_BYTELENGTH etc - p = Point x y 1 + p = Projective x y 1 in if valid p then Just p else Nothing diff --git a/ppad-secp256k1.cabal b/ppad-secp256k1.cabal @@ -29,3 +29,20 @@ library base , base16-bytestring , bytestring + +benchmark secp256k1-bench + type: exitcode-stdio-1.0 + default-language: Haskell2010 + hs-source-dirs: bench + main-is: Main.hs + + ghc-options: + -rtsopts -O2 -Wall -fno-warn-orphans + + build-depends: + base + , bytestring + , criterion + , deepseq + , ppad-secp256k1 +