commit 92adba2d27185cb2d719b73e95398b0ae739b5d6
parent 3531be4d01b2d343b5ebe08f40a1243cc05f6e16
Author: Jared Tobin <jared@jtobin.io>
Date: Sun, 21 Dec 2025 17:38:58 -0330
Merge branch 'unboxed'
Diffstat:
12 files changed, 1410 insertions(+), 413 deletions(-)
diff --git a/bench/Main.hs b/bench/Main.hs
@@ -27,9 +27,11 @@ main :: IO ()
main = defaultMain [
parse_point
, add
+ , double
, mul
- , precompute
+ , mul_vartime
, mul_wnaf
+ , precompute
, derive_pub
, schnorr
, ecdsa
@@ -67,13 +69,23 @@ mul_fixed = bgroup "mul_fixed" [
]
add :: Benchmark
-add = bgroup "add" [
- bench "2 p (double, trivial projective point)" $ nf (S.add p) p
- , bench "2 r (double, nontrivial projective point)" $ nf (S.add r) r
- , bench "p + q (trivial projective points)" $ nf (S.add p) q
- , bench "p + s (nontrivial mixed points)" $ nf (S.add p) s
- , bench "s + r (nontrivial projective points)" $ nf (S.add s) r
- ]
+add = env setup $ \ ~(!pl, !ql, !rl, !sl) ->
+ bgroup "add" [
+ bench "p + q (trivial projective points)" $ nf (S.add pl) ql
+ , bench "p + s (nontrivial mixed points)" $ nf (S.add pl) sl
+ , bench "s + r (nontrivial projective points)" $ nf (S.add sl) rl
+ ]
+ where
+ setup = pure (p, q, r, s)
+
+double :: Benchmark
+double = env setup $ \ ~(!pl, !rl) ->
+ bgroup "double" [
+ bench "2 p (double, trivial projective point)" $ nf (S.add pl) pl
+ , bench "2 r (double, nontrivial projective point)" $ nf (S.add rl) rl
+ ]
+ where
+ setup = pure (p, r)
mul :: Benchmark
mul = env setup $ \x ->
@@ -85,6 +97,16 @@ mul = env setup $ \x ->
setup = pure . parse_int256 $ decodeLenient
"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed"
+mul_vartime :: Benchmark
+mul_vartime = env setup $ \x ->
+ bgroup "mul_vartime" [
+ bench "2 G" $ nf (S.mul_vartime S._CURVE_G) 2
+ , bench "(2 ^ 255 - 19) G" $ nf (S.mul_vartime S._CURVE_G) x
+ ]
+ where
+ setup = pure . parse_int256 $ decodeLenient
+ "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed"
+
precompute :: Benchmark
precompute = bench "precompute" $ nfIO (pure S.precompute)
diff --git a/bench/Weight.hs b/bench/Weight.hs
@@ -2,12 +2,11 @@
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE OverloadedStrings #-}
--- XX need to make sure arguments aren't allocating in the benchmarks
-
module Main where
import qualified Data.ByteString as BS
import qualified Data.ByteString.Base16 as B16
+import Data.Maybe (fromJust)
import Data.Word.Wider (Wider(..))
import Control.DeepSeq
import qualified Crypto.Curve.Secp256k1 as S
@@ -28,22 +27,28 @@ parse_int bs = case S.parse_int256 bs of
Nothing -> error "bang"
Just v -> v
-big :: Wider
-big = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed
-
-- note that 'weigh' doesn't work properly in a repl
main :: IO ()
main = W.mainWith $ do
parse_int256
+ ge
add
+ double
mul
- mul_unsafe
mul_wnaf
derive_pub
schnorr
ecdsa
ecdh
+ge :: W.Weigh ()
+ge =
+ let !t = 2
+ !b = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed
+ in W.wgroup "ge" $ do
+ W.func' "small" S.ge t
+ W.func' "large" S.ge b
+
parse_int256 :: W.Weigh ()
parse_int256 =
let !a = BS.replicate 32 0x00
@@ -54,156 +59,104 @@ parse_int256 =
add :: W.Weigh ()
add =
- let !pl = p
- !rl = r
- !ql = q
- !sl = s
- in W.wgroup " add" $ do
- W.func "2 p (double, trivial projective point)" (S.add pl) pl
- W.func "2 r (double, nontrivial projective point)" (S.add rl) rl
- W.func "p + q (trivial projective points)" (S.add pl) ql
- W.func "p + s (nontrivial mixed points)" (S.add pl) sl
- W.func "s + r (nontrivial projective points)" (S.add sl) rl
+ let !p = fromJust . S.parse_point . decodeLenient $
+ "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
+ !r = fromJust . S.parse_point . decodeLenient $
+ "03a2113cf152585d96791a42cdd78782757fbfb5c6b2c11b59857eb4f7fda0b0e8"
+ !q = fromJust . S.parse_point . decodeLenient $
+ "02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9"
+ !s = fromJust . S.parse_point . decodeLenient $
+ "0306413898a49c93cccf3db6e9078c1b6a8e62568e4a4770e0d7d96792d1c580ad"
+ in W.wgroup "add" $ do
+ W.func' "p + q (trivial projective points)" (S.add p) q
+ W.func' "s + p (nontrivial mixed points)" (S.add s) p
+ W.func' "r + s (nontrivial projective points)" (S.add r) s
+
+double :: W.Weigh ()
+double =
+ let !p = fromJust . S.parse_point . decodeLenient $
+ "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
+ !r = fromJust . S.parse_point . decodeLenient $
+ "03a2113cf152585d96791a42cdd78782757fbfb5c6b2c11b59857eb4f7fda0b0e8"
+ in W.wgroup "double" $ do
+ W.func' "2 p (double, trivial projective point)" S.double p
+ W.func' "2 r (double, nontrivial projective point)" S.double r
mul :: W.Weigh ()
mul =
let !g = S._CURVE_G
!t = 2
- !bigl = big
+ !b = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed
in W.wgroup "mul" $ do
- W.func "2 G" (S.mul g) t
- W.func "(2 ^ 255 - 19) G" (S.mul g) bigl
-
-mul_unsafe :: W.Weigh ()
-mul_unsafe =
- let !g = S._CURVE_G
- !t = 2
- !bigl = big
- in W.wgroup "mul_unsafe" $ do
- W.func "2 G" (S.mul_unsafe g) t
- W.func "(2 ^ 255 - 19) G" (S.mul_unsafe g) bigl
+ W.func' "2 G" (S.mul g) t
+ W.func' "(2 ^ 255 - 19) G" (S.mul g) b
mul_wnaf :: W.Weigh ()
mul_wnaf =
let !t = 2
- !bigl = big
- !con = S._precompute 8
+ !b = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed
+ !con = S.precompute
in W.wgroup "mul_wnaf" $ do
- W.func "precompute" S._precompute (8 :: Int)
- W.func "2 G" (S.mul_wnaf con) t
- W.func "(2 ^ 255 - 19) G" (S.mul_wnaf con) bigl
+ W.func' "precompute" S._precompute (8 :: Int)
+ W.func' "2 G" (S.mul_wnaf con) t
+ W.func' "(2 ^ 255 - 19) G" (S.mul_wnaf con) b
derive_pub :: W.Weigh ()
derive_pub =
let !t = 2
- !bigl = big
- !con = S._precompute 8
+ !b = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed
+ !con = S.precompute
in W.wgroup "derive_pub" $ do
- W.func "sk = 2" S.derive_pub t
- W.func "sk = 2 ^ 255 - 19" S.derive_pub bigl
- W.func "wnaf, sk = 2" (S.derive_pub' con) t
- W.func "wnaf, sk = 2 ^ 255 - 19" (S.derive_pub' con) bigl
+ W.func' "sk = 2" S.derive_pub t
+ W.func' "sk = 2 ^ 255 - 19" S.derive_pub b
+ W.func' "wnaf, sk = 2" (S.derive_pub' con) t
+ W.func' "wnaf, sk = 2 ^ 255 - 19" (S.derive_pub' con) b
schnorr :: W.Weigh ()
schnorr =
let !t = 2
- !s_msgl = s_msg
- !s_auxl = s_aux
- !s_sigl = s_sig
- !s_pkl = s_pk
- !con = S._precompute 8
- !bigl = big
+ !b = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed
+ !con = S.precompute
+ !s_msg = decodeLenient
+ "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89"
+ !s_aux = decodeLenient
+ "0000000000000000000000000000000000000000000000000000000000000001"
+ !s_sig = decodeLenient
+ "6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A"
+ !(Just !s_pk) = S.parse_point . decodeLenient $
+ "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659"
in W.wgroup "schnorr" $ do
- W.func "sign_schnorr (small)" (S.sign_schnorr t s_msgl) s_auxl
- W.func "sign_schnorr (large)" (S.sign_schnorr bigl s_msgl) s_auxl
- W.func "sign_schnorr' (small)" (S.sign_schnorr' con t s_msgl) s_auxl
- W.func "sign_schnorr' (large)" (S.sign_schnorr' con big s_msgl) s_auxl
- W.func "verify_schnorr" (S.verify_schnorr s_msgl s_pkl) s_sigl
- W.func "verify_schnorr'" (S.verify_schnorr' con s_msgl s_pkl) s_sigl
+ W.func "sign_schnorr (small)" (S.sign_schnorr t s_msg) s_aux
+ W.func "sign_schnorr (large)" (S.sign_schnorr b s_msg) s_aux
+ W.func "sign_schnorr' (small)" (S.sign_schnorr' con t s_msg) s_aux
+ W.func "sign_schnorr' (large)" (S.sign_schnorr' con b s_msg) s_aux
+ W.func "verify_schnorr" (S.verify_schnorr s_msg s_pk) s_sig
+ W.func "verify_schnorr'" (S.verify_schnorr' con s_msg s_pk) s_sig
ecdsa :: W.Weigh ()
ecdsa =
let !t = 2
- !s_msgl = s_msg
- !con = S._precompute 8
- !bigl = big
- !msg = "i approve of this message"
- Just !pub = S.derive_pub bigl
- Just !sig = S.sign_ecdsa bigl s_msgl
+ !b = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed
+ !con = S.precompute
+ !msg = "i approve of this message"
+ !s_msg = decodeLenient
+ "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89"
+ !(Just !pub) = S.derive_pub b
+ !(Just !sig) = S.sign_ecdsa b s_msg
in W.wgroup "ecdsa" $ do
- W.func "sign_ecdsa (small)" (S.sign_ecdsa t) s_msgl
- W.func "sign_ecdsa (large)" (S.sign_ecdsa bigl) s_msgl
- W.func "sign_ecdsa' (small)" (S.sign_ecdsa' con t) s_msgl
- W.func "sign_ecdsa' (large)" (S.sign_ecdsa' con bigl) s_msgl
+ W.func "sign_ecdsa (small)" (S.sign_ecdsa t) s_msg
+ W.func "sign_ecdsa (large)" (S.sign_ecdsa b) s_msg
+ W.func "sign_ecdsa' (small)" (S.sign_ecdsa' con t) s_msg
+ W.func "sign_ecdsa' (large)" (S.sign_ecdsa' con b) s_msg
W.func "verify_ecdsa" (S.verify_ecdsa msg pub) sig
W.func "verify_ecdsa'" (S.verify_ecdsa' con msg pub) sig
ecdh :: W.Weigh ()
-ecdh = W.wgroup "ecdh" $ do
- W.func "ecdh (small)" (S.ecdh pub) 2
- W.func "ecdh (large)" (S.ecdh pub) b
- where
- !b = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed
- Just !pub = S.parse_point . decodeLenient $
- "bd02b9dfc8ef760708950bd972f2dc244893b61b6b46c3b19be1b2da7b034ac5"
-
-s_sk :: Wider
-s_sk = parse_int . decodeLenient $
- "B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF"
-
-s_sig :: BS.ByteString
-s_sig = decodeLenient "6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A"
-
-s_pk_raw :: BS.ByteString
-s_pk_raw = decodeLenient
- "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659"
-
-s_pk :: S.Projective
-s_pk = case S.parse_point s_pk_raw of
- Nothing -> error "bang"
- Just !pt -> pt
-
-s_msg :: BS.ByteString
-s_msg = decodeLenient
- "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89"
-
-s_aux :: BS.ByteString
-s_aux = decodeLenient
- "0000000000000000000000000000000000000000000000000000000000000001"
-
-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
- "0306413898a49c93cccf3db6e9078c1b6a8e62568e4a4770e0d7d96792d1c580ad"
-
-s :: S.Projective
-s = case S.parse_point s_bs of
- Nothing -> error "bang"
- Just !pt -> pt
+ecdh =
+ let !b = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed
+ !(Just !pub) = S.parse_point . decodeLenient $
+ "bd02b9dfc8ef760708950bd972f2dc244893b61b6b46c3b19be1b2da7b034ac5"
+ in W.wgroup "ecdh" $ do
+ W.func "ecdh (small)" (S.ecdh pub) 2
+ W.func "ecdh (large)" (S.ecdh pub) b
diff --git a/flake.lock b/flake.lock
@@ -184,11 +184,11 @@
]
},
"locked": {
- "lastModified": 1766269174,
- "narHash": "sha256-vgg86sfxwxc1dmeajNCPvlzZl24+aNFKxCX3+DdAXfA=",
+ "lastModified": 1766351184,
+ "narHash": "sha256-+fXHSabk0hjARotjB65uPRktAlvbH02orI/SNyyBub0=",
"ref": "master",
- "rev": "a9d4855bedf548913fcfe1e4eaf6e5dca540f524",
- "revCount": 249,
+ "rev": "497ffce0197e28b28e7534a278d52002accfd5b9",
+ "revCount": 251,
"type": "git",
"url": "git://git.ppad.tech/fixed.git"
},
diff --git a/lib/Crypto/Curve/Secp256k1.hs b/lib/Crypto/Curve/Secp256k1.hs
@@ -27,7 +27,6 @@ module Crypto.Curve.Secp256k1 (
-- * Field and group parameters
_CURVE_Q
, _CURVE_P
- , modQ
-- * secp256k1 points
, Pub
@@ -35,6 +34,8 @@ module Crypto.Curve.Secp256k1 (
, derive_pub'
, _CURVE_G
, _CURVE_ZERO
+ , ge
+ , fe
-- * Parsing
, parse_int256
@@ -72,9 +73,11 @@ module Crypto.Curve.Secp256k1 (
-- Elliptic curve group operations
, neg
, add
+ , add_mixed
+ , add_proj
, double
, mul
- , mul_unsafe
+ , mul_vartime
, mul_wnaf
-- Coordinate systems and transformations
@@ -91,21 +94,21 @@ module Crypto.Curve.Secp256k1 (
, roll32
, unsafe_roll32
, unroll32
+ , select_proj
) where
import Control.Monad (guard)
import Control.Monad.ST
import qualified Crypto.DRBG.HMAC as DRBG
import qualified Crypto.Hash.SHA256 as SHA256
-import Data.Bits ((.&.))
import qualified Data.Bits as B
import qualified Data.ByteString as BS
import qualified Data.ByteString.Internal as BI
import qualified Data.ByteString.Unsafe as BU
import qualified Data.Choice as CT
import qualified Data.Maybe as M
-import qualified Data.Primitive.Array as A
-import Data.STRef
+import Data.Primitive.ByteArray (ByteArray(..), MutableByteArray(..))
+import qualified Data.Primitive.ByteArray as BA
import Data.Word (Word8)
import Data.Word.Limb (Limb(..))
import qualified Data.Word.Limb as L
@@ -114,25 +117,36 @@ import qualified Data.Word.Wider as W
import qualified Foreign.Storable as Storable (pokeByteOff)
import qualified GHC.Exts as Exts
import GHC.Generics
-import qualified GHC.Int (Int(..))
import qualified GHC.Word (Word(..), Word8(..))
import qualified Numeric.Montgomery.Secp256k1.Curve as C
import qualified Numeric.Montgomery.Secp256k1.Scalar as S
import Prelude hiding (sqrt)
+-- convenience synonyms -------------------------------------------------------
+
+-- Unboxed Wider/Montgomery synonym.
+type Limb4 = (# Limb, Limb, Limb, Limb #)
+
+-- Unboxed Projective synonym.
+type Proj = (# Limb4, Limb4, Limb4 #)
+
+pattern Zero :: Wider
+pattern Zero = Wider Z
+
+pattern Z :: Limb4
+pattern Z = (# Limb 0##, Limb 0##, Limb 0##, Limb 0## #)
+
+pattern P :: Limb4 -> Limb4 -> Limb4 -> Projective
+pattern P x y z =
+ Projective (C.Montgomery x) (C.Montgomery y) (C.Montgomery z)
+{-# COMPLETE P #-}
+
-- utilities ------------------------------------------------------------------
fi :: (Integral a, Num b) => a -> b
fi = fromIntegral
{-# INLINE fi #-}
--- dumb strict pair
-data Pair a b = Pair !a !b
-
--- convenience pattern
-pattern Zero :: Wider
-pattern Zero = Wider (# Limb 0##, Limb 0##, Limb 0##, Limb 0## #)
-
-- convert a Word8 to a Limb
limb :: Word8 -> Limb
limb (GHC.Word.W8# (Exts.word8ToWord# -> w)) = Limb w
@@ -155,10 +169,6 @@ word8_to_wider :: Word8 -> Wider
word8_to_wider w = Wider (# limb w, Limb 0##, Limb 0##, Limb 0## #)
{-# INLINABLE word8_to_wider #-}
-wider_to_int :: Wider -> Int
-wider_to_int (Wider (# Limb l, _, _, _ #)) = GHC.Int.I# (Exts.word2Int# l)
-{-# INLINABLE wider_to_int #-}
-
-- unsafely extract the first 64-bit word from a big-endian-encoded bytestring
unsafe_word0 :: BS.ByteString -> Limb
unsafe_word0 bs =
@@ -281,6 +291,7 @@ modQ = S.from . S.to
-- bytewise xor
xor :: BS.ByteString -> BS.ByteString -> BS.ByteString
xor = BS.packZipWith B.xor
+{-# INLINABLE xor #-}
-- constants ------------------------------------------------------------------
@@ -327,7 +338,7 @@ fe n = n > 0 && n < _CURVE_P
-- Is group element?
ge :: Wider -> Bool
-ge n = n > 0 && n < _CURVE_Q
+ge (Wider n) = CT.decide (ge# n)
{-# INLINE ge #-}
-- curve points ---------------------------------------------------------------
@@ -357,12 +368,9 @@ type Pub = Projective
-- Convert to affine coordinates.
affine :: Projective -> Affine
-affine = \case
- Projective 0 1 0 -> Affine 0 0
- Projective x y 1 -> Affine x y
- Projective x y z ->
- let !iz = C.inv z
- in Affine (x * iz) (y * iz)
+affine (Projective x y z) =
+ let !iz = C.inv z
+ in Affine (x * iz) (y * iz)
{-# INLINABLE affine #-}
-- Convert to projective coordinates.
@@ -373,13 +381,15 @@ projective = \case
-- | secp256k1 generator point.
_CURVE_G :: Projective
-_CURVE_G = Projective x y C.one where
+_CURVE_G = Projective x y z where
!x = C.Montgomery
(# Limb 15507633332195041431##, Limb 2530505477788034779##
, Limb 10925531211367256732##, Limb 11061375339145502536## #)
!y = C.Montgomery
(# Limb 12780836216951778274##, Limb 10231155108014310989##
, Limb 8121878653926228278##, Limb 14933801261141951190## #)
+ !z = C.Montgomery
+ (# Limb 0x1000003D1##, Limb 0##, Limb 0##, Limb 0## #)
-- | secp256k1 zero point, point at infinity, or monoidal identity.
_CURVE_ZERO :: Projective
@@ -424,215 +434,261 @@ lift_vartime x = do
even_y_vartime :: Projective -> Projective
even_y_vartime p = case affine p of
Affine _ (C.retr -> y)
- | CT.decide (W.odd y) -> neg p -- XX
+ | CT.decide (W.odd y) -> neg p
| otherwise -> p
+-- Constant-time selection of Projective points.
+select_proj :: Projective -> Projective -> CT.Choice -> Projective
+select_proj (P ax ay az) (P bx by bz) c =
+ P (W.select# ax bx c) (W.select# ay by c) (W.select# az bz c)
+{-# INLINE select_proj #-}
+
+-- unboxed internals ----------------------------------------------------------
+
+-- algo 7, renes et al, 2015
+add_proj# :: Proj -> Proj -> Proj
+add_proj# (# x1, y1, z1 #) (# x2, y2, z2 #) =
+ let !(C.Montgomery b3) = _CURVE_Bm3
+ !t0a = C.mul# x1 x2
+ !t1a = C.mul# y1 y2
+ !t2a = C.mul# z1 z2
+ !t3a = C.add# x1 y1
+ !t4a = C.add# x2 y2
+ !t3b = C.mul# t3a t4a
+ !t4b = C.add# t0a t1a
+ !t3c = C.sub# t3b t4b
+ !t4c = C.add# y1 z1
+ !x3a = C.add# y2 z2
+ !t4d = C.mul# t4c x3a
+ !x3b = C.add# t1a t2a
+ !t4e = C.sub# t4d x3b
+ !x3c = C.add# x1 z1
+ !y3a = C.add# x2 z2
+ !x3d = C.mul# x3c y3a
+ !y3b = C.add# t0a t2a
+ !y3c = C.sub# x3d y3b
+ !x3e = C.add# t0a t0a
+ !t0b = C.add# x3e t0a
+ !t2b = C.mul# b3 t2a
+ !z3a = C.add# t1a t2b
+ !t1b = C.sub# t1a t2b
+ !y3d = C.mul# b3 y3c
+ !x3f = C.mul# t4e y3d
+ !t2c = C.mul# t3c t1b
+ !x3g = C.sub# t2c x3f
+ !y3e = C.mul# y3d t0b
+ !t1c = C.mul# t1b z3a
+ !y3f = C.add# t1c y3e
+ !t0c = C.mul# t0b t3c
+ !z3b = C.mul# z3a t4e
+ !z3c = C.add# z3b t0c
+ in (# x3g, y3f, z3c #)
+{-# INLINE add_proj# #-}
+
+-- algo 8, renes et al, 2015
+add_mixed# :: Proj -> Proj -> Proj
+add_mixed# (# x1, y1, z1 #) (# x2, y2, _z2 #) =
+ let !(C.Montgomery b3) = _CURVE_Bm3
+ !t0a = C.mul# x1 x2
+ !t1a = C.mul# y1 y2
+ !t3a = C.add# x2 y2
+ !t4a = C.add# x1 y1
+ !t3b = C.mul# t3a t4a
+ !t4b = C.add# t0a t1a
+ !t3c = C.sub# t3b t4b
+ !t4c = C.mul# y2 z1
+ !t4d = C.add# t4c y1
+ !y3a = C.mul# x2 z1
+ !y3b = C.add# y3a x1
+ !x3a = C.add# t0a t0a
+ !t0b = C.add# x3a t0a
+ !t2a = C.mul# b3 z1
+ !z3a = C.add# t1a t2a
+ !t1b = C.sub# t1a t2a
+ !y3c = C.mul# b3 y3b
+ !x3b = C.mul# t4d y3c
+ !t2b = C.mul# t3c t1b
+ !x3c = C.sub# t2b x3b
+ !y3d = C.mul# y3c t0b
+ !t1c = C.mul# t1b z3a
+ !y3e = C.add# t1c y3d
+ !t0c = C.mul# t0b t3c
+ !z3b = C.mul# z3a t4d
+ !z3c = C.add# z3b t0c
+ in (# x3c, y3e, z3c #)
+{-# INLINE add_mixed# #-}
+
+-- algo 9, renes et al, 2015
+double# :: Proj -> Proj
+double# (# x, y, z #) =
+ let !(C.Montgomery b3) = _CURVE_Bm3
+ !t0 = C.sqr# y
+ !z3a = C.add# t0 t0
+ !z3b = C.add# z3a z3a
+ !z3c = C.add# z3b z3b
+ !t1 = C.mul# y z
+ !t2a = C.sqr# z
+ !t2b = C.mul# b3 t2a
+ !x3a = C.mul# t2b z3c
+ !y3a = C.add# t0 t2b
+ !z3d = C.mul# t1 z3c
+ !t1b = C.add# t2b t2b
+ !t2c = C.add# t1b t2b
+ !t0b = C.sub# t0 t2c
+ !y3b = C.mul# t0b y3a
+ !y3c = C.add# x3a y3b
+ !t1c = C.mul# x y
+ !x3b = C.mul# t0b t1c
+ !x3c = C.add# x3b x3b
+ in (# x3c, y3c, z3d #)
+{-# INLINE double# #-}
+
+select_proj# :: Proj -> Proj -> CT.Choice -> Proj
+select_proj# (# ax, ay, az #) (# bx, by, bz #) c =
+ (# W.select# ax bx c, W.select# ay by c, W.select# az bz c #)
+{-# INLINE select_proj# #-}
+
+neg# :: Proj -> Proj
+neg# (# x, y, z #) = (# x, C.neg# y, z #)
+{-# INLINE neg# #-}
+
+mul# :: Proj -> Limb4 -> (# () | Proj #)
+mul# (# px, py, pz #) s
+ | CT.decide (CT.not# (ge# s)) = (# () | #)
+ | otherwise =
+ let !(P gx gy gz) = _CURVE_G
+ !(C.Montgomery o) = C.one
+ in loop (0 :: Int) (# Z, o, Z #) (# gx, gy, gz #) (# px, py, pz #) s
+ where
+ loop !j !a !f !d !_SECRET
+ | j == _CURVE_Q_BITS = (# | a #)
+ | otherwise =
+ let !nd = double# d
+ !(# nm, lsb_set #) = W.shr1_c# _SECRET
+ !nacc = select_proj# a (add_proj# a d) lsb_set
+ !nf = select_proj# (add_proj# f d) f lsb_set
+ in loop (succ j) nacc nf nd nm
+{-# INLINE mul# #-}
+
+ge# :: Limb4 -> CT.Choice
+ge# n =
+ let !(Wider q) = _CURVE_Q
+ in CT.and# (W.gt# n Z) (W.lt# n q)
+{-# INLINE ge# #-}
+
+mul_wnaf# :: ByteArray -> Int -> Limb4 -> (# () | Proj #)
+mul_wnaf# ctxArray ctxW ls
+ | CT.decide (CT.not# (ge# ls)) = (# () | #)
+ | otherwise =
+ let !(P zx zy zz) = _CURVE_ZERO
+ !(P gx gy gz) = _CURVE_G
+ in (# | loop 0 (# zx, zy, zz #) (# gx, gy, gz #) ls #)
+ where
+ !one = (# Limb 1##, Limb 0##, Limb 0##, Limb 0## #)
+ !wins = fi (256 `quot` ctxW + 1)
+ !size@(GHC.Word.W# s) = 2 ^ (ctxW - 1)
+ !(GHC.Word.W# mask) = 2 ^ ctxW - 1
+ !(GHC.Word.W# texW) = fi ctxW
+ !(GHC.Word.W# mnum) = 2 ^ ctxW
+
+ loop !j@(GHC.Word.W# w) !acc !f !n@(# Limb lo, _, _, _ #)
+ | j == wins = acc
+ | otherwise =
+ let !(GHC.Word.W# off0) = j * size
+ !b0 = Exts.and# lo mask
+ !bor = CT.from_word_gt# b0 s
+
+ !(# n0, _ #) = W.shr_limb# n (Exts.word2Int# texW)
+ !n0_plus_1 = W.add_w# n0 one
+ !n1 = W.select# n0 n0_plus_1 bor
+
+ !abs_b = CT.select_word# b0 (Exts.minusWord# mnum b0) bor
+ !is_zero = CT.from_word_eq# b0 0##
+ !c0 = CT.from_word# (Exts.and# w 1##)
+ !off_nz = Exts.minusWord# (Exts.plusWord# off0 abs_b) 1##
+ !off = CT.select_word# off0 off_nz (CT.not# is_zero)
+
+ !pr = index_proj# ctxArray (Exts.word2Int# off)
+ !neg_pr = neg# pr
+ !pt_zero = select_proj# pr neg_pr c0
+ !pt_nonzero = select_proj# pr neg_pr bor
+
+ !f_added = add_proj# f pt_zero
+ !acc_added = add_proj# acc pt_nonzero
+ !nacc = select_proj# acc_added acc is_zero
+ !nf = select_proj# f f_added is_zero
+ in loop (succ j) nacc nf n1
+{-# INLINE mul_wnaf# #-}
+
+-- retrieve a point (as an unboxed tuple) from a context array
+index_proj# :: ByteArray -> Exts.Int# -> Proj
+index_proj# (ByteArray arr#) i# =
+ let !base# = i# Exts.*# 12#
+ !x = (# Limb (Exts.indexWordArray# arr# base#)
+ , Limb (Exts.indexWordArray# arr# (base# Exts.+# 01#))
+ , Limb (Exts.indexWordArray# arr# (base# Exts.+# 02#))
+ , Limb (Exts.indexWordArray# arr# (base# Exts.+# 03#)) #)
+ !y = (# Limb (Exts.indexWordArray# arr# (base# Exts.+# 04#))
+ , Limb (Exts.indexWordArray# arr# (base# Exts.+# 05#))
+ , Limb (Exts.indexWordArray# arr# (base# Exts.+# 06#))
+ , Limb (Exts.indexWordArray# arr# (base# Exts.+# 07#)) #)
+ !z = (# Limb (Exts.indexWordArray# arr# (base# Exts.+# 08#))
+ , Limb (Exts.indexWordArray# arr# (base# Exts.+# 09#))
+ , Limb (Exts.indexWordArray# arr# (base# Exts.+# 10#))
+ , Limb (Exts.indexWordArray# arr# (base# Exts.+# 11#)) #)
+ in (# x, y, z #)
+{-# INLINE index_proj# #-}
+
-- ec arithmetic --------------------------------------------------------------
-- Negate secp256k1 point.
neg :: Projective -> Projective
-neg (Projective x y z) = Projective x (negate y) z
-
--- Constant-time selection of Projective points.
-select_proj :: Projective -> Projective -> CT.Choice -> Projective
-select_proj (Projective ax ay az) (Projective bx by bz) c =
- Projective (C.select ax bx c) (C.select ay by c) (C.select az bz c)
-{-# INLINE select_proj #-}
+neg (P x y z) =
+ let !(# px, py, pz #) = neg# (# x, y, z #)
+ in P px py pz
+{-# INLINABLE neg #-}
-- Elliptic curve addition on secp256k1.
add :: Projective -> Projective -> Projective
-add p q@(Projective _ _ z)
- | z == 1 = add_mixed p q -- algo 8
- | otherwise = add_proj p q -- algo 7
+add p q = add_proj p q
+{-# INLINABLE add #-}
-- algo 7, "complete addition formulas for prime order elliptic curves,"
-- renes et al, 2015
--
-- https://eprint.iacr.org/2015/1060.pdf
add_proj :: Projective -> Projective -> Projective
-add_proj (Projective x1 y1 z1) (Projective x2 y2 z2) = runST $ do
- x3 <- newSTRef 0
- y3 <- newSTRef 0
- z3 <- newSTRef 0
- t0 <- newSTRef (x1 * x2) -- 1
- t1 <- newSTRef (y1 * y2)
- t2 <- newSTRef (z1 * z2)
- t3 <- newSTRef (x1 + y1) -- 4
- t4 <- newSTRef (x2 + y2)
- readSTRef t4 >>= \r4 ->
- modifySTRef' t3 (\r3 -> r3 * r4)
- readSTRef t0 >>= \r0 ->
- readSTRef t1 >>= \r1 ->
- writeSTRef t4 (r0 + r1)
- readSTRef t4 >>= \r4 ->
- modifySTRef' t3 (\r3 -> r3 - r4) -- 8
- writeSTRef t4 (y1 + z1)
- writeSTRef x3 (y2 + z2)
- readSTRef x3 >>= \rx3 ->
- modifySTRef' t4 (\r4 -> r4 * rx3)
- readSTRef t1 >>= \r1 ->
- readSTRef t2 >>= \r2 ->
- writeSTRef x3 (r1 + r2) -- 12
- readSTRef x3 >>= \rx3 ->
- modifySTRef' t4 (\r4 -> r4 - rx3)
- writeSTRef x3 (x1 + z1)
- writeSTRef y3 (x2 + z2)
- readSTRef y3 >>= \ry3 ->
- modifySTRef' x3 (\rx3 -> rx3 * ry3) -- 16
- readSTRef t0 >>= \r0 ->
- readSTRef t2 >>= \r2 ->
- writeSTRef y3 (r0 + r2)
- readSTRef x3 >>= \rx3 ->
- modifySTRef' y3 (\ry3 -> rx3 - ry3)
- readSTRef t0 >>= \r0 ->
- writeSTRef x3 (r0 + r0)
- readSTRef x3 >>= \rx3 ->
- modifySTRef t0 (\r0 -> rx3 + r0) -- 20
- modifySTRef' t2 (\r2 -> _CURVE_Bm3 * r2)
- readSTRef t1 >>= \r1 ->
- readSTRef t2 >>= \r2 ->
- writeSTRef z3 (r1 + r2)
- readSTRef t2 >>= \r2 ->
- modifySTRef' t1 (\r1 -> r1 - r2)
- modifySTRef' y3 (\ry3 -> _CURVE_Bm3 * ry3) -- 24
- readSTRef t4 >>= \r4 ->
- readSTRef y3 >>= \ry3 ->
- writeSTRef x3 (r4 * ry3)
- readSTRef t3 >>= \r3 ->
- readSTRef t1 >>= \r1 ->
- writeSTRef t2 (r3 * r1)
- readSTRef t2 >>= \r2 ->
- modifySTRef' x3 (\rx3 -> r2 - rx3)
- readSTRef t0 >>= \r0 ->
- modifySTRef' y3 (\ry3 -> ry3 * r0) -- 28
- readSTRef z3 >>= \rz3 ->
- modifySTRef' t1 (\r1 -> r1 * rz3)
- readSTRef t1 >>= \r1 ->
- modifySTRef' y3 (\ry3 -> r1 + ry3)
- readSTRef t3 >>= \r3 ->
- modifySTRef' t0 (\r0 -> r0 * r3)
- readSTRef t4 >>= \r4 ->
- modifySTRef' z3 (\rz3 -> rz3 * r4) -- 32
- readSTRef t0 >>= \r0 ->
- modifySTRef' z3 (\rz3 -> rz3 + r0)
- Projective <$> readSTRef x3 <*> readSTRef y3 <*> readSTRef z3
+add_proj (P ax ay az) (P bx by bz) =
+ let !(# x, y, z #) = add_proj# (# ax, ay, az #) (# bx, by, bz #)
+ in P x y z
+{-# INLINABLE add_proj #-}
-- algo 8, renes et al, 2015
add_mixed :: Projective -> Projective -> Projective
-add_mixed (Projective x1 y1 z1) (Projective x2 y2 z2)
- | z2 /= 1 = error "ppad-secp256k1 (add_mixed): internal error"
- | otherwise = runST $ do
- x3 <- newSTRef 0
- y3 <- newSTRef 0
- z3 <- newSTRef 0
- t0 <- newSTRef (x1 * x2) -- 1
- t1 <- newSTRef (y1 * y2)
- t3 <- newSTRef (x2 + y2)
- t4 <- newSTRef (x1 + y1) -- 4
- readSTRef t4 >>= \r4 ->
- modifySTRef' t3 (\r3 -> r3 * r4)
- readSTRef t0 >>= \r0 ->
- readSTRef t1 >>= \r1 ->
- writeSTRef t4 (r0 + r1)
- readSTRef t4 >>= \r4 ->
- modifySTRef' t3 (\r3 -> r3 - r4) -- 7
- writeSTRef t4 (y2 * z1)
- modifySTRef' t4 (\r4 -> r4 + y1)
- writeSTRef y3 (x2 * z1) -- 10
- modifySTRef' y3 (\ry3 -> ry3 + x1)
- readSTRef t0 >>= \r0 ->
- writeSTRef x3 (r0 + r0)
- readSTRef x3 >>= \rx3 ->
- modifySTRef' t0 (\r0 -> rx3 + r0) -- 13
- t2 <- newSTRef (_CURVE_Bm3 * z1)
- readSTRef t1 >>= \r1 ->
- readSTRef t2 >>= \r2 ->
- writeSTRef z3 (r1 + r2)
- readSTRef t2 >>= \r2 ->
- modifySTRef' t1 (\r1 -> r1 - r2) -- 16
- modifySTRef' y3 (\ry3 -> _CURVE_Bm3 * ry3)
- readSTRef t4 >>= \r4 ->
- readSTRef y3 >>= \ry3 ->
- writeSTRef x3 (r4 * ry3)
- readSTRef t3 >>= \r3 ->
- readSTRef t1 >>= \r1 ->
- writeSTRef t2 (r3 * r1) -- 19
- readSTRef t2 >>= \r2 ->
- modifySTRef' x3 (\rx3 -> r2 - rx3)
- readSTRef t0 >>= \r0 ->
- modifySTRef' y3 (\ry3 -> ry3 * r0)
- readSTRef z3 >>= \rz3 ->
- modifySTRef' t1 (\r1 -> r1 * rz3) -- 22
- readSTRef t1 >>= \r1 ->
- modifySTRef' y3 (\ry3 -> r1 + ry3)
- readSTRef t3 >>= \r3 ->
- modifySTRef' t0 (\r0 -> r0 * r3)
- readSTRef t4 >>= \r4 ->
- modifySTRef' z3 (\rz3 -> rz3 * r4) -- 25
- readSTRef t0 >>= \r0 ->
- modifySTRef' z3 (\rz3 -> rz3 + r0)
- Projective <$> readSTRef x3 <*> readSTRef y3 <*> readSTRef z3
+add_mixed (P ax ay az) (P bx by bz) =
+ let !(# x, y, z #) = add_mixed# (# ax, ay, az #) (# bx, by, bz #)
+ in P x y z
+{-# INLINABLE add_mixed #-}
-- algo 9, renes et al, 2015
double :: Projective -> Projective
-double (Projective x y z) = runST $ do
- x3 <- newSTRef 0
- y3 <- newSTRef 0
- z3 <- newSTRef 0
- t0 <- newSTRef (y * y) -- 1
- readSTRef t0 >>= \r0 ->
- writeSTRef z3 (r0 + r0)
- modifySTRef' z3 (\rz3 -> rz3 + rz3)
- modifySTRef' z3 (\rz3 -> rz3 + rz3) -- 4
- t1 <- newSTRef (y * z)
- t2 <- newSTRef (z * z)
- modifySTRef t2 (\r2 -> _CURVE_Bm3 * r2) -- 7
- readSTRef z3 >>= \rz3 ->
- readSTRef t2 >>= \r2 ->
- writeSTRef x3 (r2 * rz3)
- readSTRef t0 >>= \r0 ->
- readSTRef t2 >>= \r2 ->
- writeSTRef y3 (r0 + r2)
- readSTRef t1 >>= \r1 ->
- modifySTRef' z3 (\rz3 -> r1 * rz3) -- 10
- readSTRef t2 >>= \r2 ->
- writeSTRef t1 (r2 + r2)
- readSTRef t1 >>= \r1 ->
- modifySTRef' t2 (\r2 -> r1 + r2)
- readSTRef t2 >>= \r2 ->
- modifySTRef' t0 (\r0 -> r0 - r2) -- 13
- readSTRef t0 >>= \r0 ->
- modifySTRef' y3 (\ry3 -> r0 * ry3)
- readSTRef x3 >>= \rx3 ->
- modifySTRef' y3 (\ry3 -> rx3 + ry3)
- writeSTRef t1 (x * y) -- 16
- readSTRef t0 >>= \r0 ->
- readSTRef t1 >>= \r1 ->
- writeSTRef x3 (r0 * r1)
- modifySTRef' x3 (\rx3 -> rx3 + rx3)
- Projective <$> readSTRef x3 <*> readSTRef y3 <*> readSTRef z3
+double (Projective (C.Montgomery ax) (C.Montgomery ay) (C.Montgomery az)) =
+ let !(# x, y, z #) = double# (# ax, ay, az #)
+ in P x y z
+{-# INLINABLE double #-}
-- Timing-safe scalar multiplication of secp256k1 points.
mul :: Projective -> Wider -> Maybe Projective
-mul p sec = do
- guard (ge sec)
- pure $! loop (0 :: Int) _CURVE_ZERO _CURVE_G p sec
- where
- loop !j !acc !f !d !_SECRET
- | j == _CURVE_Q_BITS = acc
- | otherwise =
- let !nd = double d
- !(# nm, lsb_set #) = W.shr1_c _SECRET
- !nacc = select_proj acc (add acc d) lsb_set
- !nf = select_proj (add f d) f lsb_set
- in loop (succ j) nacc nf nd nm
-{-# INLINE mul #-}
+mul (P x y z) (Wider s) = case mul# (# x, y, z #) s of
+ (# () | #) -> Nothing
+ (# | (# px, py, pz #) #) -> Just $! P px py pz
+{-# INLINABLE mul #-}
-- Timing-unsafe scalar multiplication of secp256k1 points.
--
-- Don't use this function if the scalar could potentially be a secret.
-mul_unsafe :: Projective -> Wider -> Maybe Projective
-mul_unsafe p = \case
+mul_vartime :: Projective -> Wider -> Maybe Projective
+mul_vartime p = \case
Zero -> pure _CURVE_ZERO
n | not (ge n) -> Nothing
| otherwise -> pure $! loop _CURVE_ZERO p n
@@ -641,14 +697,14 @@ mul_unsafe p = \case
Zero -> r
m ->
let !nd = double d
- !(# !nm, !lsb_set #) = W.shr1_c m
- !nr = if CT.decide lsb_set then add r d else r -- XX
+ !(# nm, lsb_set #) = W.shr1_c m
+ !nr = if CT.decide lsb_set 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)
+ , ctxArray :: {-# UNPACK #-} !ByteArray
} deriving Generic
instance Show Context where
@@ -666,66 +722,79 @@ instance Show Context where
precompute :: Context
precompute = _precompute 8
--- translation of noble-secp256k1's 'precompute'
+-- This is a highly-optimized version of a function originally
+-- translated from noble-secp256k1's "precompute". Points are stored in
+-- a ByteArray by arranging each limb into slices of 12 consecutive
+-- slots (each Projective point consists of three Montgomery values,
+-- each of which consists of four limbs, summing to twelve limbs in
+-- total).
+--
+-- Each point takes 96 bytes to store in this fashion, so the total size of
+-- the ByteArray is (size * 96) bytes.
_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)
+ -- construct the context array
+ ctxArray = runST $ do
+ marr <- BA.newByteArray (size * 96)
+ loop_w marr _CURVE_G 0
+ BA.unsafeFreezeByteArray marr
+
+ -- write a point into the i^th 12-slot slice in the array
+ write :: MutableByteArray s -> Int -> Projective -> ST s ()
+ write marr i
+ (P (# Limb x0, Limb x1, Limb x2, Limb x3 #)
+ (# Limb y0, Limb y1, Limb y2, Limb y3 #)
+ (# Limb z0, Limb z1, Limb z2, Limb z3 #)) = do
+ let !base = i * 12
+ BA.writeByteArray marr (base + 00) (GHC.Word.W# x0)
+ BA.writeByteArray marr (base + 01) (GHC.Word.W# x1)
+ BA.writeByteArray marr (base + 02) (GHC.Word.W# x2)
+ BA.writeByteArray marr (base + 03) (GHC.Word.W# x3)
+ BA.writeByteArray marr (base + 04) (GHC.Word.W# y0)
+ BA.writeByteArray marr (base + 05) (GHC.Word.W# y1)
+ BA.writeByteArray marr (base + 06) (GHC.Word.W# y2)
+ BA.writeByteArray marr (base + 07) (GHC.Word.W# y3)
+ BA.writeByteArray marr (base + 08) (GHC.Word.W# z0)
+ BA.writeByteArray marr (base + 09) (GHC.Word.W# z1)
+ BA.writeByteArray marr (base + 10) (GHC.Word.W# z2)
+ BA.writeByteArray marr (base + 11) (GHC.Word.W# z3)
+
+ -- loop over windows
+ loop_w :: MutableByteArray s -> Projective -> Int -> ST s ()
+ loop_w !marr !p !w
+ | w == ws = pure ()
+ | otherwise = do
+ nb <- loop_j marr p p (w * capJ) 0
+ let np = double nb
+ loop_w marr np (succ w)
+
+ -- loop within windows
+ loop_j
+ :: MutableByteArray s
+ -> Projective
+ -> Projective
+ -> Int
+ -> Int
+ -> ST s Projective
+ loop_j !marr !p !b !idx !j = do
+ write marr idx b
+ if j == capJ - 1
+ then pure b
+ else do
+ let !nb = add b p
+ loop_j marr p nb (succ idx) (succ j)
-- Timing-safe wNAF (w-ary non-adjacent form) scalar multiplication of
-- secp256k1 points.
mul_wnaf :: Context -> Wider -> 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 * wsize
-
- !b0 = wider_to_int n .&. mask
- !n0 = n `W.shr_limb` ctxW
-
- !(Pair b1 n1) | b0 > wsize = Pair (b0 - mnum) (n0 + 1)
- | otherwise = Pair b0 n0
-
- !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 #-}
+mul_wnaf Context {..} (Wider s) = case mul_wnaf# ctxArray ctxW s of
+ (# () | #) -> Nothing
+ (# | (# px, py, pz #) #) -> Just $! P px py pz
+{-# INLINABLE mul_wnaf #-}
-- | Derive a public key (i.e., a secp256k1 point) from the provided
-- secret.
@@ -761,6 +830,7 @@ parse_int256 :: BS.ByteString -> Maybe Wider
parse_int256 bs = do
guard (BS.length bs == 32)
pure $! unsafe_roll32 bs
+{-# INLINABLE parse_int256 #-}
-- | Parse compressed secp256k1 point (33 bytes), uncompressed point (65
-- bytes), or BIP0340-style point (32 bytes).
@@ -796,8 +866,7 @@ _parse_compressed h (unsafe_roll32 -> x)
| otherwise = do
let !mx = C.to x
!my <- C.sqrt (weierstrass mx)
- let !(W.Wider (# Limb w, _, _, _ #)) = C.retr my
- !yodd = B.testBit (GHC.Word.W# w) 0
+ let !yodd = CT.decide (W.odd (C.retr my))
!hodd = B.testBit h 0
pure $!
if hodd /= yodd
@@ -919,9 +988,8 @@ _sign_schnorr
_sign_schnorr _mul _SECRET m a = do
p <- _mul _SECRET
let Affine (C.retr -> x_p) (C.retr -> y_p) = affine p
- s = S.to _SECRET
- d | CT.decide (W.odd y_p) = negate s -- XX
- | otherwise = s
+ s = S.to _SECRET
+ d = S.select s (negate s) (W.odd y_p)
bytes_d = unroll32 (S.retr d)
bytes_p = unroll32 x_p
t = xor bytes_d (hash_aux a)
@@ -930,8 +998,7 @@ _sign_schnorr _mul _SECRET m a = do
guard (k' /= 0) -- negligible probability
pt <- _mul (S.retr k')
let Affine (C.retr -> x_r) (C.retr -> y_r) = affine pt
- k | CT.decide (W.odd y_r) = negate k' -- XX
- | otherwise = k'
+ k = S.select k' (negate k') (W.odd y_r)
bytes_r = unroll32 x_r
rand' = hash_challenge (bytes_r <> bytes_p <> m)
e = S.to (unsafe_roll32 rand')
@@ -956,7 +1023,7 @@ verify_schnorr
-> Pub -- ^ public key
-> BS.ByteString -- ^ 64-byte Schnorr signature
-> Bool
-verify_schnorr = _verify_schnorr (mul_unsafe _CURVE_G)
+verify_schnorr = _verify_schnorr (mul_vartime _CURVE_G)
-- | The same as 'verify_schnorr', except uses a 'Context' to optimise
-- internal calculations.
@@ -993,7 +1060,7 @@ _verify_schnorr _mul m p sig
e = modQ . unsafe_roll32 $
hash_challenge (unroll32 r <> unroll32 x_P <> m)
pt0 <- _mul s
- pt1 <- mul_unsafe capP e
+ pt1 <- mul_vartime capP e
let dif = add pt0 (neg pt1)
guard (dif /= _CURVE_ZERO)
let Affine (C.from -> x_R) (C.from -> y_R) = affine dif
@@ -1066,9 +1133,7 @@ data HashFlag =
-- Convert an ECDSA signature to low-S form.
low :: ECDSA -> ECDSA
-low (ECDSA r s) = ECDSA r ms where
- ms | s > _CURVE_QH = _CURVE_Q - s
- | otherwise = s
+low (ECDSA r s) = ECDSA r (W.select s (_CURVE_Q - s) (W.gt s _CURVE_QH))
{-# INLINE low #-}
-- | Produce an ECDSA signature for the provided message, using the
@@ -1256,7 +1321,7 @@ verify_ecdsa_unrestricted
-> Pub -- ^ public key
-> ECDSA -- ^ signature
-> Bool
-verify_ecdsa_unrestricted = _verify_ecdsa_unrestricted (mul_unsafe _CURVE_G)
+verify_ecdsa_unrestricted = _verify_ecdsa_unrestricted (mul_vartime _CURVE_G)
-- | The same as 'verify_ecdsa_unrestricted', except uses a 'Context' to
-- optimise internal calculations.
@@ -1294,7 +1359,7 @@ _verify_ecdsa_unrestricted _mul m p (ECDSA r0 s0) = M.isJust $ do
u1 = S.retr (e * si)
u2 = S.retr (r * si)
pt0 <- _mul u1
- pt1 <- mul_unsafe p u2
+ pt1 <- mul_vartime p u2
let capR = add pt0 pt1
guard (capR /= _CURVE_ZERO)
let Affine (S.to . C.retr -> v) _ = affine capR
diff --git a/weigh-0.0.18/CHANGELOG b/weigh-0.0.18/CHANGELOG
@@ -0,0 +1,49 @@
+0.0.18:
+ * Fix compatibility with `mtl`, whenever different version from the one that is wired with ghc is used.
+
+0.0.17:
+ * Changes to make compatible for GHC 9.6
+
+0.0.16:
+ * Add MaxOS parameter to indicate memory in use by RTS
+ * Add haddock docmentation to the 'Column' type
+ * Fix bug in treating words as int, use only Word as reported by GHC.
+
+0.0.14:
+ * Use the correct data source for final live bytes total
+
+0.0.13:
+ * Forward arguments to the child process
+
+0.0.12:
+ * Fix bug in non-unique groupings
+
+0.0.10:
+ * Export Grouped
+
+0.0.9:
+ * Support markdown output
+
+0.0.8:
+ * Support grouping
+
+0.0.6:
+ * Support GHC 8.2
+ * Use more reliable calculations
+
+0.0.4:
+ * Added more system-independent word size calculation
+
+0.0.3:
+ * Added more docs to haddocks
+ * Export more internal combinators
+
+0.0.2:
+ * Remove magic numbers from weighing code, better accuracy
+ * Add additional `io` combinator
+
+0.0.1:
+ * Support GHC 8.
+
+0.0.0:
+ * First release.
diff --git a/weigh-0.0.18/LICENSE b/weigh-0.0.18/LICENSE
@@ -0,0 +1,30 @@
+Copyright Chris Done (c) 2016
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+
+ * Neither the name of Chris Done nor the names of other
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+\ No newline at end of file
diff --git a/weigh-0.0.18/README.md b/weigh-0.0.18/README.md
@@ -0,0 +1,37 @@
+# weigh [](https://github.com/fpco/weigh/actions/workflows/tests.yml)
+
+Measures the memory usage of a Haskell value or function
+
+# Limitations
+
+* :warning: Turn off the `-threaded` flag, otherwise it will cause inconsistent results.
+
+## Example use
+
+``` haskell
+import Weigh
+
+main :: IO ()
+main =
+ mainWith
+ (do func "integers count 0" count 0
+ func "integers count 1" count 1
+ func "integers count 10" count 10
+ func "integers count 100" count 100)
+ where
+ count :: Integer -> ()
+ count 0 = ()
+ count a = count (a - 1)
+```
+
+Output results:
+
+|Case|Allocated|GCs|
+|:---|---:|---:|
+|integers count 0|16|0|
+|integers count 1|88|0|
+|integers count 10|736|0|
+|integers count 100|7,216|0|
+
+Output by default is plain text table; pass `--markdown` to get a
+markdown output like the above.
diff --git a/weigh-0.0.18/Setup.hs b/weigh-0.0.18/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/weigh-0.0.18/src/Weigh.hs b/weigh-0.0.18/src/Weigh.hs
@@ -0,0 +1,637 @@
+{-# LANGUAGE DeriveGeneric #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE DeriveTraversable #-}
+{-# LANGUAGE DeriveFoldable #-}
+{-# LANGUAGE DeriveFunctor #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE ExistentialQuantification #-}
+{-# LANGUAGE ViewPatterns #-}
+{-# LANGUAGE BangPatterns #-}
+
+-- | Framework for seeing how much a function allocates.
+--
+-- WARNING: weigh is incompatible with profiling. It reports much more
+-- allocations with profiling turned on.
+--
+-- Example:
+--
+-- @
+-- import Weigh
+-- main =
+-- mainWith (do func "integers count 0" count 0
+-- func "integers count 1" count 1
+-- func "integers count 2" count 2
+-- func "integers count 3" count 3
+-- func "integers count 10" count 10
+-- func "integers count 100" count 100)
+-- where count :: Integer -> ()
+-- count 0 = ()
+-- count a = count (a - 1)
+-- @
+--
+-- Use 'wgroup' to group sets of tests.
+
+module Weigh
+ (-- * Main entry points
+ mainWith
+ ,weighResults
+ -- * Configuration
+ ,setColumns
+ ,Column(..)
+ ,setFormat
+ ,Format (..)
+ ,setConfig
+ ,Config (..)
+ ,defaultConfig
+ -- * Simple combinators
+ ,func
+ ,func'
+ ,io
+ ,value
+ ,action
+ ,wgroup
+ -- * Validating combinators
+ ,validateAction
+ ,validateFunc
+ -- * Validators
+ ,maxAllocs
+ -- * Types
+ ,Weigh
+ ,Weight(..)
+ -- * Handy utilities
+ ,commas
+ ,reportGroup
+ -- * Internals
+ ,weighDispatch
+ ,weighFunc
+ ,weighFuncResult
+ ,weighAction
+ ,weighActionResult
+ ,Grouped(..)
+ )
+ where
+
+import Control.Applicative
+import Control.Arrow
+import Control.DeepSeq
+import Control.Monad (unless)
+import Control.Monad.State (State, execState, get, gets, modify)
+import Criterion.Measurement
+import qualified Data.Foldable as Foldable
+import qualified Data.List as List
+import Data.List.Split
+import Data.Maybe
+import qualified Data.Traversable as Traversable
+import Data.Word
+import GHC.Generics
+import Prelude
+import System.Environment
+import System.Exit
+import System.IO
+import System.IO.Temp
+import System.Mem
+import System.Process
+import Text.Printf
+import qualified Weigh.GHCStats as GHCStats
+
+--------------------------------------------------------------------------------
+-- Types
+
+-- | Table column.
+data Column
+ = Case -- ^ Case name for the column
+ | Allocated -- ^ Total bytes allocated
+ | GCs -- ^ Total number of GCs
+ | Live -- ^ Total amount of live data in the heap
+ | Check -- ^ Table column indicating about the test status
+ | Max -- ^ Maximum residency memory in use
+ | MaxOS -- ^ Maximum memory in use by the RTS. Valid only for
+ -- GHC >= 8.2.2. For unsupported GHC, this is reported
+ -- as 0.
+ | WallTime -- ^ Rough execution time. For general indication, not a benchmark tool.
+ deriving (Show, Eq, Enum)
+
+-- | Weigh configuration.
+data Config = Config
+ { configColumns :: [Column]
+ , configPrefix :: String
+ , configFormat :: !Format
+ } deriving (Show)
+
+data Format = Plain | Markdown
+ deriving (Show)
+
+-- | Weigh specification monad.
+newtype Weigh a =
+ Weigh {runWeigh :: State (Config, [Grouped Action]) a}
+ deriving (Monad,Functor,Applicative)
+
+-- | How much a computation weighed in at.
+data Weight =
+ Weight {weightLabel :: !String
+ ,weightAllocatedBytes :: !Word64
+ ,weightGCs :: !Word32
+ ,weightLiveBytes :: !Word64
+ ,weightMaxBytes :: !Word64
+ ,weightMaxOSBytes :: !Word64
+ ,weightWallTime :: !Double
+ }
+ deriving (Read,Show)
+
+-- | Some grouped thing.
+data Grouped a
+ = Grouped String [Grouped a]
+ | Singleton a
+ deriving (Eq, Show, Functor, Traversable.Traversable, Foldable.Foldable, Generic)
+instance NFData a => NFData (Grouped a)
+
+-- | An action to run.
+data Action =
+ forall a b. (NFData a) =>
+ Action {_actionRun :: !(Either (b -> IO a) (b -> a))
+ ,_actionArg :: !b
+ ,actionName :: !String
+ ,actionCheck :: Weight -> Maybe String}
+instance NFData Action where rnf _ = ()
+
+--------------------------------------------------------------------------------
+-- Main-runners
+
+-- | Just run the measuring and print a report. Uses 'weighResults'.
+mainWith :: Weigh a -> IO ()
+mainWith m = do
+ (results, config) <- weighResults m
+ unless
+ (null results)
+ (do putStrLn ""
+ putStrLn (report config results))
+ case mapMaybe
+ (\(w, r) -> do
+ msg <- r
+ return (w, msg))
+ (concatMap Foldable.toList (Foldable.toList results)) of
+ [] -> return ()
+ errors -> do
+ putStrLn "\nCheck problems:"
+ mapM_
+ (\(w, r) -> putStrLn (" " ++ weightLabel w ++ "\n " ++ r))
+ errors
+ exitWith (ExitFailure (-1))
+
+-- | Run the measuring and return all the results, each one may have
+-- an error.
+weighResults
+ :: Weigh a -> IO ([Grouped (Weight,Maybe String)], Config)
+weighResults m = do
+ args <- getArgs
+ weighEnv <- lookupEnv "WEIGH_CASE"
+ let (config, cases) = execState (runWeigh m) (defaultConfig, [])
+ result <- weighDispatch weighEnv cases
+ case result of
+ Nothing -> return ([], config)
+ Just weights ->
+ return
+ ( fmap
+ (fmap
+ (\w ->
+ case glookup (weightLabel w) cases of
+ Nothing -> (w, Nothing)
+ Just a -> (w, actionCheck a w)))
+ weights
+ , config
+ { configFormat =
+ if any (== "--markdown") args
+ then Markdown
+ else configFormat config
+ })
+
+--------------------------------------------------------------------------------
+-- User DSL
+
+-- | Default columns to display.
+defaultColumns :: [Column]
+defaultColumns = [Case, Allocated, GCs]
+
+-- | Default config.
+defaultConfig :: Config
+defaultConfig =
+ Config
+ {configColumns = defaultColumns, configPrefix = "", configFormat = Plain}
+
+-- | Set the columns to display in the config
+setColumns :: [Column] -> Weigh ()
+setColumns cs = Weigh (modify (first (\c -> c {configColumns = cs})))
+
+-- | Set the output format in the config
+setFormat :: Format -> Weigh ()
+setFormat fm = Weigh (modify (first (\c -> c {configFormat = fm})))
+
+-- | Set the config. Default is: 'defaultConfig'.
+setConfig :: Config -> Weigh ()
+setConfig = Weigh . modify . first . const
+
+-- | Weigh a function applied to an argument.
+--
+-- Implemented in terms of 'validateFunc'.
+func :: (NFData a)
+ => String -- ^ Name of the case.
+ -> (b -> a) -- ^ Function that does some action to measure.
+ -> b -- ^ Argument to that function.
+ -> Weigh ()
+func name !f !x = validateFunc name f x (const Nothing)
+
+-- | Weigh a function applied to an argument. Unlike 'func', the argument
+-- is evaluated to normal form before the function is applied.
+func' :: (NFData a, NFData b)
+ => String
+ -> (b -> a)
+ -> b
+ -> Weigh ()
+func' name !f (force -> !x) = validateFunc name f x (const Nothing)
+
+-- | Weigh an action applied to an argument.
+--
+-- Implemented in terms of 'validateAction'.
+io :: (NFData a)
+ => String -- ^ Name of the case.
+ -> (b -> IO a) -- ^ Action that does some IO to measure.
+ -> b -- ^ Argument to that function.
+ -> Weigh ()
+io name !f !x = validateAction name f x (const Nothing)
+
+-- | Weigh a value.
+--
+-- Implemented in terms of 'action'.
+value :: NFData a
+ => String -- ^ Name for the value.
+ -> a -- ^ The value to measure.
+ -> Weigh ()
+value name !v = func name id v
+
+-- | Weigh an IO action.
+--
+-- Implemented in terms of 'validateAction'.
+action :: NFData a
+ => String -- ^ Name for the value.
+ -> IO a -- ^ The action to measure.
+ -> Weigh ()
+action name !m = io name (const m) ()
+
+-- | Make a validator that set sthe maximum allocations.
+maxAllocs :: Word64 -- ^ The upper bound.
+ -> (Weight -> Maybe String)
+maxAllocs n =
+ \w ->
+ if weightAllocatedBytes w > n
+ then Just ("Allocated bytes exceeds " ++
+ commas n ++ ": " ++ commas (weightAllocatedBytes w))
+ else Nothing
+
+-- | Weigh an IO action, validating the result.
+validateAction :: (NFData a)
+ => String -- ^ Name of the action.
+ -> (b -> IO a) -- ^ The function which performs some IO.
+ -> b -- ^ Argument to the function. Doesn't have to be forced.
+ -> (Weight -> Maybe String) -- ^ A validating function, returns maybe an error.
+ -> Weigh ()
+validateAction name !m !arg !validate =
+ tellAction name $ flip (Action (Left m) arg) validate
+
+-- | Weigh a function, validating the result
+validateFunc :: (NFData a)
+ => String -- ^ Name of the function.
+ -> (b -> a) -- ^ The function which calculates something.
+ -> b -- ^ Argument to the function. Doesn't have to be forced.
+ -> (Weight -> Maybe String) -- ^ A validating function, returns maybe an error.
+ -> Weigh ()
+validateFunc name !f !x !validate =
+ tellAction name $ flip (Action (Right f) x) validate
+
+-- | Write out an action.
+tellAction :: String -> (String -> Action) -> Weigh ()
+tellAction name act =
+ Weigh (do prefix <- gets (configPrefix . fst)
+ modify (second (\x -> x ++ [Singleton $ act (prefix ++ "/" ++ name)])))
+
+-- | Make a grouping of tests.
+wgroup :: String -> Weigh () -> Weigh ()
+wgroup str wei = do
+ (orig, start) <- Weigh get
+ let startL = length $ start
+ Weigh (modify (first (\c -> c {configPrefix = configPrefix orig ++ "/" ++ str})))
+ wei
+ Weigh $ do
+ modify $ second $ \x -> take startL x ++ [Grouped str $ drop startL x]
+ modify (first (\c -> c {configPrefix = configPrefix orig}))
+
+--------------------------------------------------------------------------------
+-- Internal measuring actions
+
+-- | Weigh a set of actions. The value of the actions are forced
+-- completely to ensure they are fully allocated.
+weighDispatch :: Maybe String -- ^ The content of then env variable WEIGH_CASE.
+ -> [Grouped Action] -- ^ Weigh name:action mapping.
+ -> IO (Maybe [(Grouped Weight)])
+weighDispatch args cases =
+ case args of
+ Just var -> do
+ let (label:fp:_) = read var
+ let !_ = force fp
+ let !cases' = force cases
+ performGC -- flush CAF initialization allocations before measurement
+ case glookup label cases' of
+ Nothing -> error "No such case!"
+ Just act -> do
+ case act of
+ Action !run arg _ _ -> do
+ initializeTime
+ start <- getTime
+ (bytes, gcs, liveBytes, maxByte, maxOSBytes) <-
+ case run of
+ Right f -> weighFunc f arg
+ Left m -> weighAction m arg
+ end <- getTime
+ writeFile
+ fp
+ (show
+ (Weight
+ { weightLabel = label
+ , weightAllocatedBytes = bytes
+ , weightGCs = gcs
+ , weightLiveBytes = liveBytes
+ , weightMaxBytes = maxByte
+ , weightMaxOSBytes = maxOSBytes
+ , weightWallTime = end - start
+ }))
+ return Nothing
+ _ -> fmap Just (Traversable.traverse (Traversable.traverse fork) cases)
+
+-- | Lookup an action.
+glookup :: String -> [Grouped Action] -> Maybe Action
+glookup label =
+ Foldable.find ((== label) . actionName) .
+ concat . map Foldable.toList . Foldable.toList
+
+-- | Fork a case and run it.
+fork :: Action -- ^ Label for the case.
+ -> IO Weight
+fork act =
+ withSystemTempFile
+ "weigh"
+ (\fp h -> do
+ hClose h
+ setEnv "WEIGH_CASE" $ show $ [actionName act,fp]
+ me <- getExecutablePath
+ args <- getArgs
+ (exit, _, err) <-
+ readProcessWithExitCode
+ me
+ (args ++ ["+RTS", "-T", "-RTS"])
+ ""
+ case exit of
+ ExitFailure {} ->
+ error
+ ("Error in case (" ++ show (actionName act) ++ "):\n " ++ err)
+ ExitSuccess -> do
+ out <- readFile fp
+ case reads out of
+ [(!r, _)] -> return r
+ _ ->
+ error
+ (concat
+ [ "Malformed output from subprocess. Weigh"
+ , " (currently) communicates with its sub-"
+ , "processes via a temporary file."
+ ]))
+
+-- | Weigh a pure function. This function is built on top of `weighFuncResult`,
+-- which is heavily documented inside
+weighFunc
+ :: (NFData a)
+ => (b -> a) -- ^ A function whose memory use we want to measure.
+ -> b -- ^ Argument to the function. Doesn't have to be forced.
+ -> IO (Word64,Word32,Word64,Word64,Word64) -- ^ Bytes allocated and garbage collections.
+weighFunc run !arg = snd <$> weighFuncResult run arg
+
+-- | Weigh a pure function and return the result. This function is heavily
+-- documented inside.
+weighFuncResult
+ :: (NFData a)
+ => (b -> a) -- ^ A function whose memory use we want to measure.
+ -> b -- ^ Argument to the function. Doesn't have to be forced.
+ -> IO (a, (Word64,Word32,Word64,Word64,Word64)) -- ^ Result, Bytes allocated, GCs.
+weighFuncResult run !arg = do
+ ghcStatsSizeInBytes <- GHCStats.getGhcStatsSizeInBytes
+ performGC
+ -- The above forces getStats data to be generated NOW.
+ !bootupStats <- GHCStats.getStats
+ -- We need the above to subtract "program startup" overhead. This
+ -- operation itself adds n bytes for the size of GCStats, but we
+ -- subtract again that later.
+ let !result = force (run arg)
+ performGC
+ -- The above forces getStats data to be generated NOW.
+ !actionStats <- GHCStats.getStats
+ let reflectionGCs = 1 -- We performed an additional GC.
+ actionBytes =
+ (GHCStats.totalBytesAllocated actionStats `subtracting`
+ GHCStats.totalBytesAllocated bootupStats) `subtracting`
+ -- We subtract the size of "bootupStats", which will be
+ -- included after we did the performGC.
+ fromIntegral ghcStatsSizeInBytes
+ actionGCs =
+ GHCStats.gcCount actionStats `subtracting` GHCStats.gcCount bootupStats `subtracting`
+ reflectionGCs
+ -- If overheadBytes is too large, we conservatively just
+ -- return zero. It's not perfect, but this library is for
+ -- measuring large quantities anyway.
+ actualBytes = max 0 actionBytes
+ liveBytes =
+ (GHCStats.liveBytes actionStats `subtracting`
+ GHCStats.liveBytes bootupStats)
+ maxBytes =
+ (GHCStats.maxBytesInUse actionStats `subtracting`
+ GHCStats.maxBytesInUse bootupStats)
+ maxOSBytes =
+ (GHCStats.maxOSBytes actionStats `subtracting`
+ GHCStats.maxOSBytes bootupStats)
+ return (result, (actualBytes, actionGCs, liveBytes, maxBytes, maxOSBytes))
+
+subtracting :: (Ord p, Num p) => p -> p -> p
+subtracting x y =
+ if x > y
+ then x - y
+ else 0
+
+-- | Weigh an IO action. This function is based on `weighActionResult`, which is
+-- heavily documented inside.
+weighAction
+ :: (NFData a)
+ => (b -> IO a) -- ^ A function whose memory use we want to measure.
+ -> b -- ^ Argument to the function. Doesn't have to be forced.
+ -> IO (Word64,Word32,Word64,Word64,Word64) -- ^ Bytes allocated and garbage collections.
+weighAction run !arg = snd <$> weighActionResult run arg
+
+-- | Weigh an IO action, and return the result. This function is heavily
+-- documented inside.
+weighActionResult
+ :: (NFData a)
+ => (b -> IO a) -- ^ A function whose memory use we want to measure.
+ -> b -- ^ Argument to the function. Doesn't have to be forced.
+ -> IO (a, (Word64,Word32,Word64,Word64,Word64)) -- ^ Result, Bytes allocated and GCs.
+weighActionResult run !arg = do
+ ghcStatsSizeInBytes <- GHCStats.getGhcStatsSizeInBytes
+ performGC
+ -- The above forces getStats data to be generated NOW.
+ !bootupStats <- GHCStats.getStats
+ -- We need the above to subtract "program startup" overhead. This
+ -- operation itself adds n bytes for the size of GCStats, but we
+ -- subtract again that later.
+ !result <- fmap force (run arg)
+ performGC
+ -- The above forces getStats data to be generated NOW.
+ !actionStats <- GHCStats.getStats
+ let reflectionGCs = 1 -- We performed an additional GC.
+ actionBytes =
+ (GHCStats.totalBytesAllocated actionStats `subtracting`
+ GHCStats.totalBytesAllocated bootupStats) `subtracting`
+ -- We subtract the size of "bootupStats", which will be
+ -- included after we did the performGC.
+ fromIntegral ghcStatsSizeInBytes
+ actionGCs =
+ GHCStats.gcCount actionStats `subtracting` GHCStats.gcCount bootupStats `subtracting`
+ reflectionGCs
+ -- If overheadBytes is too large, we conservatively just
+ -- return zero. It's not perfect, but this library is for
+ -- measuring large quantities anyway.
+ actualBytes = max 0 actionBytes
+ liveBytes =
+ max 0 (GHCStats.liveBytes actionStats `subtracting` GHCStats.liveBytes bootupStats)
+ maxBytes =
+ max
+ 0
+ (GHCStats.maxBytesInUse actionStats `subtracting`
+ GHCStats.maxBytesInUse bootupStats)
+ maxOSBytes =
+ max
+ 0
+ (GHCStats.maxOSBytes actionStats `subtracting`
+ GHCStats.maxOSBytes bootupStats)
+ return (result,
+ ( actualBytes
+ , actionGCs
+ , liveBytes
+ , maxBytes
+ , maxOSBytes
+ ))
+
+--------------------------------------------------------------------------------
+-- Formatting functions
+
+report :: Config -> [Grouped (Weight,Maybe String)] -> String
+report config gs =
+ List.intercalate
+ "\n\n"
+ (filter
+ (not . null)
+ [ if null singletons
+ then []
+ else reportTabular config singletons
+ , List.intercalate "\n\n" (map (uncurry (reportGroup config)) groups)
+ ])
+ where
+ singletons =
+ mapMaybe
+ (\case
+ Singleton v -> Just v
+ _ -> Nothing)
+ gs
+ groups =
+ mapMaybe
+ (\case
+ Grouped title vs -> Just (title, vs)
+ _ -> Nothing)
+ gs
+
+reportGroup :: Config -> [Char] -> [Grouped (Weight, Maybe String)] -> [Char]
+reportGroup config title gs =
+ case configFormat config of
+ Plain -> title ++ "\n\n" ++ indent (report config gs)
+ Markdown -> "#" ++ title ++ "\n\n" ++ report config gs
+
+-- | Make a report of the weights.
+reportTabular :: Config -> [(Weight,Maybe String)] -> String
+reportTabular config = tabled
+ where
+ tabled =
+ (case configFormat config of
+ Plain -> tablize
+ Markdown -> mdtable) .
+ (select headings :) . map (select . toRow)
+ select row = mapMaybe (\name -> lookup name row) (configColumns config)
+ headings =
+ [ (Case, (True, "Case"))
+ , (Allocated, (False, "Allocated"))
+ , (GCs, (False, "GCs"))
+ , (Live, (False, "Live"))
+ , (Check, (True, "Check"))
+ , (Max, (False, "Max"))
+ , (MaxOS, (False, "MaxOS"))
+ , (WallTime, (False, "Wall Time"))
+ ]
+ toRow (w, err) =
+ [ (Case, (True, takeLastAfterBk $ weightLabel w))
+ , (Allocated, (False, commas (weightAllocatedBytes w)))
+ , (GCs, (False, commas (weightGCs w)))
+ , (Live, (False, commas (weightLiveBytes w)))
+ , (Max, (False, commas (weightMaxBytes w)))
+ , (MaxOS, (False, commas (weightMaxOSBytes w)))
+ , (WallTime, (False, printf "%.3fs" (weightWallTime w)))
+ , ( Check
+ , ( True
+ , case err of
+ Nothing -> "OK"
+ Just {} -> "INVALID"))
+ ]
+ takeLastAfterBk w = case List.elemIndices '/' w of
+ [] -> w
+ x -> drop (1+last x) w
+
+-- | Make a markdown table.
+mdtable ::[[(Bool,String)]] -> String
+mdtable rows = List.intercalate "\n" [heading, align, body]
+ where
+ heading = columns (map (\(_, str) -> str) (fromMaybe [] (listToMaybe rows)))
+ align =
+ columns
+ (map
+ (\(shouldAlignLeft, _) ->
+ if shouldAlignLeft
+ then ":---"
+ else "---:")
+ (fromMaybe [] (listToMaybe rows)))
+ body =
+ List.intercalate "\n" (map (\row -> columns (map snd row)) (drop 1 rows))
+ columns xs = "|" ++ List.intercalate "|" xs ++ "|"
+
+-- | Make a table out of a list of rows.
+tablize :: [[(Bool,String)]] -> String
+tablize xs =
+ List.intercalate "\n" (map (List.intercalate " " . map fill . zip [0 ..]) xs)
+ where
+ fill (x', (left', text')) =
+ printf ("%" ++ direction ++ show width ++ "s") text'
+ where
+ direction =
+ if left'
+ then "-"
+ else ""
+ width = maximum (map (length . snd . (!! x')) xs)
+
+-- | Formatting an integral number to 1,000,000, etc.
+commas :: (Num a,Integral a,Show a) => a -> String
+commas = reverse . List.intercalate "," . chunksOf 3 . reverse . show
+
+-- | Indent all lines in a string.
+indent :: [Char] -> [Char]
+indent = List.intercalate "\n" . map (replicate 2 ' '++) . lines
diff --git a/weigh-0.0.18/src/Weigh/GHCStats.hs b/weigh-0.0.18/src/Weigh/GHCStats.hs
@@ -0,0 +1,56 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE TupleSections #-}
+{-# LANGUAGE CPP #-}
+
+-- | Calculate the size of GHC.Stats statically.
+
+module Weigh.GHCStats
+ (getGhcStatsSizeInBytes
+ ,getStats
+ ,gcCount
+ ,totalBytesAllocated
+ ,liveBytes
+ ,maxBytesInUse
+ ,maxOSBytes
+ )
+ where
+
+import Data.Word
+import GHC.Stats
+import System.Mem
+
+-- | Get GHC's statistics.
+getStats :: IO RTSStats
+getStats = getRTSStats
+
+gcCount :: RTSStats -> Word32
+gcCount = gcs
+
+totalBytesAllocated :: RTSStats -> Word64
+totalBytesAllocated = allocated_bytes
+
+liveBytes :: RTSStats -> Word64
+liveBytes = gcdetails_live_bytes . gc
+
+maxBytesInUse :: RTSStats -> Word64
+maxBytesInUse = max_live_bytes
+
+maxOSBytes :: RTSStats -> Word64
+maxOSBytes = max_mem_in_use_bytes
+
+-- | Get the size of a 'RTSStats' object in bytes.
+getGhcStatsSizeInBytes :: IO Word64
+getGhcStatsSizeInBytes = do
+ s1 <- oneGetStats
+ s2 <- twoGetStats
+ return (fromIntegral (totalBytesAllocated s2 - totalBytesAllocated s1))
+ where
+ oneGetStats = do
+ performGC
+ !s <- getStats
+ return s
+ twoGetStats = do
+ performGC
+ !_ <- getStats
+ !s <- getStats
+ return s
diff --git a/weigh-0.0.18/src/test/Main.hs b/weigh-0.0.18/src/test/Main.hs
@@ -0,0 +1,104 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE DeriveGeneric #-}
+
+-- | Example uses of Weigh which should work.
+
+module Main where
+
+import Control.DeepSeq
+import Weigh
+import GHC.Generics
+
+import Data.List (nub)
+
+-- | Weigh integers.
+main :: IO ()
+main =
+ mainWith
+ (do wgroup "Integers" integers
+ wgroup "IO actions" ioactions
+ wgroup "Ints" ints
+ wgroup "Structs" struct
+ wgroup "Packing" packing
+ wgroup "fst" aFuncNamedId
+ wgroup "snd" anotherFuncNamedId
+ )
+
+-- | Weigh IO actions.
+ioactions :: Weigh ()
+ioactions =
+ do action "integers count IO CAF 0" (return (count 0))
+ io "integers count IO func 0" (return . count) 0
+ action "integers count IO CAF 1" (return (count 1))
+ io "integers count IO func 1" (return . count) 1
+ where count :: Integer -> ()
+ count 0 = ()
+ count a = count (a - 1)
+
+-- | Just counting integers.
+integers :: Weigh ()
+integers = do
+ func "integers count 0" count 0
+ func "integers count 1" count 1
+ func "integers count 2" count 2
+ func "integers count 3" count 3
+ func "integers count 10" count 10
+ func "integers count 100" count 100
+ where
+ count :: Integer -> ()
+ count 0 = ()
+ count a = count (a - 1)
+
+-- | We count ints and ensure that the allocations are optimized away
+-- to only two 64-bit Ints (16 bytes).
+ints :: Weigh ()
+ints =
+ do validateFunc "ints count 1" count 1 (maxAllocs 0)
+ validateFunc "ints count 10" count 10 (maxAllocs 0)
+ validateFunc "ints count 1000000" count 1000000 (maxAllocs 0)
+ where count :: Int -> ()
+ count 0 = ()
+ count a = count (a - 1)
+
+-- | Some simple data structure of two ints.
+data IntegerStruct = IntegerStruct !Integer !Integer
+ deriving (Generic)
+instance NFData IntegerStruct
+
+-- | Weigh allocating a user-defined structure.
+struct :: Weigh ()
+struct =
+ do func "\\_ -> IntegerStruct 0 0" (\_ -> IntegerStruct 0 0) (5 :: Integer)
+ func "\\x -> IntegerStruct x 0" (\x -> IntegerStruct x 0) 5
+ func "\\x -> IntegerStruct x x" (\x -> IntegerStruct x x) 5
+ func "\\x -> IntegerStruct (x+1) x" (\x -> IntegerStruct (x+1) x) 5
+ func "\\x -> IntegerStruct (x+1) (x+1)" (\x -> IntegerStruct (x+1) (x+1)) 5
+ func "\\x -> IntegerStruct (x+1) (x+2)" (\x -> IntegerStruct (x+1) (x+2)) 5
+
+-- | A simple structure with an Int in it.
+data HasInt = HasInt !Int
+ deriving (Generic)
+instance NFData HasInt
+
+-- | A simple structure with an Int in it.
+data HasPacked = HasPacked HasInt
+ deriving (Generic)
+instance NFData HasPacked
+
+-- | A simple structure with an Int in it.
+data HasUnpacked = HasUnpacked {-# UNPACK #-} !HasInt
+ deriving (Generic)
+instance NFData HasUnpacked
+
+-- | Weigh: packing vs no packing.
+packing :: Weigh ()
+packing =
+ do func "\\x -> HasInt x" (\x -> HasInt x) 5
+ func "\\x -> HasUnpacked (HasInt x)" (\x -> HasUnpacked (HasInt x)) 5
+ func "\\x -> HasPacked (HasInt x)" (\x -> HasPacked (HasInt x)) 5
+
+aFuncNamedId :: Weigh ()
+aFuncNamedId = func "id" id (1::Int)
+
+anotherFuncNamedId :: Weigh ()
+anotherFuncNamedId = func "id" nub ([1,2,3,4,5,1]::[Int])
diff --git a/weigh-0.0.18/weigh.cabal b/weigh-0.0.18/weigh.cabal
@@ -0,0 +1,41 @@
+name: weigh
+version: 0.0.18
+synopsis: Measure allocations of a Haskell functions/values
+description: Please see README.md
+homepage: https://github.com/fpco/weigh#readme
+license: BSD3
+license-file: LICENSE
+author: Chris Done
+maintainer: chrisdone@fpcomplete.com
+copyright: FP Complete
+category: Web
+build-type: Simple
+extra-source-files: README.md
+ CHANGELOG
+cabal-version: >=1.10
+
+library
+ hs-source-dirs: src
+ ghc-options: -Wall -O2
+ exposed-modules: Weigh
+ other-modules: Weigh.GHCStats
+ build-depends: base >= 4.7 && < 5
+ , process
+ , deepseq
+ , mtl
+ , split
+ , temporary
+ , criterion-measurement
+ default-language: Haskell2010
+ if impl(ghc < 8.2.1)
+ buildable: False
+
+test-suite weigh-test
+ default-language: Haskell2010
+ type: exitcode-stdio-1.0
+ hs-source-dirs: src/test
+ ghc-options: -O2 -Wall
+ main-is: Main.hs
+ build-depends: base
+ , weigh
+ , deepseq