packages feed

musig2-0.3.0: test/Scalars.hs

{-# LANGUAGE MultiWayIf #-}

{- | The constant-time secret scalar arithmetic against an 'Integer'
reference modulo \(n\), and the range checks of secret scalars and keys at
their boundaries.
-}
module Scalars (testScalars) where

import Crypto.Curve.Secp256k1 (neg, _CURVE_G)
import Crypto.Curve.Secp256k1.MuSig2 (DecodeError (..), pubKeyPoint, secKeyFromBytes, secKeyPubKey, secKeyToBytes)
import Crypto.Curve.Secp256k1.MuSig2.Internal.Scalar (
  bytesToInteger,
  curveOrder,
  integerToBytes32,
  secScalarFromBytes,
  secScalarFromBytesModQ,
  secScalarToBytes,
  signScalar,
  widerFromBytes,
  widerToBytes,
 )
import qualified Data.ByteString as BS
import qualified Data.Word.Wider as W
import Generators (genBytes, genNonZeroScalar, genScalar)
import Test.Tasty
import Test.Tasty.HUnit
import Test.Tasty.QuickCheck
import Util (secScalarFromInt)

testScalars :: TestTree
testScalars =
  testGroup
    "Constant-time scalars"
    [ testGroup
        "byte conversions"
        [ testProperty "widerFromBytes agrees with the Integer decoding" . forAll gen256 $ \x ->
            W.from_vartime (widerFromBytes (integerToBytes32 x)) === x
        , testProperty "widerToBytes . widerFromBytes = id" . forAll (genBytes 32) $ \b ->
            widerToBytes (widerFromBytes b) === b
        , testCase "edge values round-trip" $
            mapM_ (\x -> widerToBytes (widerFromBytes (integerToBytes32 x)) @?= integerToBytes32 x) edges
        ]
    , testGroup
        "reduction modulo n"
        [ testProperty "secScalarFromBytesModQ is int(b) mod n" . forAll gen256 $ \x ->
            reduced x === x `mod` curveOrder
        , testCase "edge values: 0, 1, n - 1, n, n + 1, 2^256 - 2, 2^256 - 1" $
            mapM_ (\x -> reduced x @?= x `mod` curveOrder) edges
        ]
    , testGroup
        "signScalar"
        [ testProperty "agrees with the Integer reference" $
            forAll ((,,) <$> genNonZeroScalar <*> genNonZeroScalar <*> genNonZeroScalar) $ \(k1, k2, d) ->
              forAll ((,,,) <$> arbitrary <*> arbitrary <*> genScalar <*> genScalar) $ \(negNonce, negKey, b, ea) ->
                signScalar (secScalarFromInt k1) (secScalarFromInt k2) (secScalarFromInt d) negNonce negKey b ea
                  === referenceSignScalar k1 k2 d negNonce negKey b ea
        , testCase "k = k1 + b k2 = 0 (negating zero gives zero)" $
            mapM_
              ( \(negNonce, negKey) ->
                  signScalar (secScalarFromInt (curveOrder - 1)) (secScalarFromInt 1) (secScalarFromInt 5) negNonce negKey 1 7
                    @?= referenceSignScalar (curveOrder - 1) 1 5 negNonce negKey 1 7
              )
              [(False, False), (True, False), (False, True), (True, True)]
        , testCase "public coefficients at the edges: 0, 1, n - 1" $
            sequence_
              [ signScalar (secScalarFromInt k1) (secScalarFromInt k2) (secScalarFromInt d) True True b ea
                  @?= referenceSignScalar k1 k2 d True True b ea
              | k1 <- [1, curveOrder - 1]
              , k2 <- [1, curveOrder - 1]
              , d <- [1, curveOrder - 1]
              , b <- [0, 1, curveOrder - 1]
              , ea <- [0, 1, curveOrder - 1]
              ]
        ]
    , testGroup
        "range checks"
        [ testCase "secScalarFromBytes at the boundaries" $ do
            failure (secScalarFromBytes (integerToBytes32 0)) @?= Just ScalarZero
            failure (secScalarFromBytes (integerToBytes32 curveOrder)) @?= Just ScalarOutOfRange
            failure (secScalarFromBytes (integerToBytes32 (2 ^ (256 :: Int) - 1))) @?= Just ScalarOutOfRange
            failure (secScalarFromBytes (BS.replicate 31 1)) @?= Just (InvalidLength 32 31)
            fmap secScalarToBytes (secScalarFromBytes (integerToBytes32 1)) @?= Right (integerToBytes32 1)
            fmap secScalarToBytes (secScalarFromBytes (integerToBytes32 (curveOrder - 1))) @?= Right (integerToBytes32 (curveOrder - 1))
        , testProperty "secScalarFromBytes accepts exactly [1, n)" . forAll gen256 $ \x ->
            fmap secScalarToBytes (secScalarFromBytes (integerToBytes32 x))
              === if
                | x == 0 -> Left ScalarZero
                | x >= curveOrder -> Left ScalarOutOfRange
                | otherwise -> Right (integerToBytes32 x)
        , testCase "secKeyFromBytes: 0, 1, n - 1, n, 2^256 - 1" $ do
            failure (secKeyFromBytes (integerToBytes32 0)) @?= Just ScalarZero
            failure (secKeyFromBytes (integerToBytes32 curveOrder)) @?= Just ScalarOutOfRange
            failure (secKeyFromBytes (integerToBytes32 (2 ^ (256 :: Int) - 1))) @?= Just ScalarOutOfRange
            let one = secKeyFromBytes (integerToBytes32 1)
                minusOne = secKeyFromBytes (integerToBytes32 (curveOrder - 1))
            fmap secKeyToBytes one @?= Right (integerToBytes32 1)
            fmap secKeyToBytes minusOne @?= Right (integerToBytes32 (curveOrder - 1))
            fmap (pubKeyPoint . secKeyPubKey) one @?= Right _CURVE_G
            fmap (pubKeyPoint . secKeyPubKey) minusOne @?= Right (neg _CURVE_G)
        ]
    ]
 where
  reduced x = bytesToInteger (secScalarToBytes (secScalarFromBytesModQ (integerToBytes32 x)))
  failure :: Either e a -> Maybe e
  failure = either Just (const Nothing)

-- | A 256-bit value, uniform or one of the 'edges'.
gen256 :: Gen Integer
gen256 = frequency [(1, elements edges), (4, choose (0, 2 ^ (256 :: Int) - 1)), (2, choose (curveOrder, 2 ^ (256 :: Int) - 1))]

-- | Boundary values for 256-bit inputs.
edges :: [Integer]
edges = [0, 1, curveOrder - 1, curveOrder, curveOrder + 1, 2 ^ (256 :: Int) - 2, 2 ^ (256 :: Int) - 1]

-- | The 'Integer' arithmetic 'signScalar' replaced.
referenceSignScalar :: Integer -> Integer -> Integer -> Bool -> Bool -> Integer -> Integer -> Integer
referenceSignScalar k1 k2 d negateNonce negateKey b ea =
  let k = (k1 + b * k2) `mod` curveOrder
      k' = if negateNonce then (curveOrder - k) `mod` curveOrder else k
      d' = if negateKey then curveOrder - d else d
   in (k' + ea * d') `mod` curveOrder