packages feed

cardano-crypto-1.3.0: src/Cardano/Crypto/Wallet/Pure.hs

{-# LANGUAGE BangPatterns        #-}
{-# LANGUAGE DeriveGeneric       #-}
{-# LANGUAGE GADTs               #-}
{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Cardano.Crypto.Wallet.Pure
    ( XPrv(..)
    , XPub(..)
    , xprvPub
    , deriveXPrv
    , deriveXPrvHardened
    , deriveXPub
    , hInitSeed
    , hFinalize
    ) where

import           Control.DeepSeq             (NFData)
import qualified Crypto.Math.Edwards25519    as Edwards25519
import           Crypto.Hash                 (SHA512)
import qualified Crypto.MAC.HMAC             as HMAC
--import qualified Crypto.PubKey.Ed25519       as Ed25519
import           Data.Bits
import           Data.ByteArray              (ByteArrayAccess, convert)
import qualified Data.ByteArray              as B (splitAt)
import           Data.ByteString             (ByteString)
import qualified Data.ByteString             as B (pack)
import           Data.Hashable               (Hashable)
import           Data.Word
import           GHC.Generics                (Generic)

import           Cardano.Crypto.Wallet.Types

data XPrv = XPrv !Edwards25519.Scalar !ChainCode

data XPub = XPub !Edwards25519.PointCompressed !ChainCode
    deriving (Eq, Ord, Show, Generic)

instance NFData XPub
instance Hashable XPub

xprvPub :: XPrv -> ByteString
xprvPub (XPrv s _) =
    Edwards25519.unPointCompressed $ Edwards25519.scalarToPoint s

deriveXPrv :: XPrv -> Word32 -> XPrv
deriveXPrv (XPrv sec ccode) n =
    let !pub     = Edwards25519.scalarToPoint sec
        (iL, iR) = walletHash $ DerivationHashNormal pub ccode n
        !derived = Edwards25519.scalar iL
     in XPrv (Edwards25519.scalarAdd sec derived) iR

deriveXPrvHardened :: XPrv -> Word32 -> XPrv
deriveXPrvHardened (XPrv sec ccode) n =
    let (iL, iR) = walletHash $ DerivationHashHardened sec ccode n
     in XPrv (Edwards25519.scalar iL) iR

-- | Derive a child public from an extended public key
deriveXPub :: XPub -> Word32 -> XPub
deriveXPub (XPub pub ccode) n =
    let (iL, iR) = walletHash $ DerivationHashNormal pub ccode n
        !derived = Edwards25519.scalarToPoint $ Edwards25519.scalar iL
     in XPub (Edwards25519.pointAdd pub derived) iR


-- hashing methods either hardened or normal
data DerivationHash where
    DerivationHashHardened :: Edwards25519.Scalar          -> ChainCode -> Word32 -> DerivationHash
    DerivationHashNormal   :: Edwards25519.PointCompressed -> ChainCode -> Word32 -> DerivationHash

walletHash :: DerivationHash -> (ByteString, ChainCode)
walletHash (DerivationHashHardened sec cc w) =
    hFinalize
            $ flip HMAC.update (encodeIndex w)
            $ flip HMAC.update (Edwards25519.unScalar sec)
            $ flip HMAC.update hardenedTag
            $ hInit cc
walletHash (DerivationHashNormal pub cc w) =
    hFinalize
            $ flip HMAC.update (encodeIndex w)
            $ flip HMAC.update (Edwards25519.unPointCompressed pub)
            $ flip HMAC.update normalTag
            $ hInit cc

hardenedTag :: ByteString
hardenedTag = B.pack $ map (fromIntegral . fromEnum) "HARD"
normalTag :: ByteString
normalTag   = B.pack $ map (fromIntegral . fromEnum) "NORM"

-- | Encode a Word32 in Big endian
encodeIndex :: Word32 -> ByteString
encodeIndex w = B.pack [d,c,b,a]
  where
    a = fromIntegral w
    b = fromIntegral (w `shiftR` 8)
    c = fromIntegral (w `shiftR` 16)
    d = fromIntegral (w `shiftR` 24)

hInit :: ChainCode -> HMAC.Context SHA512
hInit (ChainCode key) = HMAC.initialize key

hInitSeed :: ByteArrayAccess seed => seed -> HMAC.Context SHA512
hInitSeed seed = HMAC.initialize seed

hFinalize :: HMAC.Context SHA512 -> (ByteString, ChainCode)
hFinalize ctx =
    let (b1, b2) = B.splitAt 32 $ convert $ HMAC.finalize ctx
     in (b1, ChainCode b2)