-
Notifications
You must be signed in to change notification settings - Fork 213
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
port address derivation module from cardano-wallet-legacy
- Loading branch information
Showing
2 changed files
with
288 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,276 @@ | ||
{-# LANGUAGE DataKinds #-} | ||
{-# LANGUAGE DeriveGeneric #-} | ||
{-# LANGUAGE DerivingStrategies #-} | ||
{-# LANGUAGE FlexibleInstances #-} | ||
{-# LANGUAGE KindSignatures #-} | ||
{-# LANGUAGE LambdaCase #-} | ||
{-# LANGUAGE MultiParamTypeClasses #-} | ||
{-# LANGUAGE TypeApplications #-} | ||
|
||
-- | | ||
-- Copyright: © 2018-2019 IOHK | ||
-- License: MIT | ||
-- | ||
-- Primitives for performing address derivation for some given schemes. This is | ||
-- where most of the crypto happens in the wallet and, it is quite important to | ||
-- ensure that the following implementation matches with other wallet softwares | ||
-- (like Yoroi/Icarus or the cardano-cli) | ||
|
||
module Cardano.Wallet.AddressDerivation | ||
( | ||
-- * Polymorphic / General Purpose Types | ||
Key | ||
, Depth (..) | ||
, Index | ||
, getIndex | ||
, DerivationType (..) | ||
, Passphrase(..) | ||
, publicKey | ||
|
||
-- * Sequential Derivation | ||
, ChangeChain(..) | ||
, generateKeyFromSeed | ||
, unsafeGenerateKeyFromSeed | ||
, deriveAccountPrivateKey | ||
, deriveAddressPrivateKey | ||
, deriveAddressPublicKey | ||
) where | ||
|
||
import Prelude | ||
|
||
import Cardano.Crypto.Wallet | ||
( DerivationScheme (..) | ||
, XPrv | ||
, XPub | ||
, deriveXPrv | ||
, deriveXPub | ||
, generateNew | ||
, toXPub | ||
) | ||
import Data.ByteArray | ||
( ScrubbedBytes ) | ||
import Data.ByteString | ||
( ByteString ) | ||
import Data.Maybe | ||
( fromMaybe ) | ||
import Data.Word | ||
( Word32 ) | ||
import GHC.Generics | ||
( Generic ) | ||
|
||
|
||
{------------------------------------------------------------------------------- | ||
Polymorphic / General Purpose Types | ||
-------------------------------------------------------------------------------} | ||
|
||
-- | A cryptographic key, with phantom-types to disambiguate key types. | ||
-- | ||
-- @ | ||
-- let rootPrivateKey = Key 'RootK XPrv | ||
-- let accountPubKey = Key 'AccountK XPub | ||
-- let addressPubKey = Key 'AddressK XPub | ||
-- @ | ||
newtype Key (level :: Depth) key = Key key | ||
deriving stock (Generic, Show, Eq) | ||
|
||
-- | Key Depth in the derivation path, according to BIP-0039 / BIP-0044 | ||
-- | ||
-- root' / purpose' / cointype' / account' / change / address | ||
-- | ||
-- We do not manipulate purpose, cointype and change paths directly, so they are | ||
-- left out of the sum type. | ||
data Depth = RootK | AccountK | AddressK | ||
|
||
-- | A derivation index, with phantom-types to disambiguate derivation type. | ||
-- | ||
-- @ | ||
-- let accountIx = Index 'Hardened 'AccountK | ||
-- let addressIx = Index 'Soft 'AddressK | ||
-- @ | ||
newtype Index (derivationType :: DerivationType) (level :: Depth) = Index | ||
{ getIndex :: Word32 } | ||
deriving stock (Show, Eq, Ord) | ||
|
||
instance Bounded (Index 'Hardened level) where | ||
minBound = Index 0x80000000 | ||
maxBound = Index maxBound | ||
|
||
instance Bounded (Index 'Soft level) where | ||
minBound = Index minBound | ||
maxBound = let (Index ix) = minBound @(Index 'Hardened _) in Index (ix - 1) | ||
|
||
instance Enum (Index 'Hardened level) where | ||
fromEnum (Index ix) = fromIntegral ix | ||
toEnum ix | ||
| Index (fromIntegral ix) < minBound @(Index 'Hardened _) = | ||
error "Index@Hardened.toEnum: bad argument" | ||
| otherwise = | ||
Index (fromIntegral ix) | ||
|
||
instance Enum (Index 'Soft level) where | ||
fromEnum (Index ix) = fromIntegral ix | ||
toEnum ix | ||
| Index (fromIntegral ix) > maxBound @(Index 'Soft _) = | ||
error "Index@Soft.toEnum: bad argument" | ||
| otherwise = | ||
Index (fromIntegral ix) | ||
|
||
|
||
-- | Type of derivation that should be used with the given indexes. | ||
data DerivationType = Hardened | Soft | ||
|
||
-- | An encapsulated passphrase. The inner format is free, but the wrapper helps | ||
-- readability in function signatures. | ||
-- | ||
-- Note that the internal type is a 'ScrubbedBytes' and not a plain | ||
-- 'ByteString', which if fairly important from a crypto / security POV. | ||
newtype Passphrase = Passphrase ScrubbedBytes | ||
deriving stock (Show) | ||
|
||
-- | Extract the public key part of a private key. | ||
publicKey | ||
:: Key level XPrv | ||
-> Key level XPub | ||
publicKey (Key xprv) = | ||
Key (toXPub xprv) | ||
|
||
|
||
{------------------------------------------------------------------------------- | ||
Sequential Derivation | ||
-------------------------------------------------------------------------------} | ||
|
||
-- | Marker for the change chain. In practice, change of a transaction goes onto | ||
-- the addresses generated on the internal chain, whereas the external chain is | ||
-- used for addresses that are part of the 'advertised' targets of a transaction | ||
data ChangeChain | ||
= InternalChain | ||
| ExternalChain | ||
deriving (Generic, Show, Eq) | ||
|
||
-- Not deriving 'Enum' because this could have a dramatic impact if we were | ||
-- to assign the wrong index to the corresponding constructor (by swapping | ||
-- around the constructor above for instance). | ||
instance Enum ChangeChain where | ||
toEnum = \case | ||
0 -> ExternalChain | ||
1 -> InternalChain | ||
_ -> error "ChangeChain.toEnum: bad argument" | ||
fromEnum = \case | ||
ExternalChain -> 0 | ||
InternalChain -> 1 | ||
|
||
-- | Purpose is a constant set to 44' (or 0x8000002C) following the BIP-44 | ||
-- recommendation. It indicates that the subtree of this node is used | ||
-- according to this specification. | ||
-- | ||
-- Hardened derivation is used at this level. | ||
purposeIndex :: Word32 | ||
purposeIndex = 0x8000002C | ||
|
||
-- | One master node (seed) can be used for unlimited number of independent | ||
-- cryptocoins such as Bitcoin, Litecoin or Namecoin. However, sharing the | ||
-- same space for various cryptocoins has some disadvantages. | ||
-- | ||
-- This level creates a separate subtree for every cryptocoin, avoiding reusing | ||
-- addresses across cryptocoins and improving privacy issues. | ||
-- | ||
-- Coin type is a constant, set for each cryptocoin. For Cardano this constant | ||
-- is set to 1815' (or 0x80000717). 1815 is the birthyear of our beloved Ada | ||
-- Lovelace. | ||
-- | ||
-- Hardened derivation is used at this level. | ||
coinTypeIndex :: Word32 | ||
coinTypeIndex = 0x80000717 | ||
|
||
-- | Generate a new key from seed. Note that the @depth@ is left open so that | ||
-- the caller gets to decide what type of key this is. This is mostly for | ||
-- testing, in practice, seeds are used to represent root keys, and one should | ||
-- use 'generateKeyFromSeed'. | ||
unsafeGenerateKeyFromSeed | ||
:: ByteString -- ^ The actual seed | ||
-> Passphrase | ||
-> Key depth XPrv | ||
unsafeGenerateKeyFromSeed seed (Passphrase storagePwd) = | ||
Key $ generateNew seed recoveryPwd storagePwd | ||
where | ||
-- Mnemonic recovery passphrase, see # | ||
recoveryPwd :: ByteString | ||
recoveryPwd = mempty | ||
|
||
-- | Generate a root key from a corresponding seed | ||
generateKeyFromSeed | ||
:: ByteString -- ^ The actual seed | ||
-> Passphrase | ||
-> Key 'RootK XPrv | ||
generateKeyFromSeed = unsafeGenerateKeyFromSeed | ||
|
||
-- | Derives account private key from the given root private key, using | ||
-- derivation scheme 2 (see <https://github.com/input-output-hk/cardano-crypto/ cardano-crypto> | ||
-- package for more details). | ||
-- | ||
-- NOTE: The caller is expected to provide the corresponding passphrase. | ||
deriveAccountPrivateKey | ||
:: Passphrase | ||
-> Key 'RootK XPrv | ||
-> Index 'Hardened 'AccountK | ||
-> Key 'AccountK XPrv | ||
deriveAccountPrivateKey (Passphrase pwd) (Key rootXPrv) (Index accIx) = | ||
let | ||
purposeXPrv = -- lvl1 derivation; hardened derivation of purpose' | ||
deriveXPrv DerivationScheme2 pwd rootXPrv purposeIndex | ||
coinTypeXPrv = -- lvl2 derivation; hardened derivation of coin_type' | ||
deriveXPrv DerivationScheme2 pwd purposeXPrv coinTypeIndex | ||
acctXPrv = -- lvl3 derivation; hardened derivation of account' index | ||
deriveXPrv DerivationScheme2 pwd coinTypeXPrv accIx | ||
in | ||
Key acctXPrv | ||
|
||
-- | Derives address private key from the given account private key, using | ||
-- derivation scheme 2 (see <https://github.com/input-output-hk/cardano-crypto/ cardano-crypto> | ||
-- package for more details). | ||
-- | ||
-- NOTE: The caller is expected to provide the corresponding passphrase. It is | ||
-- preferred to use 'deriveAddressPublicKey' whenever possible to avoid having | ||
-- to manipulate passphrases and private keys. | ||
deriveAddressPrivateKey | ||
:: Passphrase | ||
-> Key 'AccountK XPrv | ||
-> ChangeChain | ||
-> Index 'Soft 'AddressK | ||
-> Key 'AddressK XPrv | ||
deriveAddressPrivateKey (Passphrase pwd) (Key accXPrv) changeChain (Index addrIx) = | ||
let | ||
changeCode = | ||
fromIntegral $ fromEnum changeChain | ||
changeXPrv = -- lvl4 derivation; soft derivation of change chain | ||
deriveXPrv DerivationScheme2 pwd accXPrv changeCode | ||
addrXPrv = -- lvl5 derivation; soft derivation of address index | ||
deriveXPrv DerivationScheme2 pwd changeXPrv addrIx | ||
in | ||
Key addrXPrv | ||
|
||
-- | Derives address public key from the given account public key, using | ||
-- derivation scheme 2 (see <https://github.com/input-output-hk/cardano-crypto/ cardano-crypto> | ||
-- package for more details). | ||
-- | ||
-- This is the preferred way of deriving new sequential address public keys. | ||
deriveAddressPublicKey | ||
:: Key 'AccountK XPub | ||
-> ChangeChain | ||
-> Index 'Soft 'AddressK | ||
-> Key 'AddressK XPub | ||
deriveAddressPublicKey (Key accXPub) changeChain (Index addrIx) = | ||
fromMaybe errWrongIndex $ do | ||
let changeCode = fromIntegral $ fromEnum changeChain | ||
changeXPub <- -- lvl4 derivation in bip44 is derivation of change chain | ||
deriveXPub DerivationScheme2 accXPub changeCode | ||
addrXPub <- -- lvl5 derivation in bip44 is derivation of address chain | ||
deriveXPub DerivationScheme2 changeXPub addrIx | ||
return $ Key addrXPub | ||
where | ||
errWrongIndex = error $ | ||
"Cardano.Wallet.AddressDerivation.deriveAddressPublicKey failed: \ | ||
\was given an hardened (or too big) index for soft path derivation \ | ||
\( " ++ show addrIx ++ "). This is either a programmer error, or, \ | ||
\we may have reached the maximum number of addresses for a given \ | ||
\wallet." |