mirror of
https://github.com/ilyakooo0/secp256k1-haskell.git
synced 2024-10-26 12:56:14 +03:00
BIP-340 (Schnorr) support (#26)
Compatible with jonasnick/secp256k1@372c4555ca
This commit is contained in:
parent
ae2c67e35e
commit
76f86ad0d6
@ -30,6 +30,8 @@ library:
|
|||||||
when:
|
when:
|
||||||
- condition: flag(ecdh)
|
- condition: flag(ecdh)
|
||||||
cpp-options: -DECDH
|
cpp-options: -DECDH
|
||||||
|
- condition: flag(schnorr)
|
||||||
|
cpp-options: -DSCHNORR
|
||||||
tests:
|
tests:
|
||||||
spec:
|
spec:
|
||||||
main: Spec.hs
|
main: Spec.hs
|
||||||
@ -47,3 +49,7 @@ flags:
|
|||||||
description: "Enable (experimental) ECDH APIs"
|
description: "Enable (experimental) ECDH APIs"
|
||||||
manual: true
|
manual: true
|
||||||
default: false
|
default: false
|
||||||
|
schnorr:
|
||||||
|
description: "Enable BIP-340 (Schnorr) APIs"
|
||||||
|
manual: true
|
||||||
|
default: false
|
||||||
|
@ -63,6 +63,21 @@ module Crypto.Secp256k1
|
|||||||
-- * Diffie Hellman
|
-- * Diffie Hellman
|
||||||
, ecdh
|
, ecdh
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef SCHNORR
|
||||||
|
, XOnlyPubKey
|
||||||
|
, SchnorrSig
|
||||||
|
, signMsgSchnorr
|
||||||
|
, exportSchnorrSig
|
||||||
|
, importSchnorrSig
|
||||||
|
, exportXOnlyPubKey
|
||||||
|
, importXOnlyPubKey
|
||||||
|
, verifyMsgSchnorr
|
||||||
|
, deriveXOnlyPubKey
|
||||||
|
, schnorrTweakAddPubKey
|
||||||
|
, schnorrTweakAddSecKey
|
||||||
|
, testTweakXOnlyPubKey
|
||||||
|
#endif
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Control.Monad (replicateM, unless, (<=<))
|
import Control.Monad (replicateM, unless, (<=<))
|
||||||
@ -79,6 +94,7 @@ import Foreign (ForeignPtr, alloca, allocaArray,
|
|||||||
allocaBytes, mallocForeignPtr,
|
allocaBytes, mallocForeignPtr,
|
||||||
nullFunPtr, nullPtr, peek, poke,
|
nullFunPtr, nullPtr, peek, poke,
|
||||||
pokeArray, withForeignPtr)
|
pokeArray, withForeignPtr)
|
||||||
|
import Foreign.C (CInt)
|
||||||
import System.IO.Unsafe (unsafePerformIO)
|
import System.IO.Unsafe (unsafePerformIO)
|
||||||
import Test.QuickCheck (Arbitrary (..),
|
import Test.QuickCheck (Arbitrary (..),
|
||||||
arbitraryBoundedRandom, suchThat)
|
arbitraryBoundedRandom, suchThat)
|
||||||
@ -94,6 +110,10 @@ newtype Sig = Sig (ForeignPtr Sig64)
|
|||||||
newtype SecKey = SecKey (ForeignPtr SecKey32)
|
newtype SecKey = SecKey (ForeignPtr SecKey32)
|
||||||
newtype Tweak = Tweak (ForeignPtr Tweak32)
|
newtype Tweak = Tweak (ForeignPtr Tweak32)
|
||||||
newtype RecSig = RecSig (ForeignPtr RecSig65)
|
newtype RecSig = RecSig (ForeignPtr RecSig65)
|
||||||
|
#ifdef SCHNORR
|
||||||
|
newtype XOnlyPubKey = XOnlyPubKey (ForeignPtr XOnlyPubKey64)
|
||||||
|
newtype SchnorrSig = SchnorrSig (ForeignPtr SchnorrSig64)
|
||||||
|
#endif
|
||||||
|
|
||||||
instance NFData PubKey where
|
instance NFData PubKey where
|
||||||
rnf (PubKey p) = p `seq` ()
|
rnf (PubKey p) = p `seq` ()
|
||||||
@ -232,6 +252,50 @@ instance Eq SecKey where
|
|||||||
instance Eq Tweak where
|
instance Eq Tweak where
|
||||||
ft1 == ft2 = getTweak ft1 == getTweak ft2
|
ft1 == ft2 = getTweak ft1 == getTweak ft2
|
||||||
|
|
||||||
|
#ifdef SCHNORR
|
||||||
|
instance NFData SchnorrSig where
|
||||||
|
rnf (SchnorrSig p) = p `seq` ()
|
||||||
|
|
||||||
|
instance Show SchnorrSig where
|
||||||
|
showsPrec _ = shows . B16.encode . exportSchnorrSig
|
||||||
|
|
||||||
|
instance Read SchnorrSig where
|
||||||
|
readPrec = parens $ do
|
||||||
|
String str <- lexP
|
||||||
|
maybe pfail return $ importSchnorrSig =<< decodeHex str
|
||||||
|
|
||||||
|
instance Eq SchnorrSig where
|
||||||
|
fg1 == fg2 = exportSchnorrSig fg1 == exportSchnorrSig fg2
|
||||||
|
|
||||||
|
instance IsString SchnorrSig where
|
||||||
|
fromString = fromMaybe e . (importSchnorrSig <=< decodeHex) where
|
||||||
|
e = error "Could not decode Schnorr signature from hex string"
|
||||||
|
|
||||||
|
instance Hashable SchnorrSig where
|
||||||
|
i `hashWithSalt` s = i `hashWithSalt` exportSchnorrSig s
|
||||||
|
|
||||||
|
instance NFData XOnlyPubKey where
|
||||||
|
rnf (XOnlyPubKey p) = p `seq` ()
|
||||||
|
|
||||||
|
instance Show XOnlyPubKey where
|
||||||
|
showsPrec _ = shows . B16.encode . exportXOnlyPubKey
|
||||||
|
|
||||||
|
instance Read XOnlyPubKey where
|
||||||
|
readPrec = do
|
||||||
|
String str <- lexP
|
||||||
|
maybe pfail return $ importXOnlyPubKey =<< decodeHex str
|
||||||
|
|
||||||
|
instance Eq XOnlyPubKey where
|
||||||
|
fp1 == fp2 = getXOnlyPubKey fp1 == getXOnlyPubKey fp2
|
||||||
|
|
||||||
|
instance IsString XOnlyPubKey where
|
||||||
|
fromString = fromMaybe e . (importXOnlyPubKey <=< decodeHex) where
|
||||||
|
e = error "Could not decode public key from hex string"
|
||||||
|
|
||||||
|
instance Hashable XOnlyPubKey where
|
||||||
|
i `hashWithSalt` k = i `hashWithSalt` exportXOnlyPubKey k
|
||||||
|
#endif
|
||||||
|
|
||||||
-- | Import 32-byte 'ByteString' as 'Msg'.
|
-- | Import 32-byte 'ByteString' as 'Msg'.
|
||||||
msg :: ByteString -> Maybe Msg
|
msg :: ByteString -> Maybe Msg
|
||||||
msg bs
|
msg bs
|
||||||
@ -366,6 +430,7 @@ derivePubKey (SecKey fk) = withContext $ \ctx -> withForeignPtr fk $ \k -> do
|
|||||||
unless (isSuccess ret) $ error "could not compute public key"
|
unless (isSuccess ret) $ error "could not compute public key"
|
||||||
return $ PubKey fp
|
return $ PubKey fp
|
||||||
|
|
||||||
|
|
||||||
-- | Add tweak to secret key.
|
-- | Add tweak to secret key.
|
||||||
tweakAddSecKey :: SecKey -> Tweak -> Maybe SecKey
|
tweakAddSecKey :: SecKey -> Tweak -> Maybe SecKey
|
||||||
tweakAddSecKey (SecKey fk) (Tweak ft) = withContext $ \ctx ->
|
tweakAddSecKey (SecKey fk) (Tweak ft) = withContext $ \ctx ->
|
||||||
@ -507,6 +572,102 @@ ecdh (PubKey pk) (SecKey sk) = withContext $ \ctx ->
|
|||||||
size = 32
|
size = 32
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef SCHNORR
|
||||||
|
-- Get 64-byte x-only public key.
|
||||||
|
getXOnlyPubKey :: XOnlyPubKey -> ByteString
|
||||||
|
getXOnlyPubKey (XOnlyPubKey fp) =
|
||||||
|
fromShort $ getXOnlyPubKey64 $ unsafePerformIO $ withForeignPtr fp peek
|
||||||
|
|
||||||
|
-- | Add tweak to public key. Tweak is multiplied first by G to obtain a point.
|
||||||
|
schnorrTweakAddPubKey :: XOnlyPubKey -> Tweak -> Maybe (XOnlyPubKey, CInt)
|
||||||
|
schnorrTweakAddPubKey (XOnlyPubKey fp) (Tweak ft) = withContext $ \ctx ->
|
||||||
|
withForeignPtr fp $ \p -> withForeignPtr ft $ \t -> alloca $ \is_negated -> do
|
||||||
|
fp' <- mallocForeignPtr
|
||||||
|
ret <- withForeignPtr fp' $ \p' -> do
|
||||||
|
pub <- peek p
|
||||||
|
poke p' pub
|
||||||
|
schnorrPubKeyTweakAdd ctx p' is_negated t
|
||||||
|
peeked_is_negated <- peek is_negated
|
||||||
|
if isSuccess ret then return $ Just $ (XOnlyPubKey fp', peeked_is_negated) else return Nothing
|
||||||
|
|
||||||
|
-- | Add tweak to secret key.
|
||||||
|
schnorrTweakAddSecKey :: SecKey -> Tweak -> Maybe SecKey
|
||||||
|
schnorrTweakAddSecKey (SecKey fk) (Tweak ft) = withContext $ \ctx ->
|
||||||
|
withForeignPtr fk $ \k -> withForeignPtr ft $ \t -> do
|
||||||
|
fk' <- mallocForeignPtr
|
||||||
|
ret <- withForeignPtr fk' $ \k' -> do
|
||||||
|
key <- peek k
|
||||||
|
poke k' key
|
||||||
|
schnorrSecKeyTweakAdd ctx k' t
|
||||||
|
if isSuccess ret then return $ Just $ SecKey fk' else return Nothing
|
||||||
|
|
||||||
|
signMsgSchnorr :: SecKey -> Msg -> SchnorrSig
|
||||||
|
signMsgSchnorr (SecKey fk) (Msg fm) = withContext $ \ctx ->
|
||||||
|
withForeignPtr fk $ \k -> withForeignPtr fm $ \m -> do
|
||||||
|
fg <- mallocForeignPtr
|
||||||
|
ret <- withForeignPtr fg $ \g -> schnorrSign ctx g m k nullFunPtr nullPtr
|
||||||
|
unless (isSuccess ret) $ error "could not schnorr-sign message"
|
||||||
|
return $ SchnorrSig fg
|
||||||
|
|
||||||
|
exportSchnorrSig :: SchnorrSig -> ByteString
|
||||||
|
exportSchnorrSig (SchnorrSig fg) = withContext $ \ctx ->
|
||||||
|
withForeignPtr fg $ \g -> allocaBytes 64 $ \o -> do
|
||||||
|
ret <- signatureSerializeSchnorr ctx o g
|
||||||
|
unless (isSuccess ret) $ error "could not serialize schnorr signature"
|
||||||
|
packByteString (o, 64)
|
||||||
|
|
||||||
|
importXOnlyPubKey :: ByteString -> Maybe XOnlyPubKey
|
||||||
|
importXOnlyPubKey bs
|
||||||
|
| BS.length bs == 32 = withContext $ \ctx -> do
|
||||||
|
fp <- mallocForeignPtr
|
||||||
|
ret <- withForeignPtr fp $ \pfp -> useByteString bs $ \(inp, _) ->
|
||||||
|
schnorrXOnlyPubKeyParse ctx pfp inp
|
||||||
|
if isSuccess ret
|
||||||
|
then return $ Just $ XOnlyPubKey fp
|
||||||
|
else return Nothing
|
||||||
|
| otherwise = Nothing
|
||||||
|
|
||||||
|
importSchnorrSig :: ByteString -> Maybe SchnorrSig
|
||||||
|
importSchnorrSig bs
|
||||||
|
| BS.length bs == 64 = withContext $ \ctx -> do
|
||||||
|
fp <- mallocForeignPtr
|
||||||
|
ret <- withForeignPtr fp $ \pfp -> useByteString bs $ \(inp, _) ->
|
||||||
|
schnorrSignatureParse ctx pfp inp
|
||||||
|
if isSuccess ret
|
||||||
|
then return $ Just $ SchnorrSig fp
|
||||||
|
else return Nothing
|
||||||
|
| otherwise = Nothing
|
||||||
|
|
||||||
|
verifyMsgSchnorr :: XOnlyPubKey -> SchnorrSig -> Msg -> Bool
|
||||||
|
verifyMsgSchnorr (XOnlyPubKey fp) (SchnorrSig fg) (Msg fm) = withContext $ \ctx ->
|
||||||
|
withForeignPtr fp $ \p -> withForeignPtr fg $ \g ->
|
||||||
|
withForeignPtr fm $ \m -> isSuccess <$> schnorrSignatureVerify ctx g m p
|
||||||
|
|
||||||
|
exportXOnlyPubKey :: XOnlyPubKey -> ByteString
|
||||||
|
exportXOnlyPubKey (XOnlyPubKey pub) = withContext $ \ctx ->
|
||||||
|
withForeignPtr pub $ \p -> allocaBytes 32 $ \o -> do
|
||||||
|
ret <- schnorrPubKeySerialize ctx o p
|
||||||
|
unless (isSuccess ret) $ error "could not serialize x-only public key"
|
||||||
|
packByteString (o, 32)
|
||||||
|
|
||||||
|
deriveXOnlyPubKey :: SecKey -> XOnlyPubKey
|
||||||
|
deriveXOnlyPubKey (SecKey fk) = withContext $ \ctx -> withForeignPtr fk $ \k -> do
|
||||||
|
fp <- mallocForeignPtr
|
||||||
|
ret <- withForeignPtr fp $ \p -> schnorrXOnlyPubKeyCreate ctx p k
|
||||||
|
unless (isSuccess ret) $ error "could not derive x-only public key"
|
||||||
|
return $ XOnlyPubKey fp
|
||||||
|
|
||||||
|
testTweakXOnlyPubKey :: XOnlyPubKey -> CInt -> XOnlyPubKey -> Tweak -> Bool
|
||||||
|
testTweakXOnlyPubKey (XOnlyPubKey fp) is_negated (XOnlyPubKey internal) (Tweak ft) =
|
||||||
|
withContext $ \ctx ->
|
||||||
|
withForeignPtr fp $ \p ->
|
||||||
|
withForeignPtr internal $ \internalp ->
|
||||||
|
withForeignPtr ft $ \t -> do
|
||||||
|
ret <- xOnlyPubKeyTweakTest ctx p is_negated internalp t
|
||||||
|
return $ isSuccess ret
|
||||||
|
-- End of Schnorr block
|
||||||
|
#endif
|
||||||
|
|
||||||
instance Arbitrary Msg where
|
instance Arbitrary Msg where
|
||||||
arbitrary = gen_msg
|
arbitrary = gen_msg
|
||||||
where
|
where
|
||||||
@ -524,3 +685,10 @@ instance Arbitrary PubKey where
|
|||||||
arbitrary = do
|
arbitrary = do
|
||||||
key <- arbitrary
|
key <- arbitrary
|
||||||
return $ derivePubKey key
|
return $ derivePubKey key
|
||||||
|
|
||||||
|
#ifdef SCHNORR
|
||||||
|
instance Arbitrary XOnlyPubKey where
|
||||||
|
arbitrary = do
|
||||||
|
key <- arbitrary
|
||||||
|
return $ deriveXOnlyPubKey key
|
||||||
|
#endif
|
||||||
|
@ -81,6 +81,14 @@ newtype SerFlags = SerFlags { getSerFlags :: CUInt }
|
|||||||
newtype Ret = Ret { getRet :: CInt }
|
newtype Ret = Ret { getRet :: CInt }
|
||||||
deriving (Read, Show, Eq, Ord, Generic, NFData)
|
deriving (Read, Show, Eq, Ord, Generic, NFData)
|
||||||
|
|
||||||
|
#ifdef SCHNORR
|
||||||
|
newtype XOnlyPubKey64 = XOnlyPubKey64 { getXOnlyPubKey64 :: ShortByteString }
|
||||||
|
deriving (Read, Show, Eq, Ord, Generic, NFData)
|
||||||
|
|
||||||
|
newtype SchnorrSig64 = SchnorrSig64 { getSchnorrSig64 :: ShortByteString }
|
||||||
|
deriving (Read, Show, Eq, Ord, Generic, NFData)
|
||||||
|
#endif
|
||||||
|
|
||||||
-- | Nonce32-generating function
|
-- | Nonce32-generating function
|
||||||
type NonceFunction a
|
type NonceFunction a
|
||||||
= Ptr Nonce32
|
= Ptr Nonce32
|
||||||
@ -113,6 +121,22 @@ useByteString bs f =
|
|||||||
packByteString :: (Ptr CUChar, CSize) -> IO ByteString
|
packByteString :: (Ptr CUChar, CSize) -> IO ByteString
|
||||||
packByteString (b, l) = BS.packCStringLen (castPtr b, fromIntegral l)
|
packByteString (b, l) = BS.packCStringLen (castPtr b, fromIntegral l)
|
||||||
|
|
||||||
|
#if SCHNORR
|
||||||
|
instance Storable XOnlyPubKey64 where
|
||||||
|
sizeOf _ = 64
|
||||||
|
alignment _ = 1
|
||||||
|
peek p = XOnlyPubKey64 . toShort <$> packByteString (castPtr p, 64)
|
||||||
|
poke p (XOnlyPubKey64 k) = useByteString (fromShort k) $
|
||||||
|
\(b, _) -> copyArray (castPtr p) b 64
|
||||||
|
|
||||||
|
instance Storable SchnorrSig64 where
|
||||||
|
sizeOf _ = 64
|
||||||
|
alignment _ = 1
|
||||||
|
peek p = SchnorrSig64 . toShort <$> packByteString (castPtr p, 64)
|
||||||
|
poke p (SchnorrSig64 k) = useByteString (fromShort k) $
|
||||||
|
\(b, _) -> copyArray (castPtr p) b 64
|
||||||
|
#endif
|
||||||
|
|
||||||
instance Storable PubKey64 where
|
instance Storable PubKey64 where
|
||||||
sizeOf _ = 64
|
sizeOf _ = 64
|
||||||
alignment _ = 1
|
alignment _ = 1
|
||||||
@ -484,6 +508,98 @@ foreign import ccall
|
|||||||
-> Ptr Msg32
|
-> Ptr Msg32
|
||||||
-> IO Ret
|
-> IO Ret
|
||||||
|
|
||||||
|
#ifdef SCHNORR
|
||||||
|
foreign import ccall
|
||||||
|
"secp256k1.h secp256k1_xonly_pubkey_tweak_add"
|
||||||
|
schnorrPubKeyTweakAdd
|
||||||
|
:: Ptr Ctx
|
||||||
|
-> Ptr XOnlyPubKey64
|
||||||
|
-> Ptr CInt
|
||||||
|
-> Ptr Tweak32
|
||||||
|
-> IO Ret
|
||||||
|
|
||||||
|
foreign import ccall
|
||||||
|
"secp256k1.h secp256k1_xonly_seckey_tweak_add"
|
||||||
|
schnorrSecKeyTweakAdd
|
||||||
|
:: Ptr Ctx
|
||||||
|
-> Ptr SecKey32
|
||||||
|
-> Ptr Tweak32
|
||||||
|
-> IO Ret
|
||||||
|
|
||||||
|
foreign import ccall
|
||||||
|
"secp256k1.h secp256k1_schnorrsig_serialize"
|
||||||
|
signatureSerializeSchnorr
|
||||||
|
:: Ptr Ctx
|
||||||
|
-> Ptr CUChar -- ^ array for encoded signature, must be large enough
|
||||||
|
-> Ptr SchnorrSig64
|
||||||
|
-> IO Ret
|
||||||
|
|
||||||
|
foreign import ccall
|
||||||
|
"secp256k1.h secp256k1_schnorrsig_sign"
|
||||||
|
schnorrSign
|
||||||
|
:: Ptr Ctx
|
||||||
|
-> Ptr SchnorrSig64
|
||||||
|
-> Ptr Msg32
|
||||||
|
-> Ptr SecKey32
|
||||||
|
-- TODO
|
||||||
|
-- This is actually an "extended nonce function" in the C code. So this signature is broken,
|
||||||
|
-- but we pass a nullFunPtr (and this module is Internal), so it doesn't matter right now.
|
||||||
|
-> FunPtr (NonceFunction a)
|
||||||
|
-> Ptr a -- ^ nonce data
|
||||||
|
-> IO Ret
|
||||||
|
|
||||||
|
foreign import ccall
|
||||||
|
"secp256k1.h secp256k1_xonly_pubkey_tweak_test"
|
||||||
|
xOnlyPubKeyTweakTest
|
||||||
|
:: Ptr Ctx
|
||||||
|
-> Ptr XOnlyPubKey64 -- output_pubkey
|
||||||
|
-> CInt -- is_negated
|
||||||
|
-> Ptr XOnlyPubKey64 -- internal_pubkey
|
||||||
|
-> Ptr Tweak32
|
||||||
|
-> IO Ret
|
||||||
|
|
||||||
|
foreign import ccall
|
||||||
|
"secp256k1.h secp256k1_xonly_pubkey_serialize"
|
||||||
|
schnorrPubKeySerialize
|
||||||
|
:: Ptr Ctx
|
||||||
|
-> Ptr CUChar -- 32 bytes output buffer
|
||||||
|
-> Ptr XOnlyPubKey64
|
||||||
|
-> IO Ret
|
||||||
|
|
||||||
|
foreign import ccall
|
||||||
|
"secp256k1.h secp256k1_schnorrsig_verify"
|
||||||
|
schnorrSignatureVerify
|
||||||
|
:: Ptr Ctx
|
||||||
|
-> Ptr SchnorrSig64
|
||||||
|
-> Ptr Msg32
|
||||||
|
-> Ptr XOnlyPubKey64
|
||||||
|
-> IO Ret
|
||||||
|
|
||||||
|
foreign import ccall
|
||||||
|
"secp256k1.h secp256k1_schnorrsig_parse"
|
||||||
|
schnorrSignatureParse
|
||||||
|
:: Ptr Ctx
|
||||||
|
-> Ptr SchnorrSig64 -- out
|
||||||
|
-> Ptr CUChar -- in
|
||||||
|
-> IO Ret
|
||||||
|
|
||||||
|
foreign import ccall
|
||||||
|
"secp256k1.h secp256k1_xonly_pubkey_parse"
|
||||||
|
schnorrXOnlyPubKeyParse
|
||||||
|
:: Ptr Ctx
|
||||||
|
-> Ptr XOnlyPubKey64 -- out
|
||||||
|
-> Ptr CUChar -- in
|
||||||
|
-> IO Ret
|
||||||
|
|
||||||
|
foreign import ccall
|
||||||
|
"secp256k1.h secp256k1_xonly_pubkey_create"
|
||||||
|
schnorrXOnlyPubKeyCreate
|
||||||
|
:: Ptr Ctx
|
||||||
|
-> Ptr XOnlyPubKey64
|
||||||
|
-> Ptr SecKey32
|
||||||
|
-> IO Ret
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef ECDH
|
#ifdef ECDH
|
||||||
foreign import ccall
|
foreign import ccall
|
||||||
"secp256k1_ecdh.h secp256k1_ecdh"
|
"secp256k1_ecdh.h secp256k1_ecdh"
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
module Crypto.Secp256k1Spec (spec) where
|
module Crypto.Secp256k1Spec (spec) where
|
||||||
|
|
||||||
import Crypto.Secp256k1
|
import Crypto.Secp256k1
|
||||||
|
import qualified Data.ByteString as BS
|
||||||
import qualified Data.ByteString.Base16 as B16
|
import qualified Data.ByteString.Base16 as B16
|
||||||
import qualified Data.ByteString.Char8 as B8
|
import qualified Data.ByteString.Char8 as B8
|
||||||
import Data.Maybe (fromMaybe)
|
import Data.Maybe (fromMaybe)
|
||||||
@ -57,6 +58,28 @@ spec = do
|
|||||||
describe "ecdh" $ do
|
describe "ecdh" $ do
|
||||||
it "computes dh secret" $ property $ computeDhSecret
|
it "computes dh secret" $ property $ computeDhSecret
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef SCHNORR
|
||||||
|
describe "schnorr (bip-340)" $ do
|
||||||
|
it "validates test vector 0" $ property bip340Vector0
|
||||||
|
it "rejects test vector 5" $ property $
|
||||||
|
failingVectorToAssertion InvalidPubKey
|
||||||
|
(
|
||||||
|
hexToBytes "eefdea4cdb677750a420fee807eacf21eb9898ae79b9768766e4faa04a2d4a34",
|
||||||
|
hexToBytes "243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89",
|
||||||
|
hexToBytes "667c2f778e0616e611bd0c14b8a600c5884551701a949ef0ebfd72d452d64e844160bcfc3f466ecb8facd19ade57d8699d74e7207d78c6aedc3799b52a8e0598"
|
||||||
|
)
|
||||||
|
it "rejects test vector 6" $ property $
|
||||||
|
failingVectorToAssertion InvalidSig
|
||||||
|
(
|
||||||
|
hexToBytes "dff1d77f2a671c5f36183726db2341be58feae1da2deced843240f7b502ba659",
|
||||||
|
hexToBytes "243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89",
|
||||||
|
hexToBytes "f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9935554d1aa5f0374e5cdaacb3925035c7c169b27c4426df0a6b19af3baeab138"
|
||||||
|
)
|
||||||
|
it "makes a valid taproot key spend signature" $ property taprootKeySpend
|
||||||
|
#endif
|
||||||
|
|
||||||
|
hexToBytes :: String -> BS.ByteString
|
||||||
|
hexToBytes = fst . B16.decode . B8.pack
|
||||||
|
|
||||||
isStringPubKey :: (PubKey, Bool) -> Bool
|
isStringPubKey :: (PubKey, Bool) -> Bool
|
||||||
isStringPubKey (k, c) = k == fromString (cs hex) where
|
isStringPubKey (k, c) = k == fromString (cs hex) where
|
||||||
@ -178,12 +201,12 @@ tweakAddSecKeyTest =
|
|||||||
assertEqual "tweaked keys match" expected tweaked
|
assertEqual "tweaked keys match" expected tweaked
|
||||||
where
|
where
|
||||||
tweaked = do
|
tweaked = do
|
||||||
key <- secKey $ fst $ B16.decode $ B8.pack
|
key <- secKey $ hexToBytes
|
||||||
"f65255094d7773ed8dd417badc9fc045c1f80fdc5b2d25172b031ce6933e039a"
|
"f65255094d7773ed8dd417badc9fc045c1f80fdc5b2d25172b031ce6933e039a"
|
||||||
twk <- tweak $ fst $ B16.decode $ B8.pack
|
twk <- tweak $ hexToBytes
|
||||||
"f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
|
"f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
|
||||||
tweakAddSecKey key twk
|
tweakAddSecKey key twk
|
||||||
expected = secKey $ fst $ B16.decode $ B8.pack
|
expected = secKey $ hexToBytes
|
||||||
"ec1e3ce1cefa18a671d51125e2b249688d934b0e28f5d1665384d9b02f929059"
|
"ec1e3ce1cefa18a671d51125e2b249688d934b0e28f5d1665384d9b02f929059"
|
||||||
|
|
||||||
tweakMulSecKeyTest :: Assertion
|
tweakMulSecKeyTest :: Assertion
|
||||||
@ -191,12 +214,12 @@ tweakMulSecKeyTest =
|
|||||||
assertEqual "tweaked keys match" expected tweaked
|
assertEqual "tweaked keys match" expected tweaked
|
||||||
where
|
where
|
||||||
tweaked = do
|
tweaked = do
|
||||||
key <- secKey $ fst $ B16.decode $ B8.pack
|
key <- secKey $ hexToBytes
|
||||||
"f65255094d7773ed8dd417badc9fc045c1f80fdc5b2d25172b031ce6933e039a"
|
"f65255094d7773ed8dd417badc9fc045c1f80fdc5b2d25172b031ce6933e039a"
|
||||||
twk <- tweak $ fst $ B16.decode $ B8.pack
|
twk <- tweak $ hexToBytes
|
||||||
"f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
|
"f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
|
||||||
tweakMulSecKey key twk
|
tweakMulSecKey key twk
|
||||||
expected = secKey $ fst $ B16.decode $ B8.pack
|
expected = secKey $ hexToBytes
|
||||||
"a96f5962493acb179f60a86a9785fc7a30e0c39b64c09d24fe064d9aef15e4c0"
|
"a96f5962493acb179f60a86a9785fc7a30e0c39b64c09d24fe064d9aef15e4c0"
|
||||||
|
|
||||||
tweakAddPubKeyTest :: Assertion
|
tweakAddPubKeyTest :: Assertion
|
||||||
@ -204,12 +227,12 @@ tweakAddPubKeyTest =
|
|||||||
assertEqual "tweaked keys match" expected tweaked
|
assertEqual "tweaked keys match" expected tweaked
|
||||||
where
|
where
|
||||||
tweaked = do
|
tweaked = do
|
||||||
pub <- importPubKey $ fst $ B16.decode $ B8.pack
|
pub <- importPubKey $ hexToBytes
|
||||||
"04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd4470512213d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"
|
"04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd4470512213d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"
|
||||||
twk <- tweak $ fst $ B16.decode $ B8.pack
|
twk <- tweak $ hexToBytes
|
||||||
"f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
|
"f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
|
||||||
tweakAddPubKey pub twk
|
tweakAddPubKey pub twk
|
||||||
expected = importPubKey $ fst $ B16.decode $ B8.pack
|
expected = importPubKey $ hexToBytes
|
||||||
"04441c3982b97576646e0df0c96736063df6b42f2ee566d13b9f6424302d1379e518fdc87a14c5435bff7a5db4552042cb4120c6b86a4bbd3d0643f3c14ad01368"
|
"04441c3982b97576646e0df0c96736063df6b42f2ee566d13b9f6424302d1379e518fdc87a14c5435bff7a5db4552042cb4120c6b86a4bbd3d0643f3c14ad01368"
|
||||||
|
|
||||||
tweakMulPubKeyTest :: Assertion
|
tweakMulPubKeyTest :: Assertion
|
||||||
@ -217,12 +240,12 @@ tweakMulPubKeyTest =
|
|||||||
assertEqual "tweaked keys match" expected tweaked
|
assertEqual "tweaked keys match" expected tweaked
|
||||||
where
|
where
|
||||||
tweaked = do
|
tweaked = do
|
||||||
pub <- importPubKey $ fst $ B16.decode $ B8.pack
|
pub <- importPubKey $ hexToBytes
|
||||||
"04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd4470512213d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"
|
"04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd4470512213d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"
|
||||||
twk <- tweak $ fst $ B16.decode $ B8.pack
|
twk <- tweak $ hexToBytes
|
||||||
"f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
|
"f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
|
||||||
tweakMulPubKey pub twk
|
tweakMulPubKey pub twk
|
||||||
expected = importPubKey $ fst $ B16.decode $ B8.pack
|
expected = importPubKey $ hexToBytes
|
||||||
"04f379dc99cdf5c83e433defa267fbb3377d61d6b779c06a0e4ce29ae3ff5353b12ae49c9d07e7368f2ba5a446c203255ce912322991a2d6a9d5d5761c61ed1845"
|
"04f379dc99cdf5c83e433defa267fbb3377d61d6b779c06a0e4ce29ae3ff5353b12ae49c9d07e7368f2ba5a446c203255ce912322991a2d6a9d5d5761c61ed1845"
|
||||||
|
|
||||||
combinePubKeyTest :: Assertion
|
combinePubKeyTest :: Assertion
|
||||||
@ -230,14 +253,14 @@ combinePubKeyTest =
|
|||||||
assertEqual "combined keys match" expected combined
|
assertEqual "combined keys match" expected combined
|
||||||
where
|
where
|
||||||
combined = do
|
combined = do
|
||||||
pub1 <- importPubKey $ fst $ B16.decode $ B8.pack
|
pub1 <- importPubKey $ hexToBytes
|
||||||
"04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd4470512213d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"
|
"04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd4470512213d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"
|
||||||
pub2 <- importPubKey $ fst $ B16.decode $ B8.pack
|
pub2 <- importPubKey $ hexToBytes
|
||||||
"0487d82042d93447008dfe2af762068a1e53ff394a5bf8f68a045fa642b99ea5d153f577dd2dba6c7ae4cfd7b6622409d7edd2d76dd13a8092cd3af97b77bd2c77"
|
"0487d82042d93447008dfe2af762068a1e53ff394a5bf8f68a045fa642b99ea5d153f577dd2dba6c7ae4cfd7b6622409d7edd2d76dd13a8092cd3af97b77bd2c77"
|
||||||
pub3 <- importPubKey $ fst $ B16.decode $ B8.pack
|
pub3 <- importPubKey $ hexToBytes
|
||||||
"049b101edcbe1ee37ff6b2318526a425b629e823d7d8d9154417880595a28000ee3febd908754b8ce4e491aa6fe488b41fb5d4bb3788e33c9ff95a7a9229166d59"
|
"049b101edcbe1ee37ff6b2318526a425b629e823d7d8d9154417880595a28000ee3febd908754b8ce4e491aa6fe488b41fb5d4bb3788e33c9ff95a7a9229166d59"
|
||||||
combinePubKeys [pub1, pub2, pub3]
|
combinePubKeys [pub1, pub2, pub3]
|
||||||
expected = importPubKey $ fst $ B16.decode $ B8.pack
|
expected = importPubKey $ hexToBytes
|
||||||
"043d9a7ec70011efc23c33a7e62d2ea73cca87797e3b659d93bea6aa871aebde56c3bc6134ca82e324b0ab9c0e601a6d2933afe7fb5d9f3aae900f5c5dc6e362c8"
|
"043d9a7ec70011efc23c33a7e62d2ea73cca87797e3b659d93bea6aa871aebde56c3bc6134ca82e324b0ab9c0e601a6d2933afe7fb5d9f3aae900f5c5dc6e362c8"
|
||||||
|
|
||||||
negateTweakTest :: Assertion
|
negateTweakTest :: Assertion
|
||||||
@ -258,11 +281,77 @@ computeDhSecret =
|
|||||||
assertEqual "ecdh computes known secret" expected computed
|
assertEqual "ecdh computes known secret" expected computed
|
||||||
where
|
where
|
||||||
computed = do
|
computed = do
|
||||||
pub <- importPubKey $ fst $ B16.decode $ B8.pack
|
pub <- importPubKey $ hexToBytes
|
||||||
"028d7500dd4c12685d1f568b4c2b5048e8534b873319f3a8daa612b469132ec7f7"
|
"028d7500dd4c12685d1f568b4c2b5048e8534b873319f3a8daa612b469132ec7f7"
|
||||||
sec <- secKey $ fst $ B16.decode $ B8.pack
|
sec <- secKey $ hexToBytes
|
||||||
"1212121212121212121212121212121212121212121212121212121212121212"
|
"1212121212121212121212121212121212121212121212121212121212121212"
|
||||||
pure $ ecdh pub sec
|
pure $ ecdh pub sec
|
||||||
expected = Just $ fst $ B16.decode $ B8.pack
|
expected = Just $ hexToBytes
|
||||||
"1e2fb3c8fe8fb9f262f649f64d26ecf0f2c0a805a767cf02dc2d77a6ef1fdcc3"
|
"1e2fb3c8fe8fb9f262f649f64d26ecf0f2c0a805a767cf02dc2d77a6ef1fdcc3"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef SCHNORR
|
||||||
|
data VectorError = InvalidPubKey | InvalidSig | InvalidMsg | InvalidSigFormat
|
||||||
|
deriving (Eq, Show)
|
||||||
|
|
||||||
|
failingVectorToAssertion :: VectorError -> (BS.ByteString, BS.ByteString, BS.ByteString) -> Assertion
|
||||||
|
failingVectorToAssertion expectedFailure (pubBytes, msgBytes, sigBytes) =
|
||||||
|
assertEqual ("expected error " <> show expectedFailure <> " occurs") (Left expectedFailure) computed
|
||||||
|
where
|
||||||
|
computed :: Either VectorError ()
|
||||||
|
computed =
|
||||||
|
let
|
||||||
|
pubM = importXOnlyPubKey pubBytes
|
||||||
|
sigM = importSchnorrSig sigBytes
|
||||||
|
msgM = msg $ msgBytes
|
||||||
|
in
|
||||||
|
case (pubM, sigM, msgM) of
|
||||||
|
(Nothing, _, _) -> Left InvalidPubKey
|
||||||
|
(_, Nothing, _) -> Left InvalidSigFormat
|
||||||
|
(_, _, Nothing) -> Left InvalidMsg
|
||||||
|
(Just pub, Just sig, Just msg) ->
|
||||||
|
if verifyMsgSchnorr pub sig msg
|
||||||
|
then Right ()
|
||||||
|
else Left InvalidSig
|
||||||
|
|
||||||
|
bip340Vector0 :: Assertion
|
||||||
|
bip340Vector0 =
|
||||||
|
passingVectorToAssertion 0
|
||||||
|
(
|
||||||
|
BS.replicate 31 0 <> B8.pack ['\x01']
|
||||||
|
, BS.replicate 32 0
|
||||||
|
, hexToBytes "db46b5cdc554edbd7765611b75d7bdbc639cf538fb6e9ef04a61884b765343aae148954f27eb69291a9045862f8ae4fa53436c117e9397c70275d5398066d44b"
|
||||||
|
)
|
||||||
|
|
||||||
|
-- Integer is BIP-340 vector test number
|
||||||
|
passingVectorToAssertion :: Integer -> (BS.ByteString, BS.ByteString, BS.ByteString) -> Assertion
|
||||||
|
passingVectorToAssertion idx (secBytes, msgBytes, sigBytes) =
|
||||||
|
assertEqual ("BIP-340 test vector " <> show idx <> " signature matches") expectedSig computedSig
|
||||||
|
where
|
||||||
|
expectedSig :: Maybe SchnorrSig
|
||||||
|
expectedSig = importSchnorrSig $ sigBytes
|
||||||
|
computedSig :: Maybe SchnorrSig
|
||||||
|
computedSig = do
|
||||||
|
sec <- secKey secBytes
|
||||||
|
msg <- msg $ msgBytes
|
||||||
|
pure $ signMsgSchnorr sec msg
|
||||||
|
|
||||||
|
-- This test is ported from the C code, it is called 'test_schnorrsig_taproot'.
|
||||||
|
-- But this version was modified to use fixed keys and messages.
|
||||||
|
taprootKeySpend :: Assertion
|
||||||
|
taprootKeySpend =
|
||||||
|
assertEqual "derivation succeded and resulting signature is valid" (Just True) computed
|
||||||
|
where
|
||||||
|
computed = do
|
||||||
|
sec <- secKey $ BS.replicate 32 32
|
||||||
|
msgToSign <- msg $ BS.replicate 32 00
|
||||||
|
|
||||||
|
let internalPub = deriveXOnlyPubKey sec
|
||||||
|
twea <- tweak $ exportXOnlyPubKey internalPub
|
||||||
|
(outputPub, isNegated) <- schnorrTweakAddPubKey internalPub twea
|
||||||
|
|
||||||
|
-- This is a 'key spend' in Taproot terminology:
|
||||||
|
tweakSec <- schnorrTweakAddSecKey sec twea
|
||||||
|
let sig = signMsgSchnorr tweakSec msgToSign
|
||||||
|
pure $ verifyMsgSchnorr outputPub sig msgToSign
|
||||||
|
#endif
|
||||||
|
Loading…
Reference in New Issue
Block a user