packages feed

web3-polkadot-1.0.1.0: src/Network/Polkadot/Crypto.hs

{-# LANGUAGE TypeFamilies #-}

-- |
-- Module      :  Network.Polkadot.Crypto
-- Copyright   :  Aleksandr Krupenkin 2016-2024
-- License     :  Apache-2.0
--
-- Maintainer  :  mail@akru.me
-- Stability   :  experimental
-- Portability :  portable
--
-- Polkadot signing types and methods.
--

module Network.Polkadot.Crypto
  ( Pair(..)
  , Verify(..)
  , MultiPair(..)
  , Ed25519
  , Ecdsa
  ) where

import qualified Crypto.Ecdsa.Signature      as Ecdsa (pack, sign)
import           Crypto.Ecdsa.Utils          as Ecdsa (derivePubKey,
                                                       exportPubKeyCompress,
                                                       importKey)
import           Crypto.Error                (CryptoFailable (..))
import qualified Crypto.PubKey.ECC.ECDSA     as Ecdsa (PrivateKey (..),
                                                       PublicKey (..))
import qualified Crypto.PubKey.ECC.Generate  as Ecdsa (generate)
import           Crypto.PubKey.ECC.Types     (CurveName (SEC_p256k1),
                                              getCurveByName)
import qualified Crypto.PubKey.Ed25519       as Ed25519 (PublicKey, SecretKey,
                                                         generateSecretKey,
                                                         secretKey, sign,
                                                         toPublic)
import           Data.BigNum                 (H256, H512, h256, h512)
import           Data.ByteArray              (ByteArrayAccess, uncons)
import qualified Data.ByteArray              as BA (length)
import           Data.ByteString             (ByteString)
import qualified Data.ByteString             as BS (last, take)
import           Data.Maybe                  (fromJust)
import           Data.Text                   (Text)
import           Data.Word                   (Word8)

import           Network.Polkadot.Account    (IdentifyAccount (..))
import qualified Network.Polkadot.Primitives as P (MultiAddress (..),
                                                   MultiSignature (..),
                                                   MultiSigner (..))

-- | Class suitable for typical cryptographic PKI key pair type.
class Pair a where
    -- | The type which is used to encode a public key.
    type PublicKey a
    -- | The type used to represent a signature. Can be created from a key pair and a message
    -- and verified with the message and a public key.
    type Signature a
    -- | Generate new secure (random) key pair.
    generate :: IO a
    -- | Generate new key pair from the provided `seed`.
    from_seed :: ByteArrayAccess ba => ba -> Either String a
    -- | Generate key pair from given recovery phrase and password.
    from_phrase :: Text -> Maybe Text -> Either String a
    -- | Get a public key.
    public :: a -> PublicKey a
    -- | Sign a message.
    sign :: ByteArrayAccess ba => a -> ba -> Signature a

-- | Means of signature verification.
class Verify a where
    -- | Verify a message.
    verify :: (IdentifyAccount s, ByteArrayAccess ba)
           => a
           -- ^ Message signature.
           -> ba
           -- ^ Message content.
           -> s
           -- ^ Type of the signer.
           -> Bool
           -- ^ Returns `true` if signature is valid for the value.

-- | Multiple cryptographic type support.
class Pair a => MultiPair a where
    -- | Universal short representation of signer account.
    type MultiAddress a
    -- | Universal signer account identifier.
    type MultiSigner a
    -- | Universal signature representation.
    type MultiSignature a
    -- | Derive universal account address.
    multi_address :: a -> MultiAddress a
    -- | Derive universal signer account identifier.
    multi_signer :: a -> MultiSigner a
    -- | Sign message and derive universal signature.
    multi_sign :: ByteArrayAccess ba => a -> ba -> MultiSignature a

-- | Ed25519 cryptographic pair.
data Ed25519 = Ed25519 !Ed25519.PublicKey !Ed25519.SecretKey
  deriving Eq

instance Show Ed25519 where
    show = ("Ed25519 " ++) . show . public

instance Pair Ed25519 where
    type PublicKey Ed25519 = H256
    type Signature Ed25519 = H512
    generate = do
        sec <- Ed25519.generateSecretKey
        return $ Ed25519 (Ed25519.toPublic sec) sec
    from_seed seed = case Ed25519.secretKey seed of
        CryptoPassed sec -> Right $ Ed25519 (Ed25519.toPublic sec) sec
        CryptoFailed e   -> Left (show e)
    from_phrase = undefined
    public (Ed25519 pub _) = fromJust $ h256 pub
    sign (Ed25519 pub sec) input = ed25519_sign sec pub input

-- | ECDSA cryptographic pair.
data Ecdsa = Ecdsa !Ecdsa.PublicKey !Ecdsa.PrivateKey
  deriving Eq

instance Show Ecdsa where
    show = ("Ecdsa " ++) . show . public

instance Pair Ecdsa where
    type PublicKey Ecdsa = (Word8, H256)
    type Signature Ecdsa = (H512, Word8)
    generate = uncurry Ecdsa <$> Ecdsa.generate (getCurveByName SEC_p256k1)
    from_seed seed
        | BA.length seed == 32 = let sec = Ecdsa.importKey seed
                                  in Right $ Ecdsa (Ecdsa.derivePubKey sec) sec
        | otherwise = Left "Seed should be 32 byte length"
    from_phrase = undefined
    public (Ecdsa pub _) = pack $ uncons $ Ecdsa.exportPubKeyCompress pub
      where
        pack :: Maybe (Word8, ByteString) -> (Word8, H256)
        pack (Just (px, key)) = (px, fromJust (h256 key))
        pack _                = error "impossible branch"
    sign (Ecdsa _ sec) input = ecdsa_sign sec input

ed25519_sign :: ByteArrayAccess a => Ed25519.SecretKey -> Ed25519.PublicKey -> a -> H512
ed25519_sign sec pub = fromJust . h512 . Ed25519.sign sec pub

ecdsa_sign :: ByteArrayAccess a => Ecdsa.PrivateKey -> a -> (H512, Word8)
ecdsa_sign sec input = (fromJust $ h512 $ BS.take 64 rsv, BS.last rsv)
  where
    rsv = Ecdsa.pack (Ecdsa.sign sec input)

instance MultiPair Ed25519 where
    type MultiSigner Ed25519 = P.MultiSigner
    type MultiAddress Ed25519 = P.MultiAddress
    type MultiSignature Ed25519 = P.MultiSignature
    multi_signer = P.Ed25519Signer . public
    multi_address = P.MaId . into_account . multi_signer
    multi_sign = (P.Ed25519Signature .) . sign

instance MultiPair Ecdsa where
    type MultiSigner Ecdsa = P.MultiSigner
    type MultiAddress Ecdsa = P.MultiAddress
    type MultiSignature Ecdsa = P.MultiSignature
    multi_signer = uncurry P.EcdsaSigner . public
    multi_address = P.MaId . into_account . multi_signer
    multi_sign = (uncurry P.EcdsaSignature .) . sign