Skip to content

Commit 10555f6

Browse files
committed
Refactor Schnorr signing for deterministic and non-deterministic options
- Add deterministic and non-deterministic Schnorr signing functions - Update tests to cover new signing functions - Modify schnorrSign to accept optional StdGen for controlled randomness
1 parent 9cf0af8 commit 10555f6

File tree

3 files changed

+82
-28
lines changed

3 files changed

+82
-28
lines changed

libsecp256k1.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,5 +80,6 @@ test-suite spec
8080
, libsecp256k1
8181
, memory >=0.14.15 && <1.0
8282
, monad-par
83+
, random >=1.2.1.2 && <1.3
8384
, transformers >=0.4.0.0 && <1.0
8485
default-language: Haskell2010

src/Crypto/Secp256k1.hs

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ module Crypto.Secp256k1 (
7676

7777
-- * Schnorr Operations
7878
schnorrSign,
79+
schnorrSignDeterministic,
80+
schnorrSignNondeterministic,
7981
schnorrVerify,
8082

8183
-- * Other
@@ -143,7 +145,7 @@ import Foreign.Storable (Storable (..))
143145
import GHC.Generics (Generic)
144146
import GHC.IO.Handle.Text (memcpy)
145147
import System.IO.Unsafe (unsafePerformIO)
146-
import System.Random (newStdGen, randoms)
148+
import System.Random (StdGen, newStdGen, randoms, randomIO)
147149
import Text.Read (
148150
Lexeme (String),
149151
lexP,
@@ -743,23 +745,38 @@ keyPairPubKeyXOTweakAdd KeyPair{..} Tweak{..} = unsafePerformIO . evalContT $ do
743745
else free keyPairOut $> Nothing
744746

745747

746-
-- | Compute a schnorr signature using a 'KeyPair'. The @ByteString@ must be 32 bytes long to get a @Just@ out of this
747-
-- function
748-
schnorrSign :: KeyPair -> ByteString -> IO (Maybe SchnorrSignature)
749-
schnorrSign KeyPair{..} bs
750-
| BS.length bs /= 32 = pure Nothing
751-
| otherwise = evalContT $ do
748+
-- | Compute a schnorr signature using a 'KeyPair'. The @ByteString@ must be 32 bytes long to get
749+
-- a @Just@ out of this function. Optionally takes a 'StdGen' for deterministic signing.
750+
schnorrSign :: Maybe StdGen -> KeyPair -> ByteString -> Maybe SchnorrSignature
751+
schnorrSign mGen KeyPair{..} bs
752+
| BS.length bs /= 32 = Nothing
753+
| otherwise = unsafePerformIO . evalContT $ do
752754
(msgHashPtr, _) <- ContT (unsafeUseByteString bs)
753755
keyPairPtr <- ContT (withForeignPtr keyPairFPtr)
754756
lift $ do
755757
sigBuf <- mallocBytes 64
756-
gen <- newStdGen
757-
let randomBytes = BS.pack $ Prelude.take 32 $ randoms gen
758-
BS.useAsCString randomBytes $ \randomPtr -> do
759-
ret <- Prim.schnorrsigSign ctx sigBuf msgHashPtr keyPairPtr (castPtr randomPtr)
760-
if isSuccess ret
761-
then Just . SchnorrSignature <$> newForeignPtr finalizerFree sigBuf
762-
else free sigBuf $> Nothing
758+
ret <- case mGen of
759+
Just gen -> do
760+
let randomBytes = BS.pack $ Prelude.take 32 $ randoms gen
761+
BS.useAsCStringLen randomBytes $ \(ptr, _) ->
762+
Prim.schnorrsigSign ctx sigBuf msgHashPtr keyPairPtr (castPtr ptr)
763+
Nothing ->
764+
Prim.schnorrsigSign ctx sigBuf msgHashPtr keyPairPtr nullPtr
765+
if isSuccess ret
766+
then Just . SchnorrSignature <$> newForeignPtr finalizerFree sigBuf
767+
else do
768+
free sigBuf
769+
return Nothing
770+
771+
772+
-- | Compute a deterministic schnorr signature using a 'KeyPair'.
773+
schnorrSignDeterministic :: KeyPair -> ByteString -> Maybe SchnorrSignature
774+
schnorrSignDeterministic = schnorrSign Nothing
775+
776+
777+
-- | Compute a non-deterministic schnorr signature using a 'KeyPair'.
778+
schnorrSignNondeterministic :: KeyPair -> ByteString -> IO (Maybe SchnorrSignature)
779+
schnorrSignNondeterministic kp bs = newStdGen >>= \gen -> pure $ schnorrSign (Just gen) kp bs
763780

764781

765782
-- | Verify the authenticity of a schnorr signature. @True@ means the 'Signature' is correct.

test/Crypto/Secp256k1Prop.hs

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import Hedgehog
1717
import Hedgehog.Gen hiding (discard, maybe, prune)
1818
import Hedgehog.Range (linear, singleton)
1919
import Text.Read (readMaybe)
20+
import System.Random (StdGen, mkStdGen)
2021

2122

2223
prop_secKeyReadInvertsShow :: Property
@@ -261,10 +262,17 @@ prop_schnorrSignaturesProducedAreValid = property $ do
261262
sk <- forAll secKeyGen
262263
msg <- forAll $ bytes (singleton 32)
263264
let kp = keyPairCreate sk
264-
sig <- liftIO $ schnorrSign kp msg
265-
case sig of
266-
Nothing -> failure
267-
Just s -> assert $ schnorrVerify (fst $ keyPairPubKeyXO kp) msg s
265+
sig <- maybe failure pure $ schnorrSignDeterministic kp msg
266+
assert $ schnorrVerify (fst $ keyPairPubKeyXO kp) msg sig
267+
268+
269+
prop_schnorrSignaturesProducedAreValidNonDeterministic :: Property
270+
prop_schnorrSignaturesProducedAreValidNonDeterministic = property $ do
271+
sk <- forAll secKeyGen
272+
msg <- forAll $ bytes (singleton 32)
273+
let kp = keyPairCreate sk
274+
sig <- liftIO $ maybe (error "Failed to sign") pure =<< schnorrSignNondeterministic kp msg
275+
assert $ schnorrVerify (fst $ keyPairPubKeyXO kp) msg sig
268276

269277

270278
prop_pubKeyCombineTweakIdentity :: Property
@@ -299,10 +307,18 @@ prop_schnorrSignaturesUnforgeable = property $ do
299307
let kp = keyPairCreate sk
300308
pk <- forAll pubKeyXOGen
301309
msg <- forAll $ bytes (singleton 32)
302-
sig <- liftIO $ schnorrSign kp msg
303-
case sig of
304-
Nothing -> failure
305-
Just s -> assert . not $ schnorrVerify pk msg s
310+
sig <- maybe failure pure $ schnorrSignDeterministic kp msg
311+
assert . not $ schnorrVerify pk msg sig
312+
313+
314+
prop_schnorrSignaturesUnforgeableNonDeterministic :: Property
315+
prop_schnorrSignaturesUnforgeableNonDeterministic = property $ do
316+
sk <- forAll secKeyGen
317+
let kp = keyPairCreate sk
318+
pk <- forAll pubKeyXOGen
319+
msg <- forAll $ bytes (singleton 32)
320+
sig <- liftIO $ maybe (error "Failed to sign") pure =<< schnorrSignNondeterministic kp msg
321+
assert . not $ schnorrVerify pk msg sig
306322

307323

308324
newtype Wrapped a = Wrapped {secKey :: a} deriving (Show, Read, Eq)
@@ -349,8 +365,7 @@ prop_derivedCompositeReadShowInvertSchnorrSignature = property $ do
349365
sk <- forAll secKeyGen
350366
let kp = keyPairCreate sk
351367
msg <- forAll $ bytes (singleton 32)
352-
mSig <- liftIO $ schnorrSign kp msg
353-
sig <- maybe failure pure mSig
368+
sig <- maybe failure pure $ schnorrSignDeterministic kp msg
354369
let a = sig
355370
annotateShow a
356371
annotateShow (length $ show a)
@@ -380,7 +395,7 @@ prop_schnorrSignatureParseInvertsSerialize = property $ do
380395
sk <- forAll secKeyGen
381396
msg <- forAll $ bytes (singleton 32)
382397
let kp = keyPairCreate sk
383-
sig <- liftIO $ maybe (error "schnorrSign failed") pure =<< schnorrSign kp msg
398+
sig <- maybe failure pure $ schnorrSignDeterministic kp msg
384399
let serialized = exportSchnorrSignature sig
385400
annotateShow serialized
386401
annotateShow (BS.length serialized)
@@ -393,22 +408,43 @@ prop_schnorrSignatureValidityPreservedOverSerialization = property $ do
393408
sk <- forAll secKeyGen
394409
msg <- forAll $ bytes (singleton 32)
395410
let kp = keyPairCreate sk
396-
sig <- liftIO $ maybe (error "schnorrSign failed") pure =<< schnorrSign kp msg
411+
sig <- maybe failure pure $ schnorrSignDeterministic kp msg
397412
let serialized = exportSchnorrSignature sig
398413
let parsed = importSchnorrSignature serialized
399414
parsed === Just sig
400415
assert $ schnorrVerify (fst $ keyPairPubKeyXO kp) msg sig
401416

402417

418+
prop_schnorrSignatureDeterministic :: Property
419+
prop_schnorrSignatureDeterministic = property $ do
420+
sk <- forAll secKeyGen
421+
msg <- forAll $ bytes (singleton 32)
422+
let kp = keyPairCreate sk
423+
sig1 <- maybe failure pure $ schnorrSignDeterministic kp msg
424+
sig2 <- maybe failure pure $ schnorrSignDeterministic kp msg
425+
sig1 === sig2
426+
427+
403428
prop_schnorrSignatureNonDeterministic :: Property
404429
prop_schnorrSignatureNonDeterministic = property $ do
405430
sk <- forAll secKeyGen
406431
msg <- forAll $ bytes (singleton 32)
407432
let kp = keyPairCreate sk
408-
sig1 <- liftIO $ maybe (error "schnorrSign failed") pure =<< schnorrSign kp msg
409-
sig2 <- liftIO $ maybe (error "schnorrSign failed") pure =<< schnorrSign kp msg
433+
sig1 <- liftIO $ maybe (error "Failed to sign") pure =<< schnorrSignNondeterministic kp msg
434+
sig2 <- liftIO $ maybe (error "Failed to sign") pure =<< schnorrSignNondeterministic kp msg
410435
sig1 /== sig2
411436

412437

438+
prop_schnorrSignWithStdGen :: Property
439+
prop_schnorrSignWithStdGen = property $ do
440+
sk <- forAll secKeyGen
441+
msg <- forAll $ bytes (singleton 32)
442+
let kp = keyPairCreate sk
443+
stdGen <- forAll $ mkStdGen <$> integral (linear 0 maxBound)
444+
sig1 <- maybe failure pure $ schnorrSign (Just stdGen) kp msg
445+
sig2 <- maybe failure pure $ schnorrSign (Just stdGen) kp msg
446+
sig1 === sig2
447+
448+
413449
tests :: Group
414450
tests = $$discover

0 commit comments

Comments
 (0)