secp256k1-haskell-1.3.0: test/Crypto/Secp256k1Spec.hs
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE OverloadedRecordDot #-}
module Crypto.Secp256k1Spec (spec) where
import Control.Arrow (first)
import Control.Monad.Par qualified as P
import Crypto.Secp256k1
import Data.Base16.Types (assertBase16, extractBase16)
import Data.ByteString qualified as BS
import Data.ByteString.Base16 (decodeBase16, encodeBase16)
import Data.ByteString.Char8 qualified as B8
import Data.Either (fromRight)
import Data.Maybe (fromJust, fromMaybe, isJust, isNothing)
import Data.String (fromString)
import Data.String.Conversions (cs)
import Test.HUnit (Assertion, assertBool, assertEqual)
import Test.Hspec
import Test.QuickCheck
spec :: Spec
spec = around withContext $ do
describe "signatures" $ do
it "signs message" $ \ctx ->
property $ signMsgTest ctx
it "signs messages in parallel" $ \ctx ->
property $ signMsgParTest ctx
it "detects bad signature" $ \ctx ->
property $ badSignatureTest ctx
it "normalizes signatures" $ \ctx ->
property $ normalizeSigTest ctx
describe "serialization" $ do
it "serializes public key" $ \ctx ->
property $ serializePubKeyTest ctx
it "serializes public keys in parallel" $ \ctx ->
property $ parSerializePubKeyTest ctx
it "serializes DER signature" $ \ctx ->
property $ serializeSigTest ctx
it "serializes DER signatures in parallel" $ \ctx ->
property $ parSerializeSigTest ctx
it "serializes compact signature" $ \ctx ->
property $ serializeCompactSigTest ctx
it "serialize secret key" $ \_ ->
property serializeSecKeyTest
it "shows and reads secret key" $ \_ ->
property (showRead :: SecKey -> Bool)
it "shows and reads tweak" $ \_ ->
property showReadTweak
it "shows and reads message" $ \_ ->
property (showRead :: Msg -> Bool)
it "shows and reads public key" $ \ctx ->
property $ showReadPubKey ctx
it "reads secret key from string" $ \_ ->
property isStringSecKey
it "reads message from string" $ \_ ->
property isStringMsg
it "reads tweak from string" $ \_ ->
property isStringTweak
describe "tweaks" $ do
it "add secret key" $ \ctx ->
property $ tweakAddSecKeyTest ctx
it "multiply secret key" $ \ctx ->
property $ tweakMulSecKeyTest ctx
it "add public key" $ \ctx ->
property $ tweakAddPubKeyTest ctx
it "multiply public key" $ \ctx ->
property $ tweakMulPubKeyTest ctx
it "combine public keys" $ \ctx ->
property $ combinePubKeyTest ctx
it "can't combine 0 public keys" $ \ctx ->
property $ combinePubKeyEmptyListTest ctx
it "negates tweak" $ \ctx ->
property $ negateTweakTest ctx
describe "BIP 340" $ do
it "verifies a signed message (null rand32)" $ \ctx ->
property $ bip340SigTestNull ctx
it "verifies a signed message (not-null rand32)" $ \ctx ->
property $ bip340SigTest ctx
hexToBytes :: String -> BS.ByteString
hexToBytes = decodeBase16 . assertBase16 . B8.pack
isStringMsg :: Msg -> Bool
isStringMsg m = m == fromString (cs m')
where
m' = (extractBase16 . encodeBase16) m.get
isStringSecKey :: SecKey -> Bool
isStringSecKey k = k == fromString (cs hex)
where
hex = (extractBase16 . encodeBase16) k.get
isStringTweak :: SecKey -> Bool
isStringTweak k = t == fromString (cs hex)
where
t = (fromMaybe e . tweak) k.get
hex = (extractBase16 . encodeBase16) t.get
e = error "Could not extract tweak from secret key"
showReadTweak :: SecKey -> Bool
showReadTweak k = showRead t
where
t = tweak k.get
showReadPubKey :: Ctx -> SecKey -> Bool
showReadPubKey ctx k =
(read . show) p == p
where
p = derivePubKey ctx k
showRead :: (Show a, Read a, Eq a) => a -> Bool
showRead x = read (show x) == x
signMsgTest :: Ctx -> (Msg, SecKey) -> Bool
signMsgTest ctx (fm, fk) = verifySig ctx fp fg fm
where
fp = derivePubKey ctx fk
fg = signMsg ctx fk fm
signMsgParTest :: Ctx -> [(Msg, SecKey)] -> Bool
signMsgParTest ctx xs = P.runPar $ do
ys <- mapM (P.spawnP . signMsgTest ctx) xs
and <$> mapM P.get ys
badSignatureTest :: Ctx -> (Msg, SecKey, SecKey) -> Bool
badSignatureTest ctx (fm, fk, fk') = not $ verifySig ctx fp fg fm
where
fp = derivePubKey ctx fk'
fg = signMsg ctx fk fm
normalizeSigTest :: Ctx -> (Msg, SecKey) -> Bool
normalizeSigTest ctx (fm, fk) = isNothing sig
where
fg = signMsg ctx fk fm
sig = normalizeSig ctx fg
serializePubKeyTest :: Ctx -> (SecKey, Bool) -> Bool
serializePubKeyTest ctx (fk, b) =
case importPubKey ctx $ exportPubKey ctx b fp of
Just fp' -> fp == fp'
Nothing -> False
where
fp = derivePubKey ctx fk
parSerializePubKeyTest :: Ctx -> [(SecKey, Bool)] -> Bool
parSerializePubKeyTest ctx ks = P.runPar $ do
as <- mapM (P.spawnP . serializePubKeyTest ctx) ks
and <$> mapM P.get as
where
ps = map (first (derivePubKey ctx)) ks
serializeSigTest :: Ctx -> (Msg, SecKey) -> Bool
serializeSigTest ctx (fm, fk) =
case importSig ctx $ exportSig ctx fg of
Just fg' -> fg == fg'
Nothing -> False
where
fg = signMsg ctx fk fm
parSerializeSigTest :: Ctx -> [(Msg, SecKey)] -> Bool
parSerializeSigTest ctx ms = P.runPar $ do
as <- mapM (P.spawnP . serializeSigTest ctx) ms
and <$> mapM P.get as
serializeCompactSigTest :: Ctx -> (Msg, SecKey) -> Bool
serializeCompactSigTest ctx (fm, fk) =
case importCompactSig ctx $ exportCompactSig ctx fg of
Just fg' -> fg == fg'
Nothing -> False
where
fg = signMsg ctx fk fm
serializeSecKeyTest :: SecKey -> Bool
serializeSecKeyTest fk =
case secKey fk.get of
Just fk' -> fk == fk'
Nothing -> False
tweakAddSecKeyTest :: Ctx -> Assertion
tweakAddSecKeyTest ctx =
assertEqual "tweaked keys match" expected tweaked
where
tweaked = do
key <-
secKey $
hexToBytes
"f65255094d7773ed8dd417badc9fc045c1f80fdc5b2d25172b031ce6933e039a"
twk <-
tweak $
hexToBytes
"f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
tweakAddSecKey ctx key twk
expected =
secKey $
hexToBytes
"ec1e3ce1cefa18a671d51125e2b249688d934b0e28f5d1665384d9b02f929059"
tweakMulSecKeyTest :: Ctx -> Assertion
tweakMulSecKeyTest ctx =
assertEqual "tweaked keys match" expected tweaked
where
tweaked = do
key <-
secKey $
hexToBytes
"f65255094d7773ed8dd417badc9fc045c1f80fdc5b2d25172b031ce6933e039a"
twk <-
tweak $
hexToBytes
"f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
tweakMulSecKey ctx key twk
expected =
secKey $
hexToBytes
"a96f5962493acb179f60a86a9785fc7a30e0c39b64c09d24fe064d9aef15e4c0"
tweakAddPubKeyTest :: Ctx -> Assertion
tweakAddPubKeyTest ctx = do
assertBool "did not fail to decode" $ isJust tweaked
assertBool "is not empty" $ not $ maybe False BS.null tweaked
assertEqual "tweaked keys match" expected tweaked
where
tweaked = do
pub <-
importPubKey ctx $
hexToBytes
"04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd4470512213d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"
twk <-
tweak $
hexToBytes
"f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
key <- tweakAddPubKey ctx pub twk
return $ exportPubKey ctx True key
expected = do
key <-
importPubKey ctx $
hexToBytes
"04441c3982b97576646e0df0c96736063df6b42f2ee566d13b9f6424302d1379e518fdc87a14c5435bff7a5db4552042cb4120c6b86a4bbd3d0643f3c14ad01368"
return $ exportPubKey ctx True key
tweakMulPubKeyTest :: Ctx -> Assertion
tweakMulPubKeyTest ctx = do
assertBool "did not fail to decode" $ isJust tweaked
assertBool "is not empty" $ not $ maybe False BS.null tweaked
assertEqual "tweaked keys match" expected tweaked
where
tweaked = do
pub <-
importPubKey ctx $
hexToBytes
"04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd4470512213d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"
twk <-
tweak $
hexToBytes
"f5cbe7d88182a4b8e400f96b06128921864a18187d114c8ae8541b566c8ace00"
key <- tweakMulPubKey ctx pub twk
return $ exportPubKey ctx True key
expected = do
key <-
importPubKey ctx $
hexToBytes
"04f379dc99cdf5c83e433defa267fbb3377d61d6b779c06a0e4ce29ae3ff5353b12ae49c9d07e7368f2ba5a446c203255ce912322991a2d6a9d5d5761c61ed1845"
return $ exportPubKey ctx True key
combinePubKeyTest :: Ctx -> Assertion
combinePubKeyTest ctx = do
assertBool "did not fail to decode" $ isJust combined
assertBool "is not empty" $ not $ maybe False BS.null combined
assertEqual "combined keys match" expected combined
where
combined = do
pub1 <-
importPubKey ctx $
hexToBytes
"04dded4203dac96a7e85f2c374a37ce3e9c9a155a72b64b4551b0bfe779dd4470512213d5ed790522c042dee8e85c4c0ec5f96800b72bc5940c8bc1c5e11e4fcbf"
pub2 <-
importPubKey ctx $
hexToBytes
"0487d82042d93447008dfe2af762068a1e53ff394a5bf8f68a045fa642b99ea5d153f577dd2dba6c7ae4cfd7b6622409d7edd2d76dd13a8092cd3af97b77bd2c77"
pub3 <-
importPubKey ctx $
hexToBytes
"049b101edcbe1ee37ff6b2318526a425b629e823d7d8d9154417880595a28000ee3febd908754b8ce4e491aa6fe488b41fb5d4bb3788e33c9ff95a7a9229166d59"
key <- combinePubKeys ctx [pub1, pub2, pub3]
return $ exportPubKey ctx True key
expected = do
key <-
importPubKey ctx $
hexToBytes
"043d9a7ec70011efc23c33a7e62d2ea73cca87797e3b659d93bea6aa871aebde56c3bc6134ca82e324b0ab9c0e601a6d2933afe7fb5d9f3aae900f5c5dc6e362c8"
return $ exportPubKey ctx True key
combinePubKeyEmptyListTest :: Ctx -> Assertion
combinePubKeyEmptyListTest ctx =
assertEqual "empty pubkey list must return Nothing" expected combined
where
expected = Nothing
combined = do
key <- combinePubKeys ctx []
return $ exportPubKey ctx True key
negateTweakTest :: Ctx -> Assertion
negateTweakTest ctx =
assertEqual "can recover secret key 1 after adding tweak 1" oneKey subtracted
where
Just oneKey =
secKey . decodeBase16 . assertBase16 $
B8.pack
"0000000000000000000000000000000000000000000000000000000000000001"
Just oneTwk =
tweak . decodeBase16 . assertBase16 $
B8.pack
"0000000000000000000000000000000000000000000000000000000000000001"
Just minusOneTwk = tweakNegate ctx oneTwk
Just twoKey = tweakAddSecKey ctx oneKey oneTwk
Just subtracted = tweakAddSecKey ctx twoKey minusOneTwk
bip340SigTestNull :: Ctx -> Assertion
bip340SigTestNull ctx =
assertBool "verifies signature" $
verifyBip340 ctx pk theMsg sig
where
Just sk = secKey $ BS.replicate 32 0x01
pk = deriveXOnlyPubKey ctx $ derivePubKey ctx sk
Just theMsg = msg $ BS.replicate 32 0x02
Just sig = signBip340 ctx sk theMsg Nothing
bip340SigTest :: Ctx -> Assertion
bip340SigTest ctx =
assertBool "verifies signature" $
verifyBip340 ctx pk theMsg sig
where
Just sk = secKey $ BS.replicate 32 0x01
pk = deriveXOnlyPubKey ctx $ derivePubKey ctx sk
Just theMsg = msg $ BS.replicate 32 0x02
Just r = mkRand32 $ BS.replicate 32 0x03
Just sig = signBip340 ctx sk theMsg (Just r)