packages feed

musig2-0.3.0: internal/Crypto/Curve/Secp256k1/MuSig2/Internal/Scalar.hs

{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UnboxedTuples #-}

{- |
Module: Crypto.Curve.Secp256k1.MuSig2.Internal.Scalar
Copyright: (c) 2025 Jose Storopoli
License: MIT
Maintainer: Jose Storopoli <jose@storopoli.com>

Scalar arithmetic modulo the secp256k1 group order \(n\).

Two representations, kept apart by type:

* Public scalars (key aggregation coefficients, nonce coefficients,
  challenges, tweaks, partial signatures) are 'Integer's. Arithmetic on
  'Integer' takes time that depends on the values, which is fine for values
  everyone knows.

* Secret scalars (secret keys and nonces) are t'SecScalar's: four 64-bit
  limbs in Montgomery form, with the constant-time arithmetic of
  @ppad-fixed@. They are read from and written to bytes in constant time,
  range-checked with constant-time comparisons that are decided only once
  at the end, and never converted to or from 'Integer'.

Not part of the public API.
-}
module Crypto.Curve.Secp256k1.MuSig2.Internal.Scalar (
  -- * Group order
  curveOrder,
  fieldSize,
  modQ,

  -- * Public scalars
  bytesToInteger,
  integerToBytes32,
  hashToScalar,

  -- * Secret scalars
  SecScalar,
  secScalarFromBytes,
  secScalarFromBytesModQ,
  secScalarToBytes,
  secScalarWider,

  -- * Secret arithmetic
  signScalar,

  -- * Constant-time byte conversions
  widerFromBytes,
  widerToBytes,
) where

import Control.DeepSeq (NFData (..))
import Crypto.Curve.Secp256k1.MuSig2.Internal.Error (DecodeError (..))
import Data.Bits (shiftR, unsafeShiftL, unsafeShiftR, (.|.))
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Internal as BI
import qualified Data.ByteString.Unsafe as BU
import qualified Data.Choice as C
import Data.Word (Word8)
import Data.Word.Limb (Limb (..))
import Data.Word.Wider (Wider (..))
import qualified Data.Word.Wider as W
import Foreign.Ptr (Ptr)
import Foreign.Storable (pokeByteOff)
import GHC.Exts (Word (..))
import qualified Numeric.Montgomery.Secp256k1.Scalar as S

-- Group order ----------------------------------------------------------------

-- | The secp256k1 group order \(n\).
curveOrder :: Integer
curveOrder = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141

-- | The secp256k1 field size \(p\).
fieldSize :: Integer
fieldSize = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F

-- | Reduction modulo \(n\) (variable-time: public values only).
modQ :: Integer -> Integer
modQ x = x `mod` curveOrder

-- Public scalars -------------------------------------------------------------

-- | Big-endian bytes to a non-negative 'Integer' (variable-time: public values only).
bytesToInteger :: ByteString -> Integer
bytesToInteger = BS.foldl' (\acc b -> acc * 256 + fromIntegral b) 0

-- | A non-negative 'Integer' below \(2^{256}\) to 32 big-endian bytes (variable-time: public values only).
integerToBytes32 :: Integer -> ByteString
integerToBytes32 i = fst (BS.unfoldrN 32 step 31)
 where
  step :: Int -> Maybe (Word8, Int)
  step j = Just (fromInteger (i `shiftR` (8 * j)), j - 1)

-- | @int(h) mod n@ for a 32-byte hash @h@ that is public (variable-time).
hashToScalar :: ByteString -> Integer
hashToScalar = modQ . bytesToInteger

-- Secret scalars -------------------------------------------------------------

{- | A secret scalar modulo \(n\), in the Montgomery form of @ppad-fixed@
(always fully reduced, so in \([0, n)\)).

Every operation on it runs in constant time. Deliberately has no 'Show',
'Eq', 'Ord', 'Num' or other instance that could compare, print or convert it
in variable time.
-}
newtype SecScalar = SecScalar S.Montgomery

instance NFData SecScalar where
  rnf (SecScalar m) = rnf m

-- | \(n\) as four limbs.
orderWider :: Wider
orderWider = W.wider 0xBFD25E8CD0364141 0xBAAEDCE6AF48A03B 0xFFFFFFFFFFFFFFFE 0xFFFFFFFFFFFFFFFF

-- | Zero as four limbs.
zeroWider :: Wider
zeroWider = W.wider 0 0 0 0

{- | A secret scalar from 32 big-endian bytes in \([1, n)\).

Only the length is checked in variable time. The range check compares the
limbs in constant time and decides once, on the combined result; only the
outcome (valid, zero or out of range) is revealed.
-}
secScalarFromBytes :: ByteString -> Either DecodeError SecScalar
secScalarFromBytes b
  | BS.length b /= 32 = Left (InvalidLength 32 (BS.length b))
  | C.decide (C.and (W.gt w zeroWider) (W.lt w orderWider)) = Right $! SecScalar (S.to w)
  | C.decide (W.gt w zeroWider) = Left ScalarOutOfRange
  | otherwise = Left ScalarZero
 where
  w = widerFromBytes b

{- | @int(b) mod n@ as a secret scalar, for exactly 32 bytes @b@: a hash
that becomes a secret nonce, or the stored bytes of a scalar already in
\([0, n)\) (which the reduction leaves unchanged).

The reduction is the conversion into Montgomery form: a Montgomery
multiplication by \(R^2 \bmod n\) followed by one constant-time conditional
subtraction. It is exact for every 256-bit input because
\(b \cdot (R^2 \bmod n) < R \cdot n\).
-}
secScalarFromBytesModQ :: ByteString -> SecScalar
secScalarFromBytesModQ b = SecScalar (S.to (widerFromBytes b))

-- | The 32-byte big-endian encoding of a secret scalar, in constant time.
secScalarToBytes :: SecScalar -> ByteString
secScalarToBytes = widerToBytes . secScalarWider

-- | A secret scalar as a plain t'Wider' word in \([0, n)\), e.g. for @ppad-secp256k1@'s @mul_wnaf@.
secScalarWider :: SecScalar -> Wider
secScalarWider (SecScalar m) = S.retr m

-- | A public 'Integer' in Montgomery form (variable-time in the 'Integer').
publicMontgomery :: Integer -> S.Montgomery
publicMontgomery x = S.to (W.to_vartime (modQ x))

-- | A public flag as a 'C.Choice'.
choice :: Bool -> C.Choice
choice True = C.true# ()
choice False = C.false# ()

-- Secret arithmetic ----------------------------------------------------------

{- | The scalar of a BIP-327 partial signature:

\[
s = k_1' + b \cdot k_2' + e \cdot a \cdot d \pmod n
\]

where \(k_i' = n - k_i\) when the final nonce \(R\) has odd \(y\) and
\(d = g \cdot gacc \cdot d'\), i.e. \(d'\) negated when
\(g \cdot gacc = -1\).

This is the only place where secret scalars are combined. The secrets stay
in Montgomery form throughout: additions, multiplications and the
negations (selected with a constant-time 'S.select', although both flags are
public) run in constant time. Only the result \(s\), the public partial
signature, is converted to an 'Integer'.
-}
signScalar ::
  -- | \(k_1\) (secret).
  SecScalar ->
  -- | \(k_2\) (secret).
  SecScalar ->
  -- | \(d'\), the secret key (secret).
  SecScalar ->
  -- | Negate the nonce (\(R\) has odd \(y\)).
  Bool ->
  -- | Negate the secret key (\(g \cdot gacc = n - 1\)).
  Bool ->
  -- | Nonce coefficient \(b\) (public).
  Integer ->
  -- | \(e \cdot a \bmod n\) (public).
  Integer ->
  -- | \(s\) (public).
  Integer
signScalar (SecScalar k1) (SecScalar k2) (SecScalar d) negateNonce negateKey b ea =
  let k = S.add k1 (S.mul (publicMontgomery b) k2)
      k' = S.select k (S.neg k) (choice negateNonce)
      d' = S.select d (S.neg d) (choice negateKey)
   in W.from_vartime (S.retr (S.add k' (S.mul (publicMontgomery ea) d')))

-- Constant-time byte conversions ---------------------------------------------

{- | The first 32 bytes, big-endian, as a t'Wider' word: shifts and ors with
no branch on the data.

These conversions are vendored instead of @ppad-secp256k1@'s
@unsafe_roll32@ and @unroll32@, which that package exports for testing and
benchmarking only. Precondition: at least 32 bytes (checked; the length is
public).
-}
widerFromBytes :: ByteString -> Wider
widerFromBytes b
  | BS.length b < 32 = error "widerFromBytes: fewer than 32 bytes"
  | otherwise = W.wider (limbAt 24) (limbAt 16) (limbAt 8) (limbAt 0)
 where
  limbAt :: Int -> Word
  limbAt off =
    byteAt off 56
      .|. byteAt (off + 1) 48
      .|. byteAt (off + 2) 40
      .|. byteAt (off + 3) 32
      .|. byteAt (off + 4) 24
      .|. byteAt (off + 5) 16
      .|. byteAt (off + 6) 8
      .|. byteAt (off + 7) 0
  byteAt :: Int -> Int -> Word
  byteAt i s = fromIntegral (BU.unsafeIndex b i) `unsafeShiftL` s

-- | A t'Wider' word as 32 big-endian bytes, with no branch on the data.
widerToBytes :: Wider -> ByteString
widerToBytes (Wider (# Limb w0, Limb w1, Limb w2, Limb w3 #)) = BI.unsafeCreate 32 $ \p -> do
  pokeLimb p 0 (W# w3)
  pokeLimb p 8 (W# w2)
  pokeLimb p 16 (W# w1)
  pokeLimb p 24 (W# w0)
 where
  pokeLimb :: Ptr Word8 -> Int -> Word -> IO ()
  pokeLimb p off w = do
    pokeByte p off (w `unsafeShiftR` 56)
    pokeByte p (off + 1) (w `unsafeShiftR` 48)
    pokeByte p (off + 2) (w `unsafeShiftR` 40)
    pokeByte p (off + 3) (w `unsafeShiftR` 32)
    pokeByte p (off + 4) (w `unsafeShiftR` 24)
    pokeByte p (off + 5) (w `unsafeShiftR` 16)
    pokeByte p (off + 6) (w `unsafeShiftR` 8)
    pokeByte p (off + 7) w
  pokeByte :: Ptr Word8 -> Int -> Word -> IO ()
  pokeByte p off x = pokeByteOff p off (fromIntegral x :: Word8)