Skip to content

Commit 52473c8

Browse files
committed
Add randomness to Schnorr signatures for BIP340 compliance
1 parent 3aaf360 commit 52473c8

File tree

3 files changed

+39
-25
lines changed

3 files changed

+39
-25
lines changed

libsecp256k1.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ library
4747
, hashable >=1.4.2 && <1.5
4848
, hedgehog >=1.2 && <1.5
4949
, memory >=0.14.15 && <1.0
50+
, random >=1.2.1.2 && <1.3
5051
, transformers >=0.4.0.0 && <1.0
5152
default-language: Haskell2010
5253

src/Crypto/Secp256k1.hs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ import Foreign.Storable (Storable (..))
143143
import GHC.Generics (Generic)
144144
import GHC.IO.Handle.Text (memcpy)
145145
import System.IO.Unsafe (unsafePerformIO)
146+
import System.Random (newStdGen, randoms)
146147
import Text.Read (
147148
Lexeme (String),
148149
lexP,
@@ -744,19 +745,21 @@ keyPairPubKeyXOTweakAdd KeyPair{..} Tweak{..} = unsafePerformIO . evalContT $ do
744745

745746
-- | Compute a schnorr signature using a 'KeyPair'. The @ByteString@ must be 32 bytes long to get a @Just@ out of this
746747
-- function
747-
schnorrSign :: KeyPair -> ByteString -> Maybe SchnorrSignature
748+
schnorrSign :: KeyPair -> ByteString -> IO (Maybe SchnorrSignature)
748749
schnorrSign KeyPair{..} bs
749-
| BS.length bs /= 32 = Nothing
750-
| otherwise = unsafePerformIO . evalContT $ do
750+
| BS.length bs /= 32 = pure Nothing
751+
| otherwise = evalContT $ do
751752
(msgHashPtr, _) <- ContT (unsafeUseByteString bs)
752753
keyPairPtr <- ContT (withForeignPtr keyPairFPtr)
753754
lift $ do
754755
sigBuf <- mallocBytes 64
755-
-- TODO: provide randomness here instead of supplying a null pointer
756-
ret <- Prim.schnorrsigSign ctx sigBuf msgHashPtr keyPairPtr nullPtr
757-
if isSuccess ret
758-
then Just . SchnorrSignature <$> newForeignPtr finalizerFree sigBuf
759-
else free sigBuf $> Nothing
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
760763

761764

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

test/Crypto/Secp256k1Prop.hs

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ module Crypto.Secp256k1Prop where
55

66
import Control.Applicative (Applicative (liftA2), empty)
77
import Control.Monad (when)
8+
import Control.Monad.IO.Class (liftIO)
89
import Control.Monad.Trans.Class (lift)
910
import Crypto.Secp256k1
1011
import Crypto.Secp256k1.Gen
@@ -260,9 +261,10 @@ prop_schnorrSignaturesProducedAreValid = property $ do
260261
sk <- forAll secKeyGen
261262
msg <- forAll $ bytes (singleton 32)
262263
let kp = keyPairCreate sk
263-
case schnorrSign kp msg of
264+
sig <- liftIO $ schnorrSign kp msg
265+
case sig of
264266
Nothing -> failure
265-
Just sig -> assert $ schnorrVerify (fst $ keyPairPubKeyXO kp) msg sig
267+
Just s -> assert $ schnorrVerify (fst $ keyPairPubKeyXO kp) msg s
266268

267269

268270
prop_pubKeyCombineTweakIdentity :: Property
@@ -297,9 +299,10 @@ prop_schnorrSignaturesUnforgeable = property $ do
297299
let kp = keyPairCreate sk
298300
pk <- forAll pubKeyXOGen
299301
msg <- forAll $ bytes (singleton 32)
300-
case schnorrSign kp msg of
302+
sig <- liftIO $ schnorrSign kp msg
303+
case sig of
301304
Nothing -> failure
302-
Just sig -> assert . not $ schnorrVerify pk msg sig
305+
Just s -> assert . not $ schnorrVerify pk msg s
303306

304307

305308
newtype Wrapped a = Wrapped {secKey :: a} deriving (Show, Read, Eq)
@@ -342,12 +345,19 @@ prop_derivedCompositeReadShowInvertSignature = derivedCompositeReadShowInvertTem
342345

343346

344347
prop_derivedCompositeReadShowInvertSchnorrSignature :: Property
345-
prop_derivedCompositeReadShowInvertSchnorrSignature = derivedCompositeReadShowInvertTemplate schnorrSignGen
346-
where
347-
schnorrSignGen = do
348-
sk <- secKeyGen
349-
msg <- bytes (singleton 32)
350-
maybe empty pure $ schnorrSign (keyPairCreate sk) msg
348+
prop_derivedCompositeReadShowInvertSchnorrSignature = property $ do
349+
sk <- forAll secKeyGen
350+
let kp = keyPairCreate sk
351+
msg <- forAll $ bytes (singleton 32)
352+
mSig <- liftIO $ schnorrSign kp msg
353+
sig <- maybe failure pure mSig
354+
let a = sig
355+
annotateShow a
356+
annotateShow (length $ show a)
357+
annotateShow (Wrapped a)
358+
case readMaybe (show (Wrapped a)) of
359+
Nothing -> failure
360+
Just x -> x === Wrapped a
351361

352362

353363
prop_derivedCompositeReadShowInvertRecoverableSignature :: Property
@@ -370,7 +380,7 @@ prop_schnorrSignatureParseInvertsSerialize = property $ do
370380
sk <- forAll secKeyGen
371381
msg <- forAll $ bytes (singleton 32)
372382
let kp = keyPairCreate sk
373-
sig <- maybe failure pure $ schnorrSign kp msg
383+
sig <- liftIO $ maybe (error "schnorrSign failed") pure =<< schnorrSign kp msg
374384
let serialized = exportSchnorrSignature sig
375385
annotateShow serialized
376386
annotateShow (BS.length serialized)
@@ -383,21 +393,21 @@ prop_schnorrSignatureValidityPreservedOverSerialization = property $ do
383393
sk <- forAll secKeyGen
384394
msg <- forAll $ bytes (singleton 32)
385395
let kp = keyPairCreate sk
386-
sig <- maybe failure pure $ schnorrSign kp msg
396+
sig <- liftIO $ maybe (error "schnorrSign failed") pure =<< schnorrSign kp msg
387397
let serialized = exportSchnorrSignature sig
388398
let parsed = importSchnorrSignature serialized
389399
parsed === Just sig
390400
assert $ schnorrVerify (fst $ keyPairPubKeyXO kp) msg sig
391401

392402

393-
prop_schnorrSignatureDeterministic :: Property
394-
prop_schnorrSignatureDeterministic = property $ do
403+
prop_schnorrSignatureNonDeterministic :: Property
404+
prop_schnorrSignatureNonDeterministic = property $ do
395405
sk <- forAll secKeyGen
396406
msg <- forAll $ bytes (singleton 32)
397407
let kp = keyPairCreate sk
398-
sig1 <- maybe failure pure $ schnorrSign kp msg
399-
sig2 <- maybe failure pure $ schnorrSign kp msg
400-
sig1 === sig2
408+
sig1 <- liftIO $ maybe (error "schnorrSign failed") pure =<< schnorrSign kp msg
409+
sig2 <- liftIO $ maybe (error "schnorrSign failed") pure =<< schnorrSign kp msg
410+
sig1 /== sig2
401411

402412

403413
tests :: Group

0 commit comments

Comments
 (0)