Skip to content

Commit 3aaf360

Browse files
committed
Introduced SchnorrSignature type with parsing and serialization functions.
resolves #8
1 parent 1e4ebd2 commit 3aaf360

File tree

2 files changed

+98
-11
lines changed

2 files changed

+98
-11
lines changed

src/Crypto/Secp256k1.hs

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ module Crypto.Secp256k1 (
2525
KeyPair,
2626
Signature,
2727
RecoverableSignature,
28+
SchnorrSignature,
2829
Tweak,
2930

3031
-- * Parsing and Serialization
@@ -40,6 +41,8 @@ module Crypto.Secp256k1 (
4041
exportSignatureDer,
4142
importRecoverableSignature,
4243
exportRecoverableSignature,
44+
importSchnorrSignature,
45+
exportSchnorrSignature,
4346
importTweak,
4447

4548
-- * ECDSA Operations
@@ -283,6 +286,28 @@ instance NFData Signature where
283286
rnf Signature{..} = seq signatureFPtr ()
284287

285288

289+
-- | Structure containing Schnorr Signature
290+
newtype SchnorrSignature = SchnorrSignature {schnorrSignatureFPtr :: ForeignPtr Prim.Sig64}
291+
292+
293+
instance Show SchnorrSignature where
294+
show sig = (B8.unpack . encodeBase16) (exportSchnorrSignature sig)
295+
instance Read SchnorrSignature where
296+
readsPrec i cs = case decodeBase16 $ B8.pack token of
297+
Left e -> []
298+
Right a -> maybeToList $ (,rest) <$> importSchnorrSignature a
299+
where
300+
trimmed = dropWhile isSpace cs
301+
(token, rest) = span isAlphaNum trimmed
302+
instance Eq SchnorrSignature where
303+
sig == sig' = unsafePerformIO . evalContT $ do
304+
sigp <- ContT $ withForeignPtr (schnorrSignatureFPtr sig)
305+
sigp' <- ContT $ withForeignPtr (schnorrSignatureFPtr sig')
306+
(EQ ==) <$> lift (memCompare (castPtr sigp) (castPtr sigp') 64)
307+
instance NFData SchnorrSignature where
308+
rnf SchnorrSignature{..} = seq schnorrSignatureFPtr ()
309+
310+
286311
-- | Structure containing Signature AND recovery ID
287312
newtype RecoverableSignature = RecoverableSignature {recoverableSignatureFPtr :: ForeignPtr Prim.RecSig65}
288313

@@ -493,6 +518,23 @@ exportRecoverableSignature RecoverableSignature{..} = unsafePerformIO . evalCont
493518
unsafePackByteString (outBuf, 65)
494519

495520

521+
-- | Parses 'SchnorrSignature' from Schnorr (64 byte) representation
522+
importSchnorrSignature :: ByteString -> Maybe SchnorrSignature
523+
importSchnorrSignature bs
524+
| BS.length bs /= 64 = Nothing
525+
| otherwise = unsafePerformIO $ do
526+
outBuf <- mallocBytes 64
527+
unsafeUseByteString bs $ \(ptr, _) -> do
528+
memcpy outBuf ptr 64
529+
Just . SchnorrSignature <$> newForeignPtr finalizerFree outBuf
530+
531+
532+
-- | Serializes 'SchnorrSignature' to Schnorr (64 byte) representation
533+
exportSchnorrSignature :: SchnorrSignature -> ByteString
534+
exportSchnorrSignature (SchnorrSignature fptr) = unsafePerformIO $
535+
withForeignPtr fptr $ \ptr -> BS.packCStringLen (castPtr ptr, 64)
536+
537+
496538
-- | Parses 'Tweak' from 32 byte @ByteString@. If the @ByteString@ is an invalid 'SecKey' then this will yield @Nothing@
497539
importTweak :: ByteString -> Maybe Tweak
498540
importTweak = fmap (Tweak . castForeignPtr . secKeyFPtr) . importSecKey
@@ -702,7 +744,7 @@ keyPairPubKeyXOTweakAdd KeyPair{..} Tweak{..} = unsafePerformIO . evalContT $ do
702744

703745
-- | Compute a schnorr signature using a 'KeyPair'. The @ByteString@ must be 32 bytes long to get a @Just@ out of this
704746
-- function
705-
schnorrSign :: KeyPair -> ByteString -> Maybe Signature
747+
schnorrSign :: KeyPair -> ByteString -> Maybe SchnorrSignature
706748
schnorrSign KeyPair{..} bs
707749
| BS.length bs /= 32 = Nothing
708750
| otherwise = unsafePerformIO . evalContT $ do
@@ -713,17 +755,17 @@ schnorrSign KeyPair{..} bs
713755
-- TODO: provide randomness here instead of supplying a null pointer
714756
ret <- Prim.schnorrsigSign ctx sigBuf msgHashPtr keyPairPtr nullPtr
715757
if isSuccess ret
716-
then Just . Signature <$> newForeignPtr finalizerFree sigBuf
758+
then Just . SchnorrSignature <$> newForeignPtr finalizerFree sigBuf
717759
else free sigBuf $> Nothing
718760

719761

720762
-- | Verify the authenticity of a schnorr signature. @True@ means the 'Signature' is correct.
721-
schnorrVerify :: PubKeyXO -> ByteString -> Signature -> Bool
722-
schnorrVerify PubKeyXO{..} bs Signature{..} = unsafePerformIO . evalContT $ do
763+
schnorrVerify :: PubKeyXO -> ByteString -> SchnorrSignature -> Bool
764+
schnorrVerify PubKeyXO{..} bs SchnorrSignature{..} = unsafePerformIO . evalContT $ do
723765
pubKeyPtr <- ContT (withForeignPtr pubKeyXOFPtr)
724-
signaturePtr <- ContT (withForeignPtr signatureFPtr)
766+
schnorrSignaturePtr <- ContT (withForeignPtr schnorrSignatureFPtr)
725767
(msgPtr, msgLen) <- ContT (unsafeUseByteString bs)
726-
lift $ isSuccess <$> Prim.schnorrsigSignVerify ctx signaturePtr msgPtr msgLen pubKeyPtr
768+
lift $ isSuccess <$> Prim.schnorrsigSignVerify ctx schnorrSignaturePtr msgPtr msgLen pubKeyPtr
727769

728770

729771
-- | Generate a tagged sha256 digest as specified in BIP340

test/Crypto/Secp256k1Prop.hs

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -333,11 +333,21 @@ prop_derivedCompositeReadShowInvertTweak = derivedCompositeReadShowInvertTemplat
333333

334334

335335
prop_derivedCompositeReadShowInvertSignature :: Property
336-
prop_derivedCompositeReadShowInvertSignature = derivedCompositeReadShowInvertTemplate $ choice [ecdsa, schnorr]
336+
prop_derivedCompositeReadShowInvertSignature = derivedCompositeReadShowInvertTemplate ecdsaSignGen
337337
where
338-
base = liftA2 (,) secKeyGen (bytes (singleton 32))
339-
ecdsa = base >>= maybe empty pure . uncurry ecdsaSign
340-
schnorr = base >>= maybe empty pure . uncurry (schnorrSign . keyPairCreate)
338+
ecdsaSignGen = do
339+
sk <- secKeyGen
340+
msg <- bytes (singleton 32)
341+
maybe empty pure $ ecdsaSign sk msg
342+
343+
344+
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
341351

342352

343353
prop_derivedCompositeReadShowInvertRecoverableSignature :: Property
@@ -355,5 +365,40 @@ prop_eqImportImpliesEqSecKey = property $ do
355365
k0 === k1
356366

357367

368+
prop_schnorrSignatureParseInvertsSerialize :: Property
369+
prop_schnorrSignatureParseInvertsSerialize = property $ do
370+
sk <- forAll secKeyGen
371+
msg <- forAll $ bytes (singleton 32)
372+
let kp = keyPairCreate sk
373+
sig <- maybe failure pure $ schnorrSign kp msg
374+
let serialized = exportSchnorrSignature sig
375+
annotateShow serialized
376+
annotateShow (BS.length serialized)
377+
let parsed = importSchnorrSignature serialized
378+
parsed === Just sig
379+
380+
381+
prop_schnorrSignatureValidityPreservedOverSerialization :: Property
382+
prop_schnorrSignatureValidityPreservedOverSerialization = property $ do
383+
sk <- forAll secKeyGen
384+
msg <- forAll $ bytes (singleton 32)
385+
let kp = keyPairCreate sk
386+
sig <- maybe failure pure $ schnorrSign kp msg
387+
let serialized = exportSchnorrSignature sig
388+
let parsed = importSchnorrSignature serialized
389+
parsed === Just sig
390+
assert $ schnorrVerify (fst $ keyPairPubKeyXO kp) msg sig
391+
392+
393+
prop_schnorrSignatureDeterministic :: Property
394+
prop_schnorrSignatureDeterministic = property $ do
395+
sk <- forAll secKeyGen
396+
msg <- forAll $ bytes (singleton 32)
397+
let kp = keyPairCreate sk
398+
sig1 <- maybe failure pure $ schnorrSign kp msg
399+
sig2 <- maybe failure pure $ schnorrSign kp msg
400+
sig1 === sig2
401+
402+
358403
tests :: Group
359-
tests = $$discover
404+
tests = $$discover

0 commit comments

Comments
 (0)