packages feed

morley-1.4.0: src/Tezos/Crypto/P256.hs

-- SPDX-FileCopyrightText: 2020 Tocqueville Group
--
-- SPDX-License-Identifier: LicenseRef-MIT-TQ

-- | P256 cryptographic primitives.
--
-- This module is mostly a stub, it doesn't implement actual crypto.
-- TODO (#18) implement crypto properly.

module Tezos.Crypto.P256
  ( -- * Cryptographic primitive types
    PublicKey (..)
  , SecretKey
  , Signature (..)
  , detSecretKey
  , toPublic

  -- * Generators
  , genPublicKey
  , genSecretKey
  , genSignature

  -- * Raw bytes (no checksums, tags or anything)
  , publicKeyToBytes
  , mkPublicKey
  , publicKeyLengthBytes
  , signatureToBytes
  , mkSignature
  , signatureLengthBytes

  -- * Formatting and parsing
  , formatPublicKey
  , mformatPublicKey
  , parsePublicKey
  , formatSignature
  , mformatSignature
  , parseSignature

  -- * Signing
  , checkSignature
  ) where

import Crypto.Random (getRandomBytes)
import Data.ByteArray (ByteArray, ByteArrayAccess)
import qualified Data.ByteArray as BA
import qualified Data.ByteString as BS
import Fmt (Buildable, build)
import Hedgehog (MonadGen)
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range
import Test.QuickCheck (Arbitrary(..), vector)

import Michelson.Text
import Tezos.Crypto.Util

----------------------------------------------------------------------------
-- Types, instances, conversions
----------------------------------------------------------------------------

-- | P256 public cryptographic key.
newtype PublicKey = PublicKey
  { unPublicKey :: ByteString
  } deriving stock (Show, Eq, Generic)

instance Arbitrary PublicKey where
  arbitrary = toPublic <$> arbitrary

instance NFData PublicKey

genPublicKey :: MonadGen m => m PublicKey
genPublicKey = toPublic <$> genSecretKey

-- | P256 secret cryptographic key.
newtype SecretKey = SecretKey
  { unSecretKey :: ByteString
  } deriving stock (Show, Eq, Generic)

instance NFData SecretKey

-- | Deterministicaly generate a secret key from seed.
detSecretKey :: ByteString -> SecretKey
detSecretKey seed =
  SecretKey $ deterministic seed $ getRandomBytes publicKeyLengthBytes

instance Arbitrary SecretKey where
  arbitrary = detSecretKey . BS.pack <$> vector 32

genSecretKey :: MonadGen m => m SecretKey
genSecretKey = detSecretKey <$> Gen.bytes (Range.singleton 32)

-- | Create a public key from a secret key.
toPublic :: SecretKey -> PublicKey
toPublic = PublicKey . unSecretKey

-- | P256 cryptographic signature.
newtype Signature = Signature
  { unSignature :: ByteString
  } deriving stock (Show, Eq, Generic)

instance Arbitrary Signature where
  arbitrary = Signature . BS.pack <$> replicateM signatureLengthBytes arbitrary

instance NFData Signature

genSignature :: MonadGen m => m Signature
genSignature = Signature <$> Gen.bytes (Range.singleton signatureLengthBytes)

----------------------------------------------------------------------------
-- Conversion to/from raw bytes (no checksums, tags or anything)
----------------------------------------------------------------------------

-- | Convert a 'PublicKey' to raw bytes.
--
-- TODO (#18): implement properly.
publicKeyToBytes :: forall ba. ByteArray ba => PublicKey -> ba
publicKeyToBytes = BA.convert . unPublicKey

-- | Make a 'PublicKey' from raw bytes.
--
-- TODO (#18): implement properly.
mkPublicKey :: ByteArrayAccess ba => ba -> Either CryptoParseError PublicKey
mkPublicKey ba
  | l == publicKeyLengthBytes =
    Right $ PublicKey (BA.convert ba)
  | otherwise =
    Left $ CryptoParseUnexpectedLength "public key" l
  where
    l = BA.length ba

publicKeyLengthBytes :: Integral n => n
publicKeyLengthBytes = 33

-- | Convert a 'PublicKey' to raw bytes.
--
-- TODO (#18): implement properly.
signatureToBytes :: ByteArray ba => Signature -> ba
signatureToBytes = BA.convert . unSignature

-- | Make a 'Signature' from raw bytes.
--
-- TODO (#18): implement properly.
mkSignature :: ByteArray ba => ba -> Either CryptoParseError Signature
mkSignature ba
  | l == signatureLengthBytes =
    Right $ Signature (BA.convert ba)
  | otherwise =
    Left $ CryptoParseUnexpectedLength "signature" l
  where
    l = BA.length ba

signatureLengthBytes :: Integral n => n
signatureLengthBytes = 64

----------------------------------------------------------------------------
-- Magic bytes
----------------------------------------------------------------------------

publicKeyTag :: ByteString
publicKeyTag = "\003\178\139\127"

signatureTag :: ByteString
signatureTag = "\054\240\044\052"

----------------------------------------------------------------------------
-- Formatting
----------------------------------------------------------------------------

formatPublicKey :: PublicKey -> Text
formatPublicKey = formatImpl @ByteString publicKeyTag . publicKeyToBytes

mformatPublicKey :: PublicKey -> MText
mformatPublicKey = mkMTextUnsafe . formatPublicKey

instance Buildable PublicKey where
  build = build . formatPublicKey

parsePublicKey :: Text -> Either CryptoParseError PublicKey
parsePublicKey = parseImpl publicKeyTag mkPublicKey

formatSignature :: Signature -> Text
formatSignature = formatImpl @ByteString signatureTag . signatureToBytes

mformatSignature :: Signature -> MText
mformatSignature = mkMTextUnsafe . formatSignature

instance Buildable Signature where
  build = build . formatSignature

parseSignature :: Text -> Either CryptoParseError Signature
parseSignature = parseImpl signatureTag mkSignature

----------------------------------------------------------------------------
-- Signing
----------------------------------------------------------------------------

-- | Check that a sequence of bytes has been signed with a given key.
checkSignature :: PublicKey -> Signature -> ByteString -> Bool
checkSignature _ _ _ = False