packages feed

haskoin-core-0.20.4: src/Haskoin/Keys/Common.hs

{-# LANGUAGE DeriveAnyClass    #-}
{-# LANGUAGE DeriveGeneric     #-}
{-# LANGUAGE FlexibleContexts  #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase        #-}
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-|
Module      : Haskoin.Keys.Common
Copyright   : No rights reserved
License     : MIT
Maintainer  : jprupp@protonmail.ch
Stability   : experimental
Portability : POSIX

ECDSA private and public key functions.
-}
module Haskoin.Keys.Common
    ( -- * Public & Private Keys
      PubKeyI(..)
    , SecKeyI(..)
    , exportPubKey
    , importPubKey
    , wrapPubKey
    , derivePubKeyI
    , wrapSecKey
    , fromMiniKey
    , tweakPubKey
    , tweakSecKey
    , getSecKey
    , secKey
    -- ** Private Key Wallet Import Format (WIF)
    , fromWif
    , toWif
    ) where

import           Control.DeepSeq
import           Control.Monad           (guard, mzero, (<=<))
import           Crypto.Secp256k1
import           Data.Aeson              (FromJSON, ToJSON (..), Value (String),
                                          parseJSON, withText)
import           Data.Aeson.Encoding     (unsafeToEncoding)
import           Data.Binary             (Binary (..))
import           Data.ByteString         (ByteString)
import qualified Data.ByteString         as BS
import           Data.ByteString.Builder (char7)
import           Data.Bytes.Get
import           Data.Bytes.Put
import           Data.Bytes.Serial
import           Data.Hashable
import           Data.Maybe              (fromMaybe)
import           Data.Serialize          (Serialize (..))
import           Data.String             (IsString, fromString)
import           Data.String.Conversions (cs)
import           GHC.Generics            (Generic)
import           Haskoin.Address.Base58
import           Haskoin.Constants
import           Haskoin.Crypto.Hash
import           Haskoin.Util

-- | Elliptic curve public key type with expected serialized compression flag.
data PubKeyI = PubKeyI
    { pubKeyPoint      :: !PubKey
    , pubKeyCompressed :: !Bool
    } deriving (Generic, Eq, Show, Read, Hashable, NFData)

instance IsString PubKeyI where
    fromString str =
        fromMaybe e $ eitherToMaybe . runGetS deserialize <=< decodeHex $ cs str
      where
        e = error "Could not decode public key"

instance ToJSON PubKeyI where
    toJSON = String . encodeHex . runPutS . serialize
    toEncoding s = unsafeToEncoding $
        char7 '"' <>
        hexBuilder (runPutL (serialize s)) <>
        char7 '"'

instance FromJSON PubKeyI where
    parseJSON = withText "PubKeyI" $
        maybe mzero return . ((eitherToMaybe . runGetS deserialize) <=< decodeHex)

instance Serial PubKeyI where
    deserialize = s >>= \case
        True  -> c
        False -> u
      where
        s = lookAhead $ getWord8 >>= \case
            0x02 -> return True
            0x03 -> return True
            0x04 -> return False
            _    -> fail "Not a public key"
        c = do
            bs <- getByteString 33
            maybe (fail "Could not decode public key") return $
                PubKeyI <$> importPubKey bs <*> pure True
        u = do
            bs <- getByteString 65
            maybe (fail "Could not decode public key") return $
                PubKeyI <$> importPubKey bs <*> pure False

    serialize pk = putByteString $ exportPubKey (pubKeyCompressed pk) (pubKeyPoint pk)

instance Serialize PubKeyI where
    put = serialize
    get = deserialize

instance Binary PubKeyI where
    put = serialize
    get = deserialize

-- | Wrap a public key from secp256k1 library adding information about compression.
wrapPubKey :: Bool -> PubKey -> PubKeyI
wrapPubKey c p = PubKeyI p c

-- | Derives a public key from a private key. This function will preserve
-- compression flag.
derivePubKeyI :: SecKeyI -> PubKeyI
derivePubKeyI (SecKeyI d c) = PubKeyI (derivePubKey d) c

-- | Tweak a public key.
tweakPubKey :: PubKey -> Hash256 -> Maybe PubKey
tweakPubKey p h = tweakAddPubKey p =<< tweak (runPutS (serialize h))

-- | Elliptic curve private key type with expected public key compression
-- information. Compression information is stored in private key WIF formats and
-- needs to be preserved to generate the correct address from the corresponding
-- public key.
data SecKeyI = SecKeyI
    { secKeyData       :: !SecKey
    , secKeyCompressed :: !Bool
    } deriving (Eq, Show, Read, Generic, NFData)

-- | Wrap private key with corresponding public key compression flag.
wrapSecKey :: Bool -> SecKey -> SecKeyI
wrapSecKey c d = SecKeyI d c

-- | Tweak a private key.
tweakSecKey :: SecKey -> Hash256 -> Maybe SecKey
tweakSecKey key h = tweakAddSecKey key =<< tweak (runPutS (serialize h))

-- | Decode Casascius mini private keys (22 or 30 characters).
fromMiniKey :: ByteString -> Maybe SecKeyI
fromMiniKey bs = do
    guard checkShortKey
    wrapSecKey False <$> secKey (runPutS (serialize (sha256 bs)))
  where
    checkHash = runPutS $ serialize $ sha256 $ bs `BS.append` "?"
    checkShortKey = BS.length bs `elem` [22, 30] && BS.head checkHash == 0x00

-- | Decode private key from WIF (wallet import format) string.
fromWif :: Network -> Base58 -> Maybe SecKeyI
fromWif net wif = do
    bs <- decodeBase58Check wif
    -- Check that this is a private key
    guard (BS.head bs == getSecretPrefix net)
    case BS.length bs of
        -- Uncompressed format
        33 -> wrapSecKey False <$> secKey (BS.tail bs)
        -- Compressed format
        34 -> do
            guard $ BS.last bs == 0x01
            wrapSecKey True <$> secKey (BS.tail $ BS.init bs)
        -- Bad length
        _  -> Nothing

-- | Encode private key into a WIF string.
toWif :: Network -> SecKeyI -> Base58
toWif net (SecKeyI k c) =
    encodeBase58Check . BS.cons (getSecretPrefix net) $
    if c
        then getSecKey k `BS.snoc` 0x01
        else getSecKey k