secp256k1

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

commit 012b30d20d080dacf3f8f26dad4f5e467bde93d1
parent 343e66598319f423fe10f828a61af7967450fdaf
Author: Jared Tobin <jared@jtobin.io>
Date:   Mon, 23 Sep 2024 10:30:37 +0400

lib: minor reorg

Diffstat:
Mlib/Crypto/Curve/Secp256k1.hs | 182+++++++++++++++++++++++++++++++++++++++++++-------------------------------------
1 file changed, 98 insertions(+), 84 deletions(-)

diff --git a/lib/Crypto/Curve/Secp256k1.hs b/lib/Crypto/Curve/Secp256k1.hs @@ -12,50 +12,29 @@ import Control.Monad (when) import Control.Monad.ST import qualified Data.ByteString as BS import qualified Data.ByteString.Base16 as B16 +import Data.Int (Int64) import Data.STRef import GHC.Generics import GHC.Natural import qualified GHC.Num.Integer as I import Prelude hiding (mod) --- keystroke saver +-- keystroke savers & other utilities ----------------------------------------- fi :: (Integral a, Num b) => a -> b fi = fromIntegral +{-# INLINE fi #-} --- see https://www.secg.org/sec2-v2.pdf for parameter specs - --- secp256k1 field prime --- --- ~ 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1 -_CURVE_P :: Integer -_CURVE_P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F - --- | Division modulo secp256k1 field prime. -modP :: Integer -> Integer -modP a = I.integerMod a _CURVE_P - --- secp256k1 group order -_CURVE_N :: Integer -_CURVE_N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - --- smallest integer such that _CURVE_N < 2 ^ _CURVE_N_LEN -_CURVE_N_LEN :: Integer -_CURVE_N_LEN = 256 - --- bytelength of _CURVE_N -_CURVE_N_BYTES :: Int -_CURVE_N_BYTES = 32 - --- secp256k1 short weierstrass form, /a/ coefficient -_CURVE_A :: Integer -_CURVE_A = 0 +-- generic modular inverse +-- for a, m return x such that ax = 1 mod m +modinv :: Integer -> Natural -> Maybe Integer +modinv a m = case I.integerRecipMod# a m of + (# fi -> n | #) -> Just $! n + (# | _ #) -> Nothing --- secp256k1 weierstrass form, /b/ coefficient -_CURVE_B :: Integer -_CURVE_B = 7 +-- coordinate systems --------------------------------------------------------- --- point in affine coordinates +-- curve point, affine coordinates data Affine = Affine !Integer !Integer deriving stock (Show, Generic) @@ -63,7 +42,7 @@ instance Eq Affine where Affine x1 y1 == Affine x2 y2 = modP x1 == modP x2 && modP y1 == modP y2 --- point in projective coordinates +-- curve point, projective coordinates data Projective = Projective { px :: !Integer , py :: !Integer @@ -79,9 +58,65 @@ instance Eq Projective where y2z1 = modP (by * az) in x1z2 == x2z1 && y1z2 == y2z1 +-- | Convert to affine coordinates. +affine :: Projective -> Affine +affine p@(Projective x y z) + | p == _ZERO = Affine 0 0 + | z == 1 = Affine x y + | otherwise = case modinv z (fi _CURVE_P) of + Nothing -> error "ppad-secp256k1 (affine): impossible point" + Just iz -> Affine (modP (x * iz)) (modP (y * iz)) + +-- | Convert to projective coordinates. +projective :: Affine -> Projective +projective (Affine x y) + | x == 0 && y == 0 = _ZERO + | otherwise = Projective x y 1 + +-- | Point is valid +valid :: Projective -> Bool +valid p = case affine p of + Affine x y + | not (fe x) || not (fe y) -> False + | modP (y * y) /= weierstrass x -> False + | otherwise -> True + +-- curve parameters ----------------------------------------------------------- +-- see https://www.secg.org/sec2-v2.pdf for parameter specs + +-- secp256k1 field prime +-- +-- = 2^256 - 2^32 - 2^9 - 2^8 - 2^7 - 2^6 - 2^4 - 1 +_CURVE_P :: Integer +_CURVE_P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F + +-- secp256k1 group order +_CURVE_N :: Integer +_CURVE_N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 + +-- bitlength of group order +-- +-- = smallest integer such that _CURVE_N < 2 ^ _CURVE_N_BITS +_CURVE_N_BITS :: Int64 +_CURVE_N_BITS = 256 + +-- bytelength of _CURVE_N +-- +-- = _CURVE_N_BITS / 8 +_CURVE_N_BYTES :: Int64 +_CURVE_N_BYTES = 32 + +-- secp256k1 short weierstrass form, /a/ coefficient +_CURVE_A :: Integer +_CURVE_A = 0 + +-- secp256k1 weierstrass form, /b/ coefficient +_CURVE_B :: Integer +_CURVE_B = 7 + -- secp256k1 generator -- --- ~ parse "0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798" +-- = parse "0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798" _CURVE_G :: Projective _CURVE_G = Projective x y 1 where x = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 @@ -91,27 +126,31 @@ _CURVE_G = Projective x y 1 where _ZERO :: Projective _ZERO = Projective 0 1 0 +-- | secp256k1 in prime order j-invariant 0 form (i.e. a == 0). +weierstrass :: Integer -> Integer +weierstrass x = modP (modP (x * x) * x + _CURVE_B) + +-- field, group operations ---------------------------------------------------- + +-- | Division modulo secp256k1 field prime. +modP :: Integer -> Integer +modP a = I.integerMod a _CURVE_P + -- | Division modulo secp256k1 group order. modN :: Integer -> Integer modN a = I.integerMod a _CURVE_N --- | Is field element. +-- | Is field element? fe :: Integer -> Bool fe n = 0 < n && n < _CURVE_P --- | Is group element. +-- | Is group element? ge :: Integer -> Bool ge n = 0 < n && n < _CURVE_N --- modular inverse --- for a, m return x such that ax = 1 mod m -modinv :: Integer -> Natural -> Maybe Integer -modinv a m = case I.integerRecipMod# a m of - (# fi -> n | #) -> Just n - (# | _ #) -> Nothing - --- modular square root (shanks-tonelli) --- for a, m return x such that a = xx mod m +-- | Square root (Shanks-Tonelli) modulo secp256k1 field prime. +-- +-- For a, return x such that a = x x mod _CURVE_P. modsqrt :: Integer -> Maybe Integer modsqrt n = runST $ do r <- newSTRef 1 @@ -121,7 +160,7 @@ modsqrt n = runST $ do rr <- readSTRef r pure $ if modP (rr * rr) == n - then Just rr + then Just $! rr else Nothing where loop sr snum se = do @@ -134,16 +173,13 @@ modsqrt n = runST $ do modifySTRef' se (`I.integerShiftR` 1) loop sr snum se --- prime order j-invariant 0 (i.e. a == 0) -weierstrass :: Integer -> Integer -weierstrass x = modP (modP (x * x) * x + _CURVE_B) +-- ec point operations -------------------------------------------------------- --- negate point +-- | Negate secp256k1 point. neg :: Projective -> Projective neg (Projective x y z) = Projective x (modP (negate y)) z --- general ec addition --- XX possibly shouldn't mix algos due to constant-time issues +-- | Elliptic curve addition on secp256k1. add :: Projective -> Projective -> Projective add p q@(Projective _ _ z) | p == q = double p -- algo 9 @@ -320,7 +356,7 @@ double (Projective x y z) = runST $ do modifySTRef' x3 (\rx3 -> modP (rx3 + rx3)) Projective <$> readSTRef x3 <*> readSTRef y3 <*> readSTRef z3 --- ec scalar multiplication +-- | Scalar multiplication of secp256k1 points. mul :: Projective -> Integer -> Projective mul p n | n == 0 = _ZERO @@ -335,7 +371,7 @@ mul p n nr = if I.integerTestBit m 0 then add r d else r in loop nr nd nm --- XX confirm timing safety +-- | Safe scalar multiplication of secp256k1 points. mul_safe :: Projective -> Integer -> Projective mul_safe p n | not (ge n) = error "ppad-secp256k1 (mul_safe): scalar not in group" @@ -350,28 +386,7 @@ mul_safe p n then loop (add r d) f nd nm else loop r (add f d) nd nm --- | Convert to affine coordinates. -affine :: Projective -> Affine -affine p@(Projective x y z) - | p == _ZERO = Affine 0 0 - | z == 1 = Affine x y - | otherwise = case modinv z (fi _CURVE_P) of - Nothing -> error "ppad-secp256k1 (affine): impossible point" - Just iz -> Affine (modP (x * iz)) (modP (y * iz)) - --- | Convert to projective coordinates. -projective :: Affine -> Projective -projective (Affine x y) - | x == 0 && y == 0 = _ZERO - | otherwise = Projective x y 1 - --- | Point is valid -valid :: Projective -> Bool -valid p = case affine p of - Affine x y - | not (fe x) || not (fe y) -> False - | modP (y * y) /= weierstrass x -> False - | otherwise -> True +-- parsing -------------------------------------------------------------------- -- | Parse hex-encoded compressed or uncompressed point. parse :: BS.ByteString -> Maybe Projective @@ -380,10 +395,9 @@ parse (B16.decode -> ebs) = case ebs of Right bs -> case BS.uncons bs of Nothing -> Nothing Just (fi -> h, t) -> - let (roll -> x, etc) = BS.splitAt _CURVE_N_BYTES t + let (roll -> x, etc) = BS.splitAt (fi _CURVE_N_BYTES) t len = BS.length bs - in -- compressed - if len == 33 && (h == 0x02 || h == 0x03) + in if len == 33 && (h == 0x02 || h == 0x03) -- compressed then if not (fe x) then Nothing else do @@ -394,9 +408,9 @@ parse (B16.decode -> ebs) = case ebs of if hodd /= yodd then Projective x (modP (negate y)) 1 else Projective x y 1 - else -- uncompressed - if len == 65 && h == 0x04 - then let (roll -> y, _) = BS.splitAt _CURVE_N_BYTES etc + else + if len == 65 && h == 0x04 -- uncompressed + then let (roll -> y, _) = BS.splitAt (fi _CURVE_N_BYTES) etc p = Projective x y 1 in if valid p then Just p @@ -421,7 +435,7 @@ unroll i = case i of bits2int :: BS.ByteString -> Integer bits2int bs = let (fi -> blen) = BS.length bs * 8 - (fi -> qlen) = _CURVE_N_LEN -- RFC6979 notation + (fi -> qlen) = _CURVE_N_BITS -- RFC6979 notation del = blen - qlen in if del > 0 then roll bs `I.integerShiftR` del @@ -431,7 +445,7 @@ bits2int bs = int2octets :: Integer -> BS.ByteString int2octets i = pad (unroll i) where pad !bs - | BS.length bs < _CURVE_N_BYTES = pad (BS.cons 0 bs) + | BS.length bs < fi _CURVE_N_BYTES = pad (BS.cons 0 bs) | otherwise = bs -- RFC6979