packages feed

discord-haskell-voice-3.0.0: src/Discord/Internal/Voice/Encryption.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-|
Module      : Discord.Internal.Voice.Encryption
Description : Strictly for internal use only. See Discord.Voice for the public interface.
Copyright   : (c) 2021-2022 Yuto Takano
              (c) 2025-PRESENT discord-haskell-voice Contributors
License     : MIT
Maintainer  : Yuto Takano <moa17stock@gmail.com>

= WARNING

This module is considered __internal__.

The Package Versioning Policy __does not apply__.

The contents of this module may change __in any way whatsoever__ and __without__
__any warning__ between minor versions of this package, unless the identifier is
re-exported from a non-internal module.

= Description

This module provides encryption and decryption of secretbox schemes, abstracting
over either Saltine/Libsodium (standard) or Crypton.
-}
module Discord.Internal.Voice.Encryption
    ( module Discord.Internal.Voice.Encryption
    ) where

#ifdef USE_CRYPTON
import Crypto.PubKey.Curve25519 qualified as X25519
import Crypto.SecretBox qualified as SecretBox
import Crypto.Error ( maybeCryptoError )
#else
import Crypto.Saltine.Core.SecretBox
    ( secretboxOpen
    , secretbox
    )
import Crypto.Saltine.Class qualified as SC
#endif

import Data.ByteString qualified as B
import Data.Maybe ( fromJust )
import Data.Word ( Word8 )


-- | @decrypt byteKey nonce ciphertext@ decrypts a packet using the provided
-- Discord key (32-bytes) and header nonce (24-bytes). The argument uses strict
-- bytestrings because it has to be strict when passed to FFI/Saltine anyway.
--
-- This does no error handling on misformatted key/nonce since this function is
-- only used in contexts where we are guaranteed they are valid.
--
-- When USE_CRYPTON is defined (using the use-crypton flag), the function
-- is implemented as a wrapper for the 'SecretBox.open' function.
decrypt :: [Word8] -> B.ByteString -> B.ByteString -> Maybe B.ByteString
#ifdef USE_CRYPTON
decrypt byteKey nonce ciphertext = SecretBox.open ciphertext nonce key
  where
    key = fromJust $ maybeCryptoError $ X25519.dhSecret $ B.pack byteKey
#else
decrypt byteKey byteNonce og = secretboxOpen key nonce og
  where
    key = fromJust $ SC.decode $ B.pack byteKey
    nonce = fromJust $ SC.decode byteNonce
#endif

-- | @encrypt byteKey nonce message@ encrypts an audio packet using the provided
-- Discord key and (32-bytes) header nonce (24-bytes). The argument uses strict
-- bytestrings because it has to be strict when passed to FFI/Saltine anyway.
--
-- As with decryption, this function does no error handling on the format of the
-- key and nonce (key = 32 bytes, nonce = 24 bytes).
--
-- When USE_CRYPTON is defined (using the use-crypton flag), the function
-- is implemented as a warpper for the 'SecretBox.create' function.
encrypt :: [Word8] -> B.ByteString -> B.ByteString -> B.ByteString
#ifdef USE_CRYPTON
encrypt byteKey nonce message = SecretBox.create message nonce key
  where
    key = fromJust $ maybeCryptoError $ X25519.dhSecret $ B.pack byteKey
#else
encrypt byteKey byteNonce og = secretbox key nonce og
  where
    key = fromJust $ SC.decode $ B.pack byteKey
    nonce = fromJust $ SC.decode byteNonce
#endif