ppad-fixed 0.1.6 → 0.2.0
raw patch · 11 files changed
+82/−11 lines, 11 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
Files
- CHANGELOG +12/−0
- lib/Data/Choice.hs +9/−0
- lib/Data/Word/Limb.hs +9/−1
- lib/Data/Word/Wide.hs +1/−1
- lib/Data/Word/Wider.hs +2/−2
- lib/Numeric/Montgomery/Secp256k1/Curve.hs +7/−3
- lib/Numeric/Montgomery/Secp256k1/Scalar.hs +7/−3
- ppad-fixed.cabal +1/−1
- test/Montgomery/Curve.hs +8/−0
- test/Montgomery/Scalar.hs +8/−0
- test/Wider.hs +18/−0
CHANGELOG view
@@ -1,5 +1,17 @@ # Changelog +- 0.2.0 (2026-08-01)+ * Addresses a couple of minor issues detected by an LLM scan:++ * Fixes a bug that could lead shl1_c# to produce a malformed Choice+ that would behave incorrectly for negation or selection.++ * Each Montgomery domain's 'signum' implementation was broken,+ producing R^-1 instead of the proper in-domain 'one'. Now these+ return the appropriate values.++ Also clarifies a few Haddocks, on the suggestions of the same scan.+ - 0.1.6 (2026-08-01) * The eq_{word,wide,wider}# functions now return a mask, instead of a bit, inside the resulting Choice value.
lib/Data/Choice.hs view
@@ -305,6 +305,9 @@ -- | Select an unboxed word without branching, given a 'Choice'. --+-- Returns the second word if the 'Choice' is truthy, and the first+-- otherwise.+-- -- >>> let w = C.select_word# 0## 1## (C.true# ()) in GHC.Word.W# w -- 1 select_word# :: Word# -> Word# -> Choice -> Word#@@ -312,6 +315,9 @@ {-# INLINE select_word# #-} -- | Select an unboxed two-limb word without branching, given a 'Choice'.+--+-- Returns the second word if the 'Choice' is truthy, and the first+-- otherwise. select_wide# :: Limb2 -> Limb2@@ -323,6 +329,9 @@ {-# INLINE select_wide# #-} -- | Select an unboxed four-limb word without branching, given a 'Choice'.+--+-- Returns the second word if the 'Choice' is truthy, and the first+-- otherwise. select_wider# :: Limb4 -> Limb4
lib/Data/Word/Limb.hs view
@@ -143,7 +143,7 @@ -- constant-time selection ---------------------------------------------------- --- | Return a if c is truthy, otherwise return b.+-- | Return b if c is truthy, otherwise return a. select# :: Limb -- ^ a -> Limb -- ^ b@@ -268,6 +268,9 @@ -- | Carrying addition, computing augend + addend + carry, returning -- the sum and new carry.+--+-- The carry-in must be 0 or 1; this is not checked. The carry-out+-- is always 0 or 1. add_c# :: Limb -- ^ augend -> Limb -- ^ addend@@ -304,6 +307,11 @@ -- | Borrowing subtraction, computing minuend - (subtrahend + borrow), -- returning the difference and new borrow mask.+--+-- The borrow-in is interpreted as a mask, of which only the most+-- significant bit is examined; a full-word mask (as produced by+-- this function) borrows 1. The borrow-out is 0 or a full-word+-- mask. sub_b# :: Limb -- ^ minuend -> Limb -- ^ subtrahend
lib/Data/Word/Wide.hs view
@@ -159,7 +159,7 @@ -- constant-time selection----------------------------------------------------- --- | Return a if c is truthy, otherwise return b.+-- | Return b if c is truthy, otherwise return a. -- -- >>> import qualified Data.Choice as C -- >>> select 0 1 (C.true# ())
lib/Data/Word/Wider.hs view
@@ -319,7 +319,7 @@ in L4 w0 w1 w2 w3 {-# INLINE select# #-} --- | Return a if c is truthy, otherwise return b.+-- | Return b if c is truthy, otherwise return a. -- -- >>> import qualified Data.Choice as C -- >>> select 0 1 (C.true# ())@@ -384,7 +384,7 @@ !r2 = L.or# s2 c1 !(# s3, c3 #) = (# L.shl# w3 1#, L.shr# w3 s #) !r3 = L.or# s3 c2- !(Limb w) = L.shl# c3 s+ !(Limb w) = c3 in (# (# r0, r1, r2, r3 #), C.from_bit# w #) {-# INLINE shl1_c# #-}
lib/Numeric/Montgomery/Secp256k1/Curve.hs view
@@ -109,8 +109,9 @@ signum (Montgomery (# l0, l1, l2, l3 #)) = let !(Limb l) = l0 `L.or#` l1 `L.or#` l2 `L.or#` l3 !n = C.from_word_nonzero# l- !b = C.to_word# n- in Montgomery (# Limb b, Limb 0##, Limb 0##, Limb 0## #)+ !(Montgomery z) = zero+ !(Montgomery o) = one+ in Montgomery (select# z o n) instance NFData Montgomery where rnf (Montgomery a) = case a of (# _, _, _, _ #) -> ()@@ -761,6 +762,9 @@ -- | Multiplicative inverse in the Montgomery domain. --+-- Note that 'zero' has no multiplicative inverse; 'inv' returns+-- 'zero' when applied to it.+-- -- >> inv 2 -- 57896044618658097711785492504343953926634992332820282019728792003954417335832 -- >> inv 2 * 2@@ -1354,7 +1358,7 @@ select# = WW.select# {-# INLINE select# #-} --- | Return a if c is truthy, otherwise return b.+-- | Return b if c is truthy, otherwise return a. -- -- >>> import qualified Data.Choice as C -- >>> select 0 1 (C.true# ())
lib/Numeric/Montgomery/Secp256k1/Scalar.hs view
@@ -107,8 +107,9 @@ signum (Montgomery (# l0, l1, l2, l3 #)) = let !(Limb l) = l0 `L.or#` l1 `L.or#` l2 `L.or#` l3 !n = C.from_word_nonzero# l- !b = C.to_word# n- in Montgomery (L4 b 0## 0## 0##)+ !(Montgomery z) = zero+ !(Montgomery o) = one+ in Montgomery (select# z o n) instance NFData Montgomery where rnf (Montgomery a) = case a of (# _, _, _, _ #) -> ()@@ -792,6 +793,9 @@ -- | Multiplicative inverse in the Montgomery domain. --+-- Note that 'zero' has no multiplicative inverse; 'inv' returns+-- 'zero' when applied to it.+-- -- >> inv 2 -- 57896044618658097711785492504343953926418782139537452191302581570759080747169 -- >> inv 2 * 2@@ -856,7 +860,7 @@ select# = WW.select# {-# INLINE select# #-} --- | Return a if c is truthy, otherwise return b.+-- | Return b if c is truthy, otherwise return a. -- -- >>> import qualified Data.Choice as C -- >>> select 0 1 (C.true# ())
ppad-fixed.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: ppad-fixed-version: 0.1.6+version: 0.2.0 synopsis: Large fixed-width words and constant-time arithmetic. license: MIT license-file: LICENSE
test/Montgomery/Curve.hs view
@@ -118,6 +118,13 @@ 0x00000000000000000000000000000000000000000000000000000001000003D1 0x000000000000000000000000000000000000000000000001000007A2000E90A1 +signum_correct :: H.Assertion+signum_correct = do+ H.assertBool "signum 0 = 0" (C.eq_vartime (signum 0) 0)+ H.assertBool "signum 5 = 1" (C.eq_vartime (signum 5) 1)+ H.assertBool "abs 5 * signum 5 = 5"+ (C.eq_vartime (abs 5 * signum 5) 5)+ instance Q.Arbitrary W.Wider where arbitrary = fmap W.to_vartime Q.arbitrary @@ -177,6 +184,7 @@ , H.testCase "add" add , H.testCase "sub" sub , H.testCase "mul" mul+ , H.testCase "signum" signum_correct , Q.testProperty "a + b mod m ~ ma + mb" $ Q.withMaxSuccess 500 add_matches , Q.testProperty "a * b mod m ~ ma * mb" $ Q.withMaxSuccess 500 mul_matches , Q.testProperty "a ^ 2 mod m ~ ma ^ 2" $ Q.withMaxSuccess 500 sqr_matches
test/Montgomery/Scalar.hs view
@@ -118,6 +118,13 @@ 0x000000000000000000000000000000014551231950B75FC4402DA1732FC9BEBF 0x9D671CD581C69BC5E697F5E45BCD07C6741496C20E7CF878896CF21467D7D140 +signum_correct :: H.Assertion+signum_correct = do+ H.assertBool "signum 0 = 0" (S.eq_vartime (signum 0) 0)+ H.assertBool "signum 5 = 1" (S.eq_vartime (signum 5) 1)+ H.assertBool "abs 5 * signum 5 = 5"+ (S.eq_vartime (abs 5 * signum 5) 5)+ instance Q.Arbitrary W.Wider where arbitrary = fmap W.to_vartime Q.arbitrary @@ -173,6 +180,7 @@ , H.testCase "add" add , H.testCase "sub" sub , H.testCase "mul" mul+ , H.testCase "signum" signum_correct , Q.testProperty "a + b mod m ~ ma + mb" $ Q.withMaxSuccess 500 add_matches , Q.testProperty "a * b mod m ~ ma * mb" $ Q.withMaxSuccess 500 mul_matches , Q.testProperty "a ^ 2 mod m ~ ma ^ 2" $ Q.withMaxSuccess 500 sqr_matches
test/Wider.hs view
@@ -183,6 +183,23 @@ !e = 0x44acf6b7e36c1342c2c5897204fe09504e1e2efb1a900377dbc4e7a6a133ec56 H.assertBool mempty (W.eq_vartime o e) +shl1_c :: H.Assertion+shl1_c = case W.shl1_c (2 ^ (255 :: Word)) of+ (# r1, c1 #) -> case W.shl1_c 1 of+ (# r2, c2 #) -> do+ H.assertBool "value, carry out" (W.eq_vartime r1 0)+ H.assertBool "carry decides true" (C.decide c1)+ -- the carry must be a full-word mask, not a bare bit; negating+ -- or selecting on it is otherwise wrong+ H.assertBool "negated carry decides false"+ (not (C.decide (C.not c1)))+ H.assertBool "select on carry"+ (W.eq_vartime (W.select 0 1 c1) 1)+ H.assertBool "value, no carry" (W.eq_vartime r2 2)+ H.assertBool "no carry decides false" (not (C.decide c2))+ H.assertBool "select on no carry"+ (W.eq_vartime (W.select 0 1 c2) 0)+ instance Q.Arbitrary W.Wider where arbitrary = fmap W.to_vartime Q.arbitrary @@ -216,6 +233,7 @@ , H.testCase "sqr" sqr , H.testCase "mul" mul , H.testCase "sub_mod" sub_mod+ , H.testCase "shl1_c" shl1_c , Q.testProperty "odd w ~ odd (from w)" $ Q.withMaxSuccess 500 odd_correct , Q.testProperty "lt_vartime a b ~ from a < from b" $ Q.withMaxSuccess 500 lt_vartime_correct