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 980e33ce58cd126eb9f645eef21cfd05192b9849
parent 4010a25186e47ca20ae02d8d342723e466652b4a
Author: Jared Tobin <jared@jtobin.io>
Date:   Sun, 23 Nov 2025 17:05:45 +0400

meta: use llvm flag when building ppad-fixed

Diffstat:
Mbench/Main.hs | 197++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Mflake.lock | 8++++----
Mflake.nix | 11++++++++---
Mlib/Crypto/Curve/Secp256k1.hs | 330++++++++++++++++++++++++++++++++++++++++---------------------------------------
4 files changed, 293 insertions(+), 253 deletions(-)

diff --git a/bench/Main.hs b/bench/Main.hs @@ -1,4 +1,4 @@ -{-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-} +{-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns -fno-warn-type-defaults #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE OverloadedStrings #-} @@ -11,10 +11,12 @@ import Control.DeepSeq import Criterion.Main import qualified Crypto.Curve.Secp256k1 as S +import qualified Numeric.Montgomery.Secp256k1.Curve as C + instance NFData S.Projective instance NFData S.Affine -- instance NFData S.ECDSA --- instance NFData S.Context +instance NFData S.Context decodeLenient :: BS.ByteString -> BS.ByteString decodeLenient bs = case B16.decode bs of @@ -27,11 +29,11 @@ main = defaultMain [ add , mul --, precompute - --, mul_wnaf - --, derive_pub + , mul_wnaf + , derive_pub --, schnorr --, ecdsa - --, ecdh + , ecdh ] parse_int256 :: BS.ByteString -> Integer @@ -68,6 +70,12 @@ parse_int256 bs = case S.parse_int256 bs of -- big = BS.replicate 32 0xFF -- pure (small, big) +mul_fixed :: Benchmark +mul_fixed = bgroup "mul_fixed" [ + bench "curve: M(2) * M(2)" $ nf (C.mul 2) 2 + , bench "curve: M(2) * M(2 ^ 255 - 19)" $ nf (C.mul 2) (2 ^ 255 - 19) + ] + add :: Benchmark add = bgroup "add" [ bench "2 p (double, trivial projective point)" $ nf (S.add p) p @@ -78,46 +86,50 @@ add = bgroup "add" [ ] mul :: Benchmark -mul = env setup $ \x -> - bgroup "mul" [ - bench "2 G" $ nf (S.mul S._CURVE_G) (W.to 2) - , bench "(2 ^ 255 - 19) G" $ nf (S.mul S._CURVE_G) x - ] - where - setup = pure . W.to . parse_int256 $ decodeLenient - "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed" +mul = bench "mul" $ nf (S.mul p) (W.to 12831231) --- precompute :: Benchmark --- precompute = bench "precompute" $ nfIO (pure S.precompute) --- --- mul_wnaf :: Benchmark --- mul_wnaf = env setup $ \ ~(tex, x) -> --- bgroup "mul_wnaf" [ --- bench "2 G" $ nf (S.mul_wnaf tex) 2 --- , bench "(2 ^ 255 - 19) G" $ nf (S.mul_wnaf tex) x --- ] --- where --- setup = do --- let !tex = S.precompute --- !int = parse_int256 $ decodeLenient --- "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed" --- pure (tex, int) --- --- derive_pub :: Benchmark --- derive_pub = env setup $ \ ~(tex, x) -> --- bgroup "derive_pub" [ --- bench "sk = 2" $ nf S.derive_pub 2 --- , bench "sk = 2 ^ 255 - 19" $ nf S.derive_pub x --- , bench "wnaf, sk = 2" $ nf (S.derive_pub' tex) 2 --- , bench "wnaf, sk = 2 ^ 255 - 19" $ nf (S.derive_pub' tex) x + +-- mul :: Benchmark +-- mul = env setup $ \x -> +-- bgroup "mul" [ +-- bench "2 G" $ nf (S.mul S._CURVE_G) (W.to 2) +-- , bench "(2 ^ 255 - 19) G" $ nf (S.mul S._CURVE_G) x -- ] -- where --- setup = do --- let !tex = S.precompute --- !int = parse_int256 $ decodeLenient --- "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed" --- pure (tex, int) --- +-- setup = pure . W.to . parse_int256 $ decodeLenient +-- "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed" + +-- precompute :: Benchmark +-- precompute = bench "precompute" $ nfIO (pure S.precompute) + +mul_wnaf :: Benchmark +mul_wnaf = env setup $ \ ~(tex, x) -> + bgroup "mul_wnaf" [ + bench "2 G" $ nf (S.mul_wnaf tex) 2 + , bench "(2 ^ 255 - 19) G" $ nf (S.mul_wnaf tex) x + ] + where + setup = do + let !tex = S.precompute + !int = parse_int256 $ decodeLenient + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed" + pure (tex, int) + +derive_pub :: Benchmark +derive_pub = env setup $ \ ~(tex, x) -> + bgroup "derive_pub" [ + bench "sk = 2" $ nf S.derive_pub (W.to 2) + , bench "sk = 2 ^ 255 - 19" $ nf S.derive_pub x + , bench "wnaf, sk = 2" $ nf (S.derive_pub' tex) 2 + , bench "wnaf, sk = 2 ^ 255 - 19" $ nf (S.derive_pub' tex) (W.from x) + ] + where + setup = do + let !tex = S.precompute + !int = parse_int256 $ decodeLenient + "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed" + pure (tex, W.to int) + -- schnorr :: Benchmark -- schnorr = env setup $ \ ~(tex, big) -> -- bgroup "schnorr" [ @@ -154,47 +166,66 @@ mul = env setup $ \x -> -- msg = "i approve of this message" -- Just sig = S.sign_ecdsa big s_msg -- pure (tex, big, pub, msg, sig) --- --- ecdh :: Benchmark --- ecdh = env setup $ \ ~(big, pub) -> --- bgroup "ecdh" [ --- bench "ecdh (small)" $ nf (S.ecdh pub) 2 --- , bench "ecdh (large)" $ nf (S.ecdh pub) big --- ] --- where --- setup = do --- let !big = --- 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed --- !(Just !pub) = S.parse_point . decodeLenient $ --- "bd02b9dfc8ef760708950bd972f2dc244893b61b6b46c3b19be1b2da7b034ac5" --- pure (big, pub) -p_bs :: BS.ByteString -p_bs = decodeLenient - "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" +ecdh :: Benchmark +ecdh = env setup $ \ ~(big, pub) -> + bgroup "ecdh" [ + bench "ecdh (small)" $ nf (S.ecdh pub) (W.to 2) + , bench "ecdh (large)" $ nf (S.ecdh pub) big + ] + where + setup = do + let !big = + W.to 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed + !(Just !pub) = S.parse_point . decodeLenient $ + "bd02b9dfc8ef760708950bd972f2dc244893b61b6b46c3b19be1b2da7b034ac5" + pure (big, pub) -p :: S.Projective -p = case S.parse_point p_bs of - Nothing -> error "bang" - Just !pt -> pt -q_bs :: BS.ByteString -q_bs = decodeLenient - "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9" +p :: S.Projective +p = S.Projective + 55066263022277343669578718895168534326250603453777594175500187360389116729240 + 32670510020758816978083085130507043184471273380659243275938904335757337482424 + 1 q :: S.Projective -q = case S.parse_point q_bs of - Nothing -> error "bang" - Just !pt -> pt - -r_bs :: BS.ByteString -r_bs = decodeLenient - "03a2113cf152585d96791a42cdd78782757fbfb5c6b2c11b59857eb4f7fda0b0e8" +q = S.Projective + 112711660439710606056748659173929673102114977341539408544630613555209775888121 + 25583027980570883691656905877401976406448868254816295069919888960541586679410 + 1 r :: S.Projective -r = case S.parse_point r_bs of - Nothing -> error "bang" - Just !pt -> pt +r = S.Projective + 73305138481390301074068425511419969342201196102229546346478796034582161436904 + 77311080844824646227678701997218206005272179480834599837053144390237051080427 + 1 + +-- p_bs :: BS.ByteString +-- p_bs = decodeLenient +-- "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" +-- +-- p :: S.Projective +-- p = case S.parse_point p_bs of +-- Nothing -> error "bang" +-- Just !pt -> pt +-- +-- q_bs :: BS.ByteString +-- q_bs = decodeLenient +-- "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9" +-- +-- q :: S.Projective +-- q = case S.parse_point q_bs of +-- Nothing -> error "bang" +-- Just !pt -> pt +-- +-- r_bs :: BS.ByteString +-- r_bs = decodeLenient +-- "03a2113cf152585d96791a42cdd78782757fbfb5c6b2c11b59857eb4f7fda0b0e8" +-- +-- r :: S.Projective +-- r = case S.parse_point r_bs of +-- Nothing -> error "bang" +-- Just !pt -> pt s_bs :: BS.ByteString s_bs = decodeLenient @@ -205,13 +236,13 @@ s = case S.parse_point s_bs of Nothing -> error "bang" Just !pt -> pt -t_bs :: BS.ByteString -t_bs = decodeLenient "04b838ff44e5bc177bf21189d0766082fc9d843226887fc9760371100b7ee20a6ff0c9d75bfba7b31a6bca1974496eeb56de357071955d83c4b1badaa0b21832e9" - -t :: S.Projective -t = case S.parse_point t_bs of - Nothing -> error "bang" - Just !pt -> pt +-- t_bs :: BS.ByteString +-- t_bs = decodeLenient "04b838ff44e5bc177bf21189d0766082fc9d843226887fc9760371100b7ee20a6ff0c9d75bfba7b31a6bca1974496eeb56de357071955d83c4b1badaa0b21832e9" +-- +-- t :: S.Projective +-- t = case S.parse_point t_bs of +-- Nothing -> error "bang" +-- Just !pt -> pt -- s_sk :: Integer -- s_sk = parse_int256 . decodeLenient $ diff --git a/flake.lock b/flake.lock @@ -184,11 +184,11 @@ ] }, "locked": { - "lastModified": 1763816080, - "narHash": "sha256-dHPSezr7Y5M5YgK2d+p62XW6jckNiDfjpAw32VqT8nQ=", + "lastModified": 1763905570, + "narHash": "sha256-uqTvkAlY/1rUEQx1G54UuMxkAbrWtLhPtvFU4nmq2Rk=", "ref": "master", - "rev": "5145ed6597dfdac8ffa3d52f2ed892f69831315b", - "revCount": 162, + "rev": "1517cd938c0447460d32872e75ab798d2d5d245b", + "revCount": 171, "type": "git", "url": "git://git.ppad.tech/fixed.git" }, diff --git a/flake.nix b/flake.nix @@ -56,17 +56,23 @@ pkgs = import nixpkgs { inherit system; }; hlib = pkgs.haskell.lib; + llvm = pkgs.llvmPackages_15.llvm; base16 = ppad-base16.packages.${system}.default; sha256 = ppad-sha256.packages.${system}.default; hmac-drbg = ppad-hmac-drbg.packages.${system}.default; + fixed = ppad-fixed.packages.${system}.default; + fixed-llvm = + hlib.addBuildTools + (hlib.enableCabalFlag fixed "llvm") + [ llvm ]; hpkgs = pkgs.haskell.packages.ghc981.extend (new: old: { ppad-base16 = base16; ppad-sha256 = sha256; ppad-hmac-drbg = hmac-drbg; - ppad-fixed = fixed; + ppad-fixed = fixed-llvm; ${lib} = new.callCabal2nix lib ./. { ppad-base16 = new.ppad-base16; ppad-sha256 = new.ppad-sha256; @@ -78,7 +84,6 @@ cc = pkgs.stdenv.cc; ghc = hpkgs.ghc; cabal = hpkgs.cabal-install; - llvm = pkgs.llvmPackages_15.llvm; in { packages.default = hpkgs.${lib}; @@ -94,7 +99,7 @@ llvm ]; - inputsFrom = builtins.attrValues self.packages.${system}; + # inputsFrom = builtins.attrValues self.packages.${system}; doBenchmark = true; diff --git a/lib/Crypto/Curve/Secp256k1.hs b/lib/Crypto/Curve/Secp256k1.hs @@ -175,26 +175,26 @@ roll32 bs = go (0 :: Word64) (0 :: Word64) (0 :: Word64) (0 :: Word64) 0 where in go acc0 acc1 acc2 ((acc3 `B.unsafeShiftL` 8) .|. b) (j + 1) {-# INLINE roll32 #-} --- -- this "looks" inefficient due to the call to reverse, but it's --- -- actually really fast --- --- -- 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 `I.integerShiftR` 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 +-- this "looks" inefficient due to the call to reverse, but it's +-- actually really fast + +-- 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 `I.integerShiftR` 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 -- (bip0340) return point with x coordinate == x and with even y coordinate lift :: Integer -> Maybe Affine @@ -249,6 +249,10 @@ affine p@(Projective x y z) let !iz = C.inv z in Affine (x * iz) (y * iz) +from_montgomery :: Affine -> Pair Integer Integer +from_montgomery (Affine (C.retr -> x) (C.retr -> y)) = + Pair (W.from x) (W.from y) + -- Convert to projective coordinates. projective :: Affine -> Projective projective p@(Affine x y) @@ -598,124 +602,124 @@ mul p sec@(W.Wider _SECRET) = do -- nm = I.integerShiftR m 1 -- nr = if I.integerTestBit m 0 then add r d else r -- in loop nr nd nm + +-- | Precomputed multiples of the secp256k1 base or generator point. +data Context = Context { + ctxW :: {-# UNPACK #-} !Int + , ctxArray :: !(A.Array Projective) + } deriving (Eq, Generic) + +instance Show Context where + show Context {} = "<secp256k1 context>" + +-- | Create a secp256k1 context by precomputing multiples of the curve's +-- generator point. -- --- -- | Precomputed multiples of the secp256k1 base or generator point. --- data Context = Context { --- ctxW :: {-# UNPACK #-} !Int --- , ctxArray :: !(A.Array Projective) --- } deriving (Eq, Generic) --- --- instance Show Context where --- show Context {} = "<secp256k1 context>" --- --- -- | Create a secp256k1 context by precomputing multiples of the curve's --- -- generator point. --- -- --- -- This should be used once to create a 'Context' to be reused --- -- repeatedly afterwards. --- -- --- -- >>> let !tex = precompute --- -- >>> sign_ecdsa' tex sec msg --- -- >>> sign_schnorr' tex sec msg aux --- precompute :: Context --- precompute = _precompute 8 --- --- -- dumb strict pair --- data Pair a b = Pair !a !b --- --- -- translation of noble-secp256k1's 'precompute' --- _precompute :: Int -> Context --- _precompute ctxW = Context {..} where --- ctxArray = A.arrayFromListN size (loop_w mempty _CURVE_G 0) --- capJ = (2 :: Int) ^ (ctxW - 1) --- ws = 256 `quot` ctxW + 1 --- size = ws * capJ --- --- loop_w !acc !p !w --- | w == ws = reverse acc --- | otherwise = --- let b = p --- !(Pair nacc nb) = loop_j p (b : acc) b 1 --- np = double nb --- in loop_w nacc np (succ w) +-- This should be used once to create a 'Context' to be reused +-- repeatedly afterwards. -- --- loop_j !p !acc !b !j --- | j == capJ = Pair acc b --- | otherwise = --- let nb = add b p --- in loop_j p (nb : acc) nb (succ j) +-- >>> let !tex = precompute +-- >>> sign_ecdsa' tex sec msg +-- >>> sign_schnorr' tex sec msg aux +precompute :: Context +precompute = _precompute 8 + +-- dumb strict pair +data Pair a b = Pair !a !b + +-- translation of noble-secp256k1's 'precompute' +_precompute :: Int -> Context +_precompute ctxW = Context {..} where + ctxArray = A.arrayFromListN size (loop_w mempty _CURVE_G 0) + capJ = (2 :: Int) ^ (ctxW - 1) + ws = 256 `quot` ctxW + 1 + size = ws * capJ + + loop_w !acc !p !w + | w == ws = reverse acc + | otherwise = + let b = p + !(Pair nacc nb) = loop_j p (b : acc) b 1 + np = double nb + in loop_w nacc np (succ w) + + loop_j !p !acc !b !j + | j == capJ = Pair acc b + | otherwise = + let nb = add b p + in loop_j p (nb : acc) nb (succ j) -- -- Timing-safe wNAF (w-ary non-adjacent form) scalar multiplication of --- -- secp256k1 points. --- mul_wnaf :: Context -> Integer -> Maybe Projective --- mul_wnaf Context {..} _SECRET = do --- guard (ge _SECRET) --- pure $! loop 0 _CURVE_ZERO _CURVE_G _SECRET --- where --- wins = 256 `quot` ctxW + 1 --- wsize = 2 ^ (ctxW - 1) --- mask = 2 ^ ctxW - 1 --- mnum = 2 ^ ctxW --- --- loop !w !acc !f !n --- | w == wins = acc --- | otherwise = --- let !off0 = w * fi wsize --- --- -- XX branches on secret data --- --- -- b0 = n & (m-1) --- -- carry = (b0 >> (w-1)) & 1 -- 0 or 1 --- -- d = b0 - carry*m -- signed in [-(m-1), ..., +(m-1)] --- -- n' = (n >> w) + carry --- !b0 = n `I.integerAnd` mask --- !n0 = n `I.integerShiftR` fi ctxW --- --- !(Pair b1 n1) | b0 > wsize = Pair (b0 - mnum) (n0 + 1) --- | otherwise = Pair b0 n0 --- --- -- XX branches on secret data --- --- -- sgn = maskbit(d < 0) -- 0x..FF if d<0 else 0x..00 --- -- ad = abs(d) = (d ^ sgn) - sgn --- !c0 = B.testBit w 0 --- !c1 = b1 < 0 --- --- !off1 = off0 + fi (abs b1) - 1 --- --- in if b1 == 0 --- then let !pr = A.indexArray ctxArray off0 --- !pt | c0 = neg pr --- | otherwise = pr --- in loop (w + 1) acc (add f pt) n1 --- else let !pr = A.indexArray ctxArray off1 --- !pt | c1 = neg pr --- | otherwise = pr --- in loop (w + 1) (add acc pt) f n1 --- {-# INLINE mul_wnaf #-} - --- -- | Derive a public key (i.e., a secp256k1 point) from the provided --- -- secret. --- -- --- -- >>> import qualified System.Entropy as E --- -- >>> sk <- fmap parse_int256 (E.getEntropy 32) --- -- >>> derive_pub sk --- -- Just "<secp256k1 point>" --- derive_pub :: Integer -> Maybe Pub --- derive_pub = mul _CURVE_G --- {-# NOINLINE derive_pub #-} --- --- -- | The same as 'derive_pub', except uses a 'Context' to optimise --- -- internal calculations. --- -- --- -- >>> import qualified System.Entropy as E --- -- >>> sk <- fmap parse_int256 (E.getEntropy 32) --- -- >>> let !tex = precompute --- -- >>> derive_pub' tex sk --- -- Just "<secp256k1 point>" --- derive_pub' :: Context -> Integer -> Maybe Pub --- derive_pub' = mul_wnaf --- {-# NOINLINE derive_pub' #-} +-- secp256k1 points. +mul_wnaf :: Context -> Integer -> Maybe Projective +mul_wnaf Context {..} _SECRET = do + guard (ge _SECRET) + pure $! loop 0 _CURVE_ZERO _CURVE_G _SECRET + where + wins = 256 `quot` ctxW + 1 + wsize = 2 ^ (ctxW - 1) + mask = 2 ^ ctxW - 1 + mnum = 2 ^ ctxW + + loop !w !acc !f !n + | w == wins = acc + | otherwise = + let !off0 = w * fi wsize + + -- XX branches on secret data + + -- b0 = n & (m-1) + -- carry = (b0 >> (w-1)) & 1 -- 0 or 1 + -- d = b0 - carry*m -- signed in [-(m-1), ..., +(m-1)] + -- n' = (n >> w) + carry + !b0 = n `I.integerAnd` mask + !n0 = n `I.integerShiftR` fi ctxW + + !(Pair b1 n1) | b0 > wsize = Pair (b0 - mnum) (n0 + 1) + | otherwise = Pair b0 n0 + + -- XX branches on secret data + + -- sgn = maskbit(d < 0) -- 0x..FF if d<0 else 0x..00 + -- ad = abs(d) = (d ^ sgn) - sgn + !c0 = B.testBit w 0 + !c1 = b1 < 0 + + !off1 = off0 + fi (abs b1) - 1 + + in if b1 == 0 + then let !pr = A.indexArray ctxArray off0 + !pt | c0 = neg pr + | otherwise = pr + in loop (w + 1) acc (add f pt) n1 + else let !pr = A.indexArray ctxArray off1 + !pt | c1 = neg pr + | otherwise = pr + in loop (w + 1) (add acc pt) f n1 +{-# INLINE mul_wnaf #-} + +-- | Derive a public key (i.e., a secp256k1 point) from the provided +-- secret. +-- +-- >>> import qualified System.Entropy as E +-- >>> sk <- fmap parse_int256 (E.getEntropy 32) +-- >>> derive_pub sk +-- Just "<secp256k1 point>" +derive_pub :: W.Wider -> Maybe Pub +derive_pub = mul _CURVE_G +{-# NOINLINE derive_pub #-} + +-- | The same as 'derive_pub', except uses a 'Context' to optimise +-- internal calculations. +-- +-- >>> import qualified System.Entropy as E +-- >>> sk <- fmap parse_int256 (E.getEntropy 32) +-- >>> let !tex = precompute +-- >>> derive_pub' tex sk +-- Just "<secp256k1 point>" +derive_pub' :: Context -> Integer -> Maybe Pub +derive_pub' = mul_wnaf +{-# NOINLINE derive_pub' #-} -- parsing -------------------------------------------------------------------- @@ -1246,32 +1250,32 @@ _parse_uncompressed h (BS.splitAt _CURVE_Q_BYTES -> (roll32 -> x, roll32 -> y)) -- let Affine (modQ -> v) _ = affine capR -- guard (v == r) -- {-# INLINE _verify_ecdsa_unrestricted #-} --- --- -- ecdh ----------------------------------------------------------------------- --- --- -- SEC1-v2 3.3.1, plus SHA256 hash --- --- -- | Compute a shared secret, given a secret key and public secp256k1 point, --- -- via Elliptic Curve Diffie-Hellman (ECDH). --- -- --- -- The shared secret is the SHA256 hash of the x-coordinate of the --- -- point obtained by scalar multiplication. --- -- --- -- >>> let sec_alice = 0x03 -- contrived --- -- >>> let sec_bob = 2 ^ 128 - 1 -- contrived --- -- >>> let Just pub_alice = derive_pub sec_alice --- -- >>> let Just pub_bob = derive_pub sec_bob --- -- >>> let secret_as_computed_by_alice = ecdh pub_bob sec_alice --- -- >>> let secret_as_computed_by_bob = ecdh pub_alice sec_bob --- -- >>> secret_as_computed_by_alice == secret_as_computed_by_bob --- -- True --- ecdh --- :: Projective -- ^ public key --- -> Integer -- ^ secret key --- -> Maybe BS.ByteString -- ^ shared secret --- ecdh pub _SECRET = do --- pt <- mul pub _SECRET --- guard (pt /= _CURVE_ZERO) --- let Affine x _ = affine pt --- pure $! SHA256.hash (unroll32 x) --- + +-- ecdh ----------------------------------------------------------------------- + +-- SEC1-v2 3.3.1, plus SHA256 hash + +-- | Compute a shared secret, given a secret key and public secp256k1 point, +-- via Elliptic Curve Diffie-Hellman (ECDH). +-- +-- The shared secret is the SHA256 hash of the x-coordinate of the +-- point obtained by scalar multiplication. +-- +-- >>> let sec_alice = 0x03 -- contrived +-- >>> let sec_bob = 2 ^ 128 - 1 -- contrived +-- >>> let Just pub_alice = derive_pub sec_alice +-- >>> let Just pub_bob = derive_pub sec_bob +-- >>> let secret_as_computed_by_alice = ecdh pub_bob sec_alice +-- >>> let secret_as_computed_by_bob = ecdh pub_alice sec_bob +-- >>> secret_as_computed_by_alice == secret_as_computed_by_bob +-- True +ecdh + :: Projective -- ^ public key + -> W.Wider -- ^ secret key + -> Maybe BS.ByteString -- ^ shared secret +ecdh pub _SECRET = do + pt <- mul pub _SECRET + guard (pt /= _CURVE_ZERO) + let !(Pair x _) = from_montgomery (affine pt) + pure $! SHA256.hash (unroll32 x) +