musig2-0.2.0: test/AggPartialsProperty.hs
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
module AggPartialsProperty (propertyAggPartials) where
import Crypto.Curve.Secp256k1 (Pub)
import Crypto.Curve.Secp256k1.MuSig2 (PubNonce, SessionContext, aggNonces, aggPartials, mkSessionContext, partialSigVerify, publicNonce, sign)
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Test.Tasty
import Test.Tasty.QuickCheck as QC
import Util (SignerMaterial (..), unsafeRight)
propertyAggPartials :: TestTree
propertyAggPartials =
testGroup
"aggPartials Properties"
[ testProperty "Aggregated partials from valid signatures" prop_aggValidPartials
, testProperty "Aggregation is deterministic" prop_aggDeterministic
, testProperty "All partial sigs verify before aggregation" prop_partialsVerifyBeforeAgg
, testProperty "Aggregation with single signer equals partial sig" prop_singleSignerAgg
]
mkTwoSignerContext :: SignerMaterial -> SignerMaterial -> ByteString -> ([PubNonce], [Pub], SessionContext)
mkTwoSignerContext signer1 signer2 msg =
let pubNonces = [unsafeRight $ publicNonce signer1.signerSecNonce, unsafeRight $ publicNonce signer2.signerSecNonce]
pubkeys = [signer1.signerPubKey, signer2.signerPubKey]
aggNonce = unsafeRight $ aggNonces pubNonces
ctx = unsafeRight $ mkSessionContext aggNonce pubkeys [] msg
in (pubNonces, pubkeys, ctx)
-- | Property: Aggregating valid partial signatures produces a consistent result
prop_aggValidPartials :: SignerMaterial -> SignerMaterial -> ByteString -> Property
prop_aggValidPartials signer1 signer2 msg =
let (_, _, ctx) = mkTwoSignerContext signer1 signer2 msg
partialSig1 = unsafeRight $ sign signer1.signerSecNonce signer1.signerSecKey ctx
partialSig2 = unsafeRight $ sign signer2.signerSecNonce signer2.signerSecKey ctx
aggregated = unsafeRight $ aggPartials [partialSig1, partialSig2] ctx
in BS.length aggregated === 64
-- | Property: Aggregation is deterministic - same inputs produce same output
prop_aggDeterministic :: SignerMaterial -> SignerMaterial -> ByteString -> Property
prop_aggDeterministic signer1 signer2 msg =
let (_, _, ctx) = mkTwoSignerContext signer1 signer2 msg
partialSig1 = unsafeRight $ sign signer1.signerSecNonce signer1.signerSecKey ctx
partialSig2 = unsafeRight $ sign signer2.signerSecNonce signer2.signerSecKey ctx
partialSigs = [partialSig1, partialSig2]
aggregated1 = unsafeRight $ aggPartials partialSigs ctx
aggregated2 = unsafeRight $ aggPartials partialSigs ctx
in aggregated1 === aggregated2
-- | Property: All partial signatures should verify individually before aggregation
prop_partialsVerifyBeforeAgg :: SignerMaterial -> SignerMaterial -> ByteString -> Property
prop_partialsVerifyBeforeAgg signer1 signer2 msg =
let (pubNonces, pubkeys, ctx) = mkTwoSignerContext signer1 signer2 msg
partialSig1 = unsafeRight $ sign signer1.signerSecNonce signer1.signerSecKey ctx
partialSig2 = unsafeRight $ sign signer2.signerSecNonce signer2.signerSecKey ctx
verify1 = partialSigVerify partialSig1 pubNonces pubkeys [] msg 0
verify2 = partialSigVerify partialSig2 pubNonces pubkeys [] msg 1
in verify1 === Right True .&&. verify2 === Right True
-- | Property: For a single signer, aggregation should work correctly
prop_singleSignerAgg :: SignerMaterial -> ByteString -> Property
prop_singleSignerAgg signer msg =
let pubNonce = unsafeRight $ publicNonce signer.signerSecNonce
pubNonces = [pubNonce]
pubkeys = [signer.signerPubKey]
aggNonce = unsafeRight $ aggNonces pubNonces
ctx = unsafeRight $ mkSessionContext aggNonce pubkeys [] msg
partialSig = unsafeRight $ sign signer.signerSecNonce signer.signerSecKey ctx
aggregated = unsafeRight $ aggPartials [partialSig] ctx
verifyResult = partialSigVerify partialSig pubNonces pubkeys [] msg 0
in verifyResult === Right True .&&. BS.length aggregated === 64