packages feed

musig2-0.1.2: test/SignVerifyTweakProperty.hs

{-# LANGUAGE OverloadedStrings #-}

module SignVerifyTweakProperty (propertySignVerifyTweak) where

import Crypto.Curve.Secp256k1 (derive_pub)
import Crypto.Curve.Secp256k1.MuSig2 (SecKey (..), SecNonce (..), Tweak (..), 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
import Util ()

propertySignVerifyTweak :: TestTree
propertySignVerifyTweak =
  testGroup
    "sign and partialSigVerify with Tweaks Properties"
    [ testProperty "Valid Signature Range with Tweaks" prop_validSignatureRangeWithTweaks
    , testProperty "Sign-Verify Roundtrip with Tweaks" prop_signVerifyRoundtripWithTweaks
    , testProperty "Signature Determinism with Tweaks" prop_signatureDeterminismWithTweaks
    , testProperty "Empty Tweaks Equals No Tweaks" prop_emptyTweaksEqualsNoTweaks
    , testProperty "Plain Tweaks Are Commutative" prop_plainTweaksCommutative
    ]

-- | Property: Generated signatures with tweaks are in the valid range \([0, Q-1]\).
prop_validSignatureRangeWithTweaks :: SecNonce -> SecKey -> Property
prop_validSignatureRangeWithTweaks secNonce secKey@(SecKey sk) =
  forAll (resize 5 $ listOf arbitrary) $ \tweaks ->
    forAll arbitrary $ \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 tweaks msg
          sig = sign secNonce secKey ctx
       in (sig >= 0) .&&. (sig < curveOrder)

-- | Property: A signature created with tweaks verifies with 'partialSigVerify'.
prop_signVerifyRoundtripWithTweaks :: SecNonce -> SecKey -> Property
prop_signVerifyRoundtripWithTweaks secNonce secKey@(SecKey sk) =
  forAll (resize 5 $ listOf arbitrary) $ \tweaks ->
    forAll arbitrary $ \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 tweaks msg
          sig = sign secNonce secKey ctx
          signerIndex = 0 -- We're always the first signer in this test
          result = partialSigVerify sig pubNonces pubkeys tweaks msg signerIndex
       in result === True

-- | Property: Signing the same message with the same tweaks produces the same signature.
prop_signatureDeterminismWithTweaks :: SecNonce -> SecKey -> Property
prop_signatureDeterminismWithTweaks secNonce secKey@(SecKey sk) =
  forAll (resize 5 $ listOf arbitrary) $ \tweaks ->
    forAll arbitrary $ \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 tweaks msg
          sig1 = sign secNonce secKey ctx
          sig2 = sign secNonce secKey ctx
       in sig1 === sig2

-- | Property: Empty tweaks should produce the same result as no tweaks.
prop_emptyTweaksEqualsNoTweaks :: SecNonce -> SecKey -> ByteString -> Property
prop_emptyTweaksEqualsNoTweaks 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

      -- Context with empty tweaks
      ctxWithEmptyTweaks = mkSessionContext aggNonce pubkeys [] msg
      sigWithEmptyTweaks = sign secNonce secKey ctxWithEmptyTweaks

      -- Context with no tweaks parameter (though we still pass empty list)
      ctxWithNoTweaks = mkSessionContext aggNonce pubkeys [] msg
      sigWithNoTweaks = sign secNonce secKey ctxWithNoTweaks
   in sigWithEmptyTweaks === sigWithNoTweaks

-- | Property: Plain tweaks are commutative.
prop_plainTweaksCommutative :: SecNonce -> SecKey -> Integer -> Integer -> ByteString -> Property
prop_plainTweaksCommutative secNonce secKey@(SecKey sk) t1 t2 msg =
  t1 > 0
    && t1 < curveOrder
    && t2 > 0
    && t2 < curveOrder
    && t1
      /= t2
    ==> let pubkey = fromMaybe (error "Failed to derive pubkey") $ derive_pub (fromInteger sk)
            pubNonce = publicNonce secNonce
            pubNonces = [pubNonce]
            pubkeys = [pubkey]
            aggNonce = fromJust $ aggNonces pubNonces

            -- First order: [PlainTweak t1, PlainTweak t2]
            tweak1 = PlainTweak t1
            tweak2 = PlainTweak t2
            ctx1 = mkSessionContext aggNonce pubkeys [tweak1, tweak2] msg
            sig1 = sign secNonce secKey ctx1

            -- Second order: [PlainTweak t2, PlainTweak t1]
            ctx2 = mkSessionContext aggNonce pubkeys [tweak2, tweak1] msg
            sig2 = sign secNonce secKey ctx2
         in -- Plain tweaks are commutative, order shouldn't matter
            sig1 === sig2