Skip to content

Commit

Permalink
CIP-1855: Add primitives for MintBurn policy key derivation
Browse files Browse the repository at this point in the history
- Add primitives for deriving policy keys used to mint/burn assets. Implemented
  according to [CIP-1855](https://github.com/cardano-foundation/CIPs/blob/b2e9d02cb9a71ba9e754a432c78197428abf7e4c/CIP-1855/CIP-1855.md).
- Add "PolicyK" option to the "Depth" enum.
- Add simple tests for key derivation.
  • Loading branch information
sevanspowell committed Jul 28, 2021
1 parent b722abc commit c05ea21
Show file tree
Hide file tree
Showing 4 changed files with 204 additions and 1 deletion.
2 changes: 2 additions & 0 deletions lib/core/cardano-wallet-core.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ library
Cardano.Wallet.Primitive.AddressDerivation
Cardano.Wallet.Primitive.AddressDerivation.Byron
Cardano.Wallet.Primitive.AddressDerivation.Icarus
Cardano.Wallet.Primitive.AddressDerivation.MintBurn
Cardano.Wallet.Primitive.AddressDerivation.Shared
Cardano.Wallet.Primitive.AddressDerivation.SharedKey
Cardano.Wallet.Primitive.AddressDerivation.Shelley
Expand Down Expand Up @@ -362,6 +363,7 @@ test-suite unit
Cardano.Wallet.NetworkSpec
Cardano.Wallet.Primitive.AddressDerivation.ByronSpec
Cardano.Wallet.Primitive.AddressDerivation.IcarusSpec
Cardano.Wallet.Primitive.AddressDerivation.MintBurnSpec
Cardano.Wallet.Primitive.AddressDerivationSpec
Cardano.Wallet.Primitive.AddressDiscovery.RandomSpec
Cardano.Wallet.Primitive.AddressDiscovery.SequentialSpec
Expand Down
9 changes: 8 additions & 1 deletion lib/core/src/Cardano/Wallet/Primitive/AddressDerivation.hs
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,14 @@ import qualified Data.Text.Encoding as T
--
-- @m | purpose' | cointype' | account' | role | address@
data Depth
= RootK | PurposeK | CoinTypeK | AccountK | RoleK | AddressK | ScriptK
= RootK
| PurposeK
| CoinTypeK
| AccountK
| RoleK
| AddressK
| ScriptK
| PolicyK

-- | Marker for addresses type engaged. We want to handle four cases here.
-- The first two are pertinent to UTxO accounting,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
{-# LANGUAGE DataKinds #-}

-- |
-- Copyright: © 2018-2021 IOHK
-- License: Apache-2.0
--
-- Derivation of policy keys which are used to create scripts for the purposes
-- of minting and burning. Derived according to CIP-1855
-- (https://github.com/cardano-foundation/CIPs/blob/b2e9d02cb9a71ba9e754a432c78197428abf7e4c/CIP-1855/CIP-1855.md).
--
-- The policy keys are derived from the following path:
--
-- m / purpose' / coin_type' / policy_ix'
-- m / 1855' / 1815' / [2^31 .. 2^32-1]'
--
-- Where purpose' and coin_type' are fixed, and each new policy_ix' represents a
-- different policy key.

module Cardano.Wallet.Primitive.AddressDerivation.MintBurn
( -- * Constants
purposeCIP1855
-- * Helpers
, derivePolicyKey
, derivePolicyPrivateKey
) where

import Prelude

import Cardano.Address.Derivation
( XPrv )
import Cardano.Address.Script
( KeyHash )
import Cardano.Crypto.Wallet
( deriveXPrv )
import Cardano.Crypto.Wallet.Types
( DerivationScheme (DerivationScheme2) )
import Cardano.Wallet.Primitive.AddressDerivation
( Depth (PolicyK, PurposeK, RootK)
, DerivationType (Hardened)
, Index (getIndex)
, Index (Index)
, Passphrase (Passphrase)
, WalletKey (publicKey)
, getRawKey
, hashVerificationKey
, liftRawKey
)
import Cardano.Wallet.Primitive.AddressDiscovery
( coinTypeAda )

import qualified Cardano.Address.Script as CA

-- | Purpose for forged policy keys is a constant set to 1855' (or 0x8000073F)
-- following the original CIP-1855: "Forging policy keys for HD Wallets".
--
-- It indicates that the subtree of this node is used according to this
-- specification.
--
-- Hardened derivation is used at this level.
purposeCIP1855 :: Index 'Hardened 'PurposeK
purposeCIP1855 = toEnum 0x8000073F

-- | Derive the policy private key that should be used to create mint/burn
-- scripts.
derivePolicyPrivateKey
:: Passphrase purpose
-- ^ Passphrase for wallet
-> XPrv
-- ^ Root private key to derive policy private key from
-> Index 'Hardened 'PolicyK
-- ^ Index of policy script
-> XPrv
-- ^ Policy private key
derivePolicyPrivateKey (Passphrase pwd) rootXPrv (Index policyIx) =
let
purposeXPrv = -- lvl1 derivation; hardened derivation of purpose'
deriveXPrv DerivationScheme2 pwd rootXPrv (getIndex purposeCIP1855)
coinTypeXPrv = -- lvl2 derivation; hardened derivation of coin_type'
deriveXPrv DerivationScheme2 pwd purposeXPrv (getIndex coinTypeAda)
-- lvl3 derivation; hardened derivation of policy' index
in deriveXPrv DerivationScheme2 pwd coinTypeXPrv policyIx

-- | Derive the policy private key that should be used to create mint/burn
-- scripts, as well as the key hash of the policy public key.
derivePolicyKey
:: WalletKey key
=> Passphrase "encryption"
-- ^ Passphrase for wallet
-> key 'RootK XPrv
-- ^ Root private key to derive policy private key from
-> Index 'Hardened 'PolicyK
-- ^ Index of policy script
-> (key 'PolicyK XPrv, KeyHash)
-- ^ Policy private key
derivePolicyKey pwd rootPrv policyIx = (policyK, vkeyHash)
where
policyK = liftRawKey policyPrv
policyPrv = derivePolicyPrivateKey pwd (getRawKey rootPrv) policyIx
vkeyHash = hashVerificationKey CA.Payment (publicKey policyK)
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}

{-# OPTIONS_GHC -Wno-orphans #-}

module Cardano.Wallet.Primitive.AddressDerivation.MintBurnSpec
( spec
) where

import Prelude

import Cardano.Address.Derivation
( XPrv, XPub )
import Cardano.Address.Script
( KeyHash )
import Cardano.Wallet.Primitive.AddressDerivation
( Depth (PolicyK, RootK, ScriptK)
, DerivationType (Hardened)
, Index
, Passphrase
, WalletKey (publicKey)
, getRawKey
, hashVerificationKey
, liftRawKey
)
import Cardano.Wallet.Primitive.AddressDerivation.MintBurn
( derivePolicyKey, derivePolicyPrivateKey )
import Cardano.Wallet.Primitive.AddressDerivation.Shelley
( ShelleyKey )
import Cardano.Wallet.Primitive.AddressDerivationSpec
()
import Cardano.Wallet.Unsafe
( unsafeXPrv )
import qualified Data.ByteString as BS
import Test.Hspec
( Spec, describe, it )
import Test.Hspec.Extra
( parallel )
import Test.QuickCheck
( Arbitrary (..), Property, property, vector, (===) )
import Test.QuickCheck.Arbitrary
( arbitraryBoundedEnum )

import qualified Cardano.Address.Script as CA

spec :: Spec
spec = do
parallel $ describe "Mint/Burn Policy key Address Derivation Properties" $ do
it "Policy key derivation from master key works for various indexes" $
property prop_keyDerivationFromXPrv
it "Policy public key hash matches private key" $
property prop_keyHashMatchesXPrv

{-------------------------------------------------------------------------------
Properties
-------------------------------------------------------------------------------}

prop_keyDerivationFromXPrv
:: Passphrase "encryption"
-> XPrv
-> Index 'Hardened 'PolicyK
-> Property
prop_keyDerivationFromXPrv pwd masterkey policyIx =
rndKey `seq` property () -- NOTE Making sure this doesn't throw
where
rndKey :: XPrv
rndKey = derivePolicyPrivateKey pwd masterkey policyIx

prop_keyHashMatchesXPrv
:: Passphrase "encryption"
-> ShelleyKey 'RootK XPrv
-> Index 'Hardened 'PolicyK
-> Property
prop_keyHashMatchesXPrv pwd masterkey policyIx =
hashVerificationKey
CA.Payment
(getPublicKey rndKey)
=== keyHash
where
rndKey :: ShelleyKey 'PolicyK XPrv
keyHash :: KeyHash
(rndKey, keyHash) = derivePolicyKey pwd masterkey policyIx

getPublicKey
:: ShelleyKey 'PolicyK XPrv
-> ShelleyKey 'ScriptK XPub
getPublicKey =
publicKey . (liftRawKey :: XPrv -> ShelleyKey 'ScriptK XPrv) . getRawKey

instance Arbitrary XPrv where
arbitrary = unsafeXPrv . BS.pack <$> vector 128

instance Arbitrary (Index 'Hardened 'PolicyK) where
shrink _ = []
arbitrary = arbitraryBoundedEnum

0 comments on commit c05ea21

Please sign in to comment.