BIP-340 (Schnorr) support (#26)

Compatible with jonasnick/secp256k1@372c4555ca
This commit is contained in:
Janus Troelsen 2020-04-09 06:17:50 -05:00 committed by GitHub
parent ae2c67e35e
commit 76f86ad0d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 398 additions and 19 deletions

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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