packages feed

musig2-0.1.0: test/SignVerify.hs

{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -Wno-x-partial #-}

module SignVerify (testSignVerify) where

import Control.Exception (ErrorCall (..), evaluate, try)
import Crypto.Curve.Secp256k1 (Pub)
import Crypto.Curve.Secp256k1.MuSig2 (PubNonce (..), SecKey (..), SecNonce (..), aggNonces, mkSessionContext, partialSigVerify, sign)
import Crypto.Curve.Secp256k1.MuSig2.Internal (bytesToInteger)
import Data.ByteString (ByteString)
import Data.List (isInfixOf)
import Data.Maybe (fromJust)
import Test.Tasty
import Test.Tasty.HUnit
import Util (decodeHex, parsePoint, parsePubNonce)

-- | Input public keys from BIP327 test vectors
inputPubkeys :: [Pub]
inputPubkeys =
  map
    parsePoint
    [ "03935F972DA013F80AE011890FA89B67A27B7BE6CCB24D3274D18B2D4067F261A9"
    , "02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9"
    , "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA661"
    , "020000000000000000000000000000000000000000000000000000000000000007"
    ]

-- | Public nonces from BIP327 test vectors
inputPubNonces :: [PubNonce]
inputPubNonces =
  map
    parsePubNonce
    [ "0337C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480"
    , "0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F817980279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798"
    , "032DE2662628C90B03F5E720284EB52FF7D71F4284F627B68A853D78C78E1FFE9303E4C5524E83FFE1493B9077CF1CA6BEB2090C93D930321071AD40B2F44E599046"
    , "0237C87821AFD50A8644D820A8F3E02E499C931865C2360FB43D0A0D20DAFE07EA0387BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480"
    , "0200000000000000000000000000000000000000000000000000000000000000090287BF891D2A6DEAEBADC909352AA9405D1428C15F4B75F04DAE642A95C2548480"
    ]

-- | Messages from BIP327 test vectors
testMessages :: [ByteString]
testMessages =
  [ decodeHex "F95466D086770E689964664219266FE5ED215C92AE20BAB5C9D79ADDDDF3C0CF"
  , ""
  , decodeHex "2626262626262626262626262626262626262626262626262626262626262626262626262626"
  ]

-- | Valid test vector data: (key indices, nonce indices, msg index, signer index, expected signature)
validTestVectors :: [([Int], [Int], Int, Int, ByteString)]
validTestVectors =
  [ ([0, 1, 2], [0, 1, 2], 0, 0, decodeHex "012ABBCB52B3016AC03AD82395A1A415C48B93DEF78718E62A7A90052FE224FB")
  , ([1, 0, 2], [1, 0, 2], 0, 1, decodeHex "9FF2F7AAA856150CC8819254218D3ADEEB0535269051897724F9DB3789513A52")
  , ([1, 2, 0], [1, 2, 0], 0, 2, decodeHex "FA23C359F6FAC4E7796BB93BC9F0532A95468C539BA20FF86D7C76ED92227900")
  , ([0, 1, 2], [0, 1, 2], 1, 0, decodeHex "D7D63FFD644CCDA4E62BC2BC0B1D02DD32A1DC3030E155195810231D1037D82D") -- Empty message
  , ([0, 1, 2], [0, 1, 2], 2, 0, decodeHex "E184351828DA5094A97C79CABDAAA0BFB87608C32E8829A4DF5340A6F243B78C") -- 38-byte message
  ]

-- | Invalid test vector data: (signature, key indices, nonce indices, msg index, signer index, expected to fail)
invalidTestVectors :: [(ByteString, [Int], [Int], Int, Int, String)]
invalidTestVectors =
  [ (decodeHex "FED54434AD4CFE953FC527DC6A5E5BE8F6234907B7C187559557CE87A0541C46", [0, 1, 2], [0, 1, 2], 0, 0, "Wrong signature (negation of valid signature)")
  , (decodeHex "012ABBCB52B3016AC03AD82395A1A415C48B93DEF78718E62A7A90052FE224FB", [0, 1, 2], [0, 1, 2], 0, 1, "Wrong signer")
  ]

-- | Error test vector data: (signature, key indices, nonce indices, msg index, signer index, expected error message)
errorTestVectors :: [(ByteString, [Int], [Int], Int, Int, String)]
errorTestVectors =
  [ (decodeHex "012ABBCB52B3016AC03AD82395A1A415C48B93DEF78718E62A7A90052FE224FB", [0, 1, 2], [4, 1, 2], 0, 0, "Invalid pubnonce")
  , (decodeHex "012ABBCB52B3016AC03AD82395A1A415C48B93DEF78718E62A7A90052FE224FB", [3, 1, 2], [0, 1, 2], 0, 0, "Invalid pubkey")
  , (decodeHex "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", [0, 1, 2], [0, 1, 2], 0, 0, "Signature exceeds group size")
  ]

-- | Valid secret key from BIP327 test vectors
testSecKey :: SecKey
testSecKey = SecKey $ bytesToInteger $ decodeHex "7FB9E0E687ADA1EEBF7ECFE2F21E73EBDB51A7D450948DFE8D76D7F2D1007671"

-- | Invalid secret nonce with k1 = 0 (from BIP327 test vectors secnonce index 1)
invalidSecNonce :: SecNonce
invalidSecNonce = SecNonce{k1 = 0, k2 = 0}

-- | Creates test case from valid test vector data
makeValidTestCase :: Int -> ([Int], [Int], Int, Int, ByteString) -> TestTree
makeValidTestCase i (keyIndices, nonceIndices, msgIndex, signerIndex, expectedSig) =
  testCase ("BIP327 valid test vector " <> show (i + 1)) $ do
    let selectedKeys = map (inputPubkeys !!) keyIndices
        selectedNonces = map (inputPubNonces !!) nonceIndices
        msg = testMessages !! msgIndex
        signature = bytesToInteger expectedSig

    -- Test that the valid signature verifies
    let result = partialSigVerify signature selectedNonces selectedKeys [] msg signerIndex
    assertBool "Valid signature should verify" result

-- | Creates test case from invalid test vector data
makeInvalidTestCase :: Int -> (ByteString, [Int], [Int], Int, Int, String) -> TestTree
makeInvalidTestCase i (sigBytes, keyIndices, nonceIndices, msgIndex, signerIndex, comment) =
  testCase ("BIP327 invalid test vector " <> show (i + 1) <> ": " <> comment) $ do
    let selectedKeys = map (inputPubkeys !!) keyIndices
        selectedNonces = map (inputPubNonces !!) nonceIndices
        msg = testMessages !! msgIndex
        signature = bytesToInteger sigBytes

    -- Test that the invalid signature fails verification
    let result = partialSigVerify signature selectedNonces selectedKeys [] msg signerIndex
    assertBool ("Invalid signature should not verify: " <> comment) (not result)

-- | Creates test case from error test vector data
makeErrorTestCase :: Int -> (ByteString, [Int], [Int], Int, Int, String) -> TestTree
makeErrorTestCase i (sigBytes, keyIndices, nonceIndices, msgIndex, signerIndex, comment) =
  testCase ("BIP327 error test vector " <> show (i + 1) <> ": " <> comment) $ do
    let selectedKeys = map (inputPubkeys !!) keyIndices
        selectedNonces = map (inputPubNonces !!) nonceIndices
        msg = testMessages !! msgIndex
        signature = bytesToInteger sigBytes

    -- Test that this causes an error during verification
    result <- try $ evaluate $ partialSigVerify signature selectedNonces selectedKeys [] msg signerIndex
    case result of
      Left (ErrorCall _) -> return () -- Expected error
      Right _ -> assertFailure ("Expected error but verification succeeded: " <> comment)

-- | Test case for invalid secret nonce (k1 = 0) from BIP327 sign_error_test_cases
testInvalidSecNonce :: TestTree
testInvalidSecNonce =
  testCase "BIP327 sign error: first secnonce value is out of range" $ do
    let selectedKeys = take 3 inputPubkeys -- [0, 1, 2]
        selectedNonces = take 3 inputPubNonces -- [0, 1, 2]
        msg = head testMessages -- msg_index 0
        aggNonce = fromJust $ aggNonces selectedNonces
        ctx = mkSessionContext aggNonce selectedKeys [] msg

    result <- try $ evaluate $ sign invalidSecNonce testSecKey ctx
    case result of
      Left (ErrorCall errMsg) ->
        assertBool
          ("Expected error message containing 'first secret scalar k1 is zero', got: " ++ errMsg)
          ("first secret scalar k1 is zero" `isInfixOf` errMsg)
      Right _ -> assertFailure "Expected error but signing succeeded"

-- | Test vectors from [BIP327 `sign_verify_vectors.json`](https://github.com/bitcoin/bips/blob/master/bip-0327/vectors/sign_verify_vectors.json)
testSignVerify :: TestTree
testSignVerify =
  testGroup "sign and verify" $
    zipWith makeValidTestCase [0 ..] validTestVectors
      <> zipWith makeInvalidTestCase [0 ..] invalidTestVectors
      <> zipWith makeErrorTestCase [0 ..] errorTestVectors
      <> [testInvalidSecNonce]