Skip to content

Commit f407e52

Browse files
committed
add serialization tests
1 parent 89252cc commit f407e52

File tree

12 files changed

+904
-755
lines changed

12 files changed

+904
-755
lines changed

hie.yaml

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
cradle:
2-
cabal:
3-
- path: "src"
4-
component: "lib:libsecp256k1"
5-
6-
- path: "test"
2+
stack:
3+
- path: "./"
4+
component: "libsecp256k1:lib"
5+
- path: "./test"
76
component: "libsecp256k1:test:spec"

libsecp256k1.cabal

+16-12
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
cabal-version: 2.0
1+
cabal-version: 1.12
22

33
-- This file has been generated from package.yaml by hpack version 0.34.4.
44
--
@@ -28,10 +28,10 @@ source-repository head
2828
library
2929
exposed-modules:
3030
Crypto.Secp256k1
31+
Crypto.Secp256k1.Gen
3132
Crypto.Secp256k1.Internal
3233
Crypto.Secp256k1.Prim
33-
Paths_libsecp256k1
34-
autogen-modules:
34+
other-modules:
3535
Paths_libsecp256k1
3636
hs-source-dirs:
3737
src
@@ -40,16 +40,21 @@ library
4040
build-depends:
4141
base >=4.9 && <5
4242
, bytestring >=0.10.8 && <0.12
43-
, memory
44-
, transformers
43+
, entropy >=0.3.8 && <0.5
44+
, hedgehog
45+
, memory >=0.14.15 && <1.0
46+
, transformers >=0.4.0.0 && <1.0
4547
default-language: Haskell2010
4648

4749
test-suite spec
4850
type: exitcode-stdio-1.0
49-
main-is: Spec.hs
51+
main-is: Main.hs
5052
other-modules:
5153
Crypto.Secp256k1.PrimSpec
54+
Crypto.Secp256k1Prop
5255
Crypto.Secp256k1Spec
56+
Spec
57+
Util
5358
Paths_libsecp256k1
5459
hs-source-dirs:
5560
test
@@ -58,11 +63,10 @@ test-suite spec
5863
HUnit
5964
, base >=4.9 && <5
6065
, bytestring >=0.10.8 && <0.12
66+
, entropy >=0.3.8 && <0.5
67+
, hedgehog
6168
, hspec
62-
, memory
63-
, monad-par
64-
, mtl
65-
, secp256k1-haskell
66-
, transformers
69+
, libsecp256k1
70+
, memory >=0.14.15 && <1.0
71+
, transformers >=0.4.0.0 && <1.0
6772
default-language: Haskell2010
68-
build-tool-depends: hspec-discover:hspec-discover

package.yaml

+6-17
Original file line numberDiff line numberDiff line change
@@ -15,35 +15,24 @@ extra-source-files:
1515
- README.md
1616
dependencies:
1717
- base >=4.9 && <5
18-
# - base16 >=0.3.0.1
1918
- bytestring >=0.10.8 && <0.12
20-
# - cereal >=0.5.4 && <0.6
21-
- memory
22-
# - deepseq >=1.4.2 && <1.5
23-
# - entropy >=0.3.8 && <0.5
24-
# - hashable >=1.2.6 && <1.5
25-
# - QuickCheck >=2.9.2 && <2.15
26-
# - string-conversions >=0.4 && <0.5
27-
- transformers
19+
- entropy >= 0.3.8 && <0.5
20+
- hedgehog
21+
- memory >= 0.14.15 && <1.0
22+
- transformers >= 0.4.0.0 && <1.0
2823
library:
2924
source-dirs: src
30-
generated-exposed-modules:
31-
- Paths_libsecp256k1
3225
pkg-config-dependencies:
3326
- libsecp256k1
3427
tests:
3528
spec:
36-
main: Spec.hs
29+
main: Main.hs
3730
source-dirs: test
3831
ghc-options:
3932
- -threaded
4033
- -rtsopts
4134
- -with-rtsopts=-N
42-
verbatim:
43-
build-tool-depends: hspec-discover:hspec-discover
4435
dependencies:
4536
- hspec
46-
- secp256k1-haskell
47-
- monad-par
48-
- mtl
37+
- libsecp256k1
4938
- HUnit

src/Crypto/Secp256k1.hs

+75-26
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
{-# LANGUAGE DataKinds #-}
2-
{-# LANGUAGE DeriveGeneric #-}
32
{-# LANGUAGE FlexibleContexts #-}
43
{-# LANGUAGE GADTs #-}
5-
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
64
{-# LANGUAGE MultiParamTypeClasses #-}
75
{-# LANGUAGE MultiWayIf #-}
86
{-# LANGUAGE RankNTypes #-}
@@ -32,6 +30,7 @@ module Crypto.Secp256k1 (
3230

3331
-- * Parsing and Serialization
3432
importSecKey,
33+
exportSecKey,
3534
importPubKeyXY,
3635
exportPubKeyXY,
3736
importPubKeyXO,
@@ -86,6 +85,7 @@ import Control.Monad.Trans.Cont (ContT (..), evalContT)
8685
import Crypto.Secp256k1.Internal
8786
import Crypto.Secp256k1.Prim (flagsEcUncompressed)
8887
import qualified Crypto.Secp256k1.Prim as Prim
88+
import qualified Data.ByteArray.Encoding as BA
8989
import Data.ByteArray.Sized
9090
import Data.ByteString (ByteString)
9191
import qualified Data.ByteString as BS
@@ -100,6 +100,7 @@ import Data.String (IsString (..))
100100

101101
-- import Data.String.Conversions (ConvertibleStrings, cs)
102102

103+
import qualified Data.ByteString.Char8 as B8
103104
import Data.Foldable (for_)
104105
import Data.Memory.PtrMethods (memCompare)
105106
import Foreign (
@@ -147,7 +148,16 @@ import Text.Read (
147148
)
148149

149150

151+
-- | Secret Key
150152
newtype SecKey = SecKey {secKeyFPtr :: ForeignPtr Prim.Seckey32}
153+
154+
155+
instance Show SecKey where
156+
show SecKey{..} = unsafePerformIO . evalContT $ do
157+
secKeyPtr <- ContT (withForeignPtr secKeyFPtr)
158+
-- avoid allocating a new bytestring because we are only reading from this pointer
159+
bs <- lift (Data.ByteString.Unsafe.unsafePackCStringLen (castPtr secKeyPtr, 32))
160+
pure $ "0x" <> B8.unpack (BA.convertToBase BA.Base16 bs)
151161
instance Eq SecKey where
152162
sk == sk' = unsafePerformIO . evalContT $ do
153163
skp <- ContT $ withForeignPtr (secKeyFPtr sk)
@@ -160,7 +170,14 @@ instance Ord SecKey where
160170
lift (memCompare (castPtr skp) (castPtr skp') 32)
161171

162172

173+
-- | Public Key with both X and Y coordinates
163174
newtype PubKeyXY = PubKeyXY {pubKeyXYFPtr :: ForeignPtr Prim.Pubkey64}
175+
176+
177+
instance Show PubKeyXY where
178+
show pk = "0x" <> B8.unpack (BA.convertToBase BA.Base16 (exportPubKeyXY True pk))
179+
180+
164181
instance Eq PubKeyXY where
165182
pk == pk' = unsafePerformIO . evalContT $ do
166183
pkp <- ContT . withForeignPtr . pubKeyXYFPtr $ pk
@@ -175,7 +192,14 @@ instance Ord PubKeyXY where
175192
pure $ compare res 0
176193

177194

195+
-- | Public Key with only an X coordinate.
178196
newtype PubKeyXO = PubKeyXO {pubKeyXOFPtr :: ForeignPtr Prim.XonlyPubkey64}
197+
198+
199+
instance Show PubKeyXO where
200+
show pk = "0x" <> B8.unpack (BA.convertToBase BA.Base16 (exportPubKeyXO pk))
201+
202+
179203
instance Eq PubKeyXO where
180204
pk == pk' = unsafePerformIO . evalContT $ do
181205
pkp <- ContT . withForeignPtr . pubKeyXOFPtr $ pk
@@ -190,31 +214,45 @@ instance Ord PubKeyXO where
190214
pure $ compare res 0
191215

192216

217+
-- | Structure containing information equivalent to 'SecKey' and 'PubKeyXY'
193218
newtype KeyPair = KeyPair {keyPairFPtr :: ForeignPtr Prim.Keypair96}
219+
220+
194221
instance Eq KeyPair where
195222
kp == kp' = unsafePerformIO . evalContT $ do
196223
kpp <- ContT $ withForeignPtr (keyPairFPtr kp)
197224
kpp' <- ContT $ withForeignPtr (keyPairFPtr kp')
198225
(EQ ==) <$> lift (memCompare (castPtr kpp) (castPtr kpp') 32)
199226

200227

228+
-- | Structure containing Signature (R,S) data.
201229
newtype Signature = Signature {signatureFPtr :: ForeignPtr Prim.Sig64}
230+
231+
232+
instance Show Signature where
233+
show sig = "0x" <> B8.unpack (BA.convertToBase BA.Base16 (exportSignatureCompact sig))
202234
instance Eq Signature where
203235
sig == sig' = unsafePerformIO . evalContT $ do
204236
sigp <- ContT $ withForeignPtr (signatureFPtr sig)
205237
sigp' <- ContT $ withForeignPtr (signatureFPtr sig')
206238
(EQ ==) <$> lift (memCompare (castPtr sigp) (castPtr sigp') 32)
207239

208240

241+
-- | Structure containing Signature AND recovery ID
209242
newtype RecoverableSignature = RecoverableSignature {recoverableSignatureFPtr :: ForeignPtr Prim.RecSig65}
243+
244+
210245
instance Eq RecoverableSignature where
211246
rs == rs' = unsafePerformIO . evalContT $ do
212247
rsp <- ContT $ withForeignPtr (recoverableSignatureFPtr rs)
213248
rsp' <- ContT $ withForeignPtr (recoverableSignatureFPtr rs')
214249
(EQ ==) <$> lift (memCompare (castPtr rsp) (castPtr rsp') 32)
215250

216251

252+
-- | Isomorphic to 'SecKey' but specifically used for tweaking (EC Group operations) other keys
217253
newtype Tweak = Tweak {tweakFPtr :: ForeignPtr Prim.Tweak32}
254+
255+
218256
instance Eq Tweak where
219257
sk == sk' = unsafePerformIO . evalContT $ do
220258
skp <- ContT $ withForeignPtr (tweakFPtr sk)
@@ -248,29 +286,39 @@ importSecKey bs
248286
else pure Nothing
249287

250288

289+
exportSecKey :: SecKey -> ByteString
290+
exportSecKey SecKey{..} = unsafePerformIO . evalContT $ do
291+
secKeyPtr <- ContT (withForeignPtr secKeyFPtr)
292+
lift $ packByteString (secKeyPtr, 32)
293+
294+
251295
-- | Parses a 33 or 65 byte 'PubKeyXY', all other lengths will result in @Nothing@
252296
importPubKeyXY :: ByteString -> Maybe PubKeyXY
253-
importPubKeyXY bs = unsafePerformIO $
254-
unsafeUseByteString bs $ \(input, len) -> do
255-
pubkeyOutputBuf <- mallocBytes 64
297+
importPubKeyXY bs = unsafePerformIO . evalContT $ do
298+
(input, len) <- ContT (unsafeUseByteString bs)
299+
lift $ do
256300
if len == 33 || len == 65
257301
then do
258-
ret <- Prim.ecPubkeyParse ctx (castPtr pubkeyOutputBuf) input len
302+
pubkeyOutputBuf <- mallocBytes 64
303+
ret <- Prim.ecPubkeyParse ctx pubkeyOutputBuf input len
259304
if isSuccess ret
260-
then Just . PubKeyXY <$> newForeignPtr finalizerFree (castPtr pubkeyOutputBuf)
305+
then Just . PubKeyXY <$> newForeignPtr finalizerFree pubkeyOutputBuf
261306
else free pubkeyOutputBuf $> Nothing
262307
else pure Nothing
263308

264309

265310
-- | Serialize 'PubKeyXY'. First argument @True@ for compressed output (33 bytes), @False@ for uncompressed (65 bytes).
266311
exportPubKeyXY :: Bool -> PubKeyXY -> ByteString
267-
exportPubKeyXY compress (PubKeyXY fptr) = unsafePerformIO $ do
312+
exportPubKeyXY compress PubKeyXY{..} = unsafePerformIO . evalContT $ do
268313
let flags = if compress then Prim.flagsEcCompressed else Prim.flagsEcUncompressed
269314
let sz = if compress then 33 else 65
270-
buf <- mallocBytes sz
271-
alloca $ \written -> do
315+
ptr <- ContT (withForeignPtr pubKeyXYFPtr)
316+
written <- ContT alloca
317+
lift $ do
318+
poke written (fromIntegral sz)
319+
buf <- mallocBytes sz
272320
-- always succeeds so we don't need to check
273-
_ret <- withForeignPtr fptr $ \ptr -> Prim.ecPubkeySerialize ctx buf written ptr flags
321+
_ret <- Prim.ecPubkeySerialize ctx buf written ptr flags
274322
len <- peek written
275323
unsafePackMallocCStringLen (castPtr buf, fromIntegral len)
276324

@@ -286,7 +334,7 @@ importPubKeyXO bs
286334
ret <- Prim.xonlyPubkeyParse ctx outBuf ptr
287335
if isSuccess ret
288336
then Just . PubKeyXO <$> newForeignPtr finalizerFree outBuf
289-
else pure Nothing
337+
else free outBuf $> Nothing
290338

291339

292340
-- | Serializes 'PubKeyXO' to 32 byte @ByteString@
@@ -307,11 +355,11 @@ importSignature bs = unsafePerformIO $
307355
-- compact
308356
| len == 64 -> Prim.ecdsaSignatureParseCompact ctx outBuf inBuf
309357
-- der
310-
| len >= 71 && len <= 73 -> Prim.ecdsaSignatureParseDer ctx outBuf inBuf len
358+
| len >= 69 && len <= 73 -> Prim.ecdsaSignatureParseDer ctx outBuf inBuf len
311359
-- invalid
312360
| otherwise -> pure 0
313361
if isSuccess ret
314-
then Just . Signature <$> newForeignPtr finalizerFree (castPtr outBuf)
362+
then Just . Signature <$> newForeignPtr finalizerFree outBuf
315363
else free outBuf $> Nothing
316364

317365

@@ -330,6 +378,7 @@ exportSignatureDer (Signature fptr) = unsafePerformIO $ do
330378
-- as of Q4'2015 73 byte sigs became nonstandard so we will never create one that big
331379
outBuf <- mallocBytes 72
332380
alloca $ \written -> do
381+
poke written 72
333382
-- always succeeds
334383
_ret <- withForeignPtr fptr $ Prim.ecdsaSignatureSerializeDer ctx outBuf written
335384
len <- peek written
@@ -383,16 +432,16 @@ ecdsaVerify msgHash (PubKeyXY pkFPtr) (Signature sigFPtr) = unsafePerformIO $
383432
ecdsaSign :: SecKey -> ByteString -> Maybe Signature
384433
ecdsaSign (SecKey skFPtr) msgHash
385434
| BS.length msgHash /= 32 = Nothing
386-
| otherwise = unsafePerformIO $
387-
evalContT $ do
388-
skPtr <- ContT (withForeignPtr skFPtr)
389-
(msgHashPtr, _) <- ContT (unsafeUseByteString msgHash)
390-
sigBuf <- lift $ mallocBytes 64
391-
ret <- lift $ Prim.ecdsaSign ctx sigBuf msgHashPtr skPtr Prim.nonceFunctionDefault nullPtr
392-
lift $
393-
if isSuccess ret
394-
then Just . Signature <$> newForeignPtr finalizerFree sigBuf
395-
else free sigBuf $> Nothing
435+
| otherwise = unsafePerformIO . evalContT $ do
436+
skPtr <- ContT (withForeignPtr skFPtr)
437+
(msgHashPtr, _) <- ContT (unsafeUseByteString msgHash)
438+
lift $ do
439+
sigBuf <- mallocBytes 64
440+
entropy <- mallocBytes 32
441+
ret <- Prim.ecdsaSign ctx sigBuf msgHashPtr skPtr Prim.nonceFunctionRfc6979 entropy
442+
if isSuccess ret
443+
then Just . Signature <$> newForeignPtr finalizerFree sigBuf
444+
else free sigBuf $> Nothing
396445

397446

398447
-- | Signs @ByteString@ with 'SecKey' only if @ByteString@ is 32 bytes. Retains ability to compute 'PubKeyXY' from the
@@ -553,8 +602,8 @@ keyPairPubKeyXOTweakAdd KeyPair{..} Tweak{..} = unsafePerformIO . evalContT $ do
553602
keyPairPtr <- ContT (withForeignPtr keyPairFPtr)
554603
tweakPtr <- ContT (withForeignPtr tweakFPtr)
555604
lift $ do
556-
keyPairOut <- (mallocBytes 96)
557-
_ <- (memcpy keyPairOut keyPairPtr 96)
605+
keyPairOut <- mallocBytes 96
606+
_ <- memcpy keyPairOut keyPairPtr 96
558607
ret <- Prim.keypairXonlyTweakAdd ctx keyPairOut tweakPtr
559608
if isSuccess ret
560609
then Just . KeyPair <$> newForeignPtr finalizerFree keyPairOut

src/Crypto/Secp256k1/Gen.hs

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
module Crypto.Secp256k1.Gen where
2+
3+
import Crypto.Secp256k1 (KeyPair, PubKeyXO, PubKeyXY, SecKey, Tweak, derivePubKey, importSecKey, importTweak, keyPairCreate, xyToXO)
4+
import Hedgehog (MonadGen)
5+
import Hedgehog.Gen (bytes, discard)
6+
import Hedgehog.Range (singleton)
7+
8+
9+
secKeyGen :: MonadGen m => m SecKey
10+
secKeyGen = do
11+
bs <- bytes (singleton 32)
12+
maybe discard pure (importSecKey bs)
13+
14+
15+
pubKeyXYGen :: MonadGen m => m PubKeyXY
16+
pubKeyXYGen = derivePubKey <$> secKeyGen
17+
18+
19+
pubKeyXOGen :: MonadGen m => m PubKeyXO
20+
pubKeyXOGen = fst . xyToXO <$> pubKeyXYGen
21+
22+
23+
keyPairGen :: MonadGen m => m KeyPair
24+
keyPairGen = keyPairCreate <$> secKeyGen
25+
26+
27+
tweakGen :: MonadGen m => m Tweak
28+
tweakGen = do
29+
bs <- bytes (singleton 32)
30+
maybe discard pure (importTweak bs)

0 commit comments

Comments
 (0)