packages feed

musig2-0.1.0: test/SignVerifyTweakProperty.hs

{-# LANGUAGE OverloadedStrings #-}

module SignVerifyTweakProperty (propertySignVerifyTweak) where

import Crypto.Curve.Secp256k1 (derive_pub, _CURVE_Q)
import Crypto.Curve.Secp256k1.MuSig2 (SecKey (..), SecNonce (..), Tweak (..), aggNonces, mkSessionContext, partialSigVerify, publicNonce, sign)
import Data.ByteString (ByteString)
import Data.Maybe (fromJust)
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 = derive_pub 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 < _CURVE_Q)

-- | 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 = derive_pub 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 = derive_pub 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 = derive_pub 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 < _CURVE_Q
    && t2 > 0
    && t2 < _CURVE_Q
    && t1
      /= t2
    ==> let pubkey = derive_pub 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