packages feed

musig2-0.1.2: test/SignVerifyProperty.hs

{-# LANGUAGE OverloadedStrings #-}

module SignVerifyProperty (propertySignVerify) where

import Crypto.Curve.Secp256k1 (derive_pub)
import Crypto.Curve.Secp256k1.MuSig2 (SecKey (..), SecNonce (..), aggNonces, mkSessionContext, partialSigVerify, publicNonce, sign)
import Crypto.Curve.Secp256k1.MuSig2.Internal (curveOrder)
import Data.ByteString (ByteString)
import Data.Maybe (fromJust, fromMaybe)
import Test.Tasty
import Test.Tasty.QuickCheck as QC
import Util ()

propertySignVerify :: TestTree
propertySignVerify =
  testGroup
    "sign and partialSigVerify Properties"
    [ testProperty "Valid Signature Range" prop_validSignatureRange
    , testProperty "Sign-Verify Roundtrip" prop_signVerifyRoundtrip
    , testProperty "Signature Determinism" prop_signatureDeterminism
    , testProperty "Invalid Signer Index Fails" prop_invalidSignerIndex
    ]

-- | Property: Generated signatures are in the valid range \([0, Q-1]\).
prop_validSignatureRange :: SecNonce -> SecKey -> ByteString -> Property
prop_validSignatureRange secNonce secKey@(SecKey sk) msg =
  let pubkey = fromMaybe (error "Failed to derive pubkey") $ derive_pub (fromInteger sk)
      pubNonce = publicNonce secNonce
      pubNonces = [pubNonce]
      pubkeys = [pubkey]
      aggNonce = fromJust $ aggNonces pubNonces
      ctx = mkSessionContext aggNonce pubkeys [] msg
      sig = sign secNonce secKey ctx
   in (sig >= 0) .&&. (sig < curveOrder)

-- | Property: A signature created with sign verifies with 'partialSigVerify'.
prop_signVerifyRoundtrip :: SecNonce -> SecKey -> ByteString -> Property
prop_signVerifyRoundtrip secNonce secKey@(SecKey sk) msg =
  let pubkey = fromMaybe (error "Failed to derive pubkey") $ derive_pub (fromInteger sk)
      pubNonce = publicNonce secNonce
      pubNonces = [pubNonce]
      pubkeys = [pubkey]
      aggNonce = fromJust $ aggNonces pubNonces
      ctx = mkSessionContext aggNonce pubkeys [] msg
      sig = sign secNonce secKey ctx
      signerIndex = 0 -- We're always the first signer in this test
      result = partialSigVerify sig pubNonces pubkeys [] msg signerIndex
   in result === True

-- | Property: Signing the same message with the same parameters produces the same signature.
prop_signatureDeterminism :: SecNonce -> SecKey -> ByteString -> Property
prop_signatureDeterminism secNonce secKey@(SecKey sk) msg =
  let pubkey = fromMaybe (error "Failed to derive pubkey") $ derive_pub (fromInteger sk)
      pubNonce = publicNonce secNonce
      pubNonces = [pubNonce]
      pubkeys = [pubkey]
      aggNonce = fromJust $ aggNonces pubNonces
      ctx = mkSessionContext aggNonce pubkeys [] msg
      sig1 = sign secNonce secKey ctx
      sig2 = sign secNonce secKey ctx
   in sig1 === sig2

-- | Property: Using an invalid signer index should fail verification.
prop_invalidSignerIndex :: SecNonce -> SecKey -> ByteString -> Property
prop_invalidSignerIndex secNonce secKey@(SecKey sk) msg =
  let pubkey = fromMaybe (error "Failed to derive pubkey") $ derive_pub (fromInteger sk)
      pubNonce = publicNonce secNonce
      pubNonces = [pubNonce]
      pubkeys = [pubkey]
      aggNonce = fromJust $ aggNonces pubNonces
      ctx = mkSessionContext aggNonce pubkeys [] msg
      sig = sign secNonce secKey ctx
      invalidIndex = 1 -- Out of bounds index (only 1 signer at index 0)
      result = partialSigVerify sig pubNonces pubkeys [] msg invalidIndex
   in expectFailure (result === True)