Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Derive token policy forging keys according to CIP-1855 #2774

Merged
2 changes: 2 additions & 0 deletions lib/core/cardano-wallet-core.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,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 @@ -359,6 +360,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
36 changes: 32 additions & 4 deletions lib/core/src/Cardano/Wallet/Primitive/AddressDerivation.hs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ module Cardano.Wallet.Primitive.AddressDerivation
, DerivationPrefix (..)
, DerivationIndex (..)
, liftIndex
, hashVerificationKey

-- * Delegation
, RewardAccount (..)
Expand Down Expand Up @@ -85,7 +86,7 @@ module Cardano.Wallet.Primitive.AddressDerivation
import Prelude

import Cardano.Address.Derivation
( XPrv, XPub )
( XPrv, XPub, xpubPublicKey )
import Cardano.Mnemonic
( SomeMnemonic )
import Cardano.Wallet.Primitive.Types
Expand All @@ -103,7 +104,7 @@ import Control.Monad
import Crypto.Hash
( Digest, HashAlgorithm )
import Crypto.Hash.Utils
( blake2b256 )
( blake2b224, blake2b256 )
import Crypto.KDF.PBKDF2
( Parameters (..), fastPBKDF2_SHA512 )
import Crypto.Random.Types
Expand Down Expand Up @@ -151,6 +152,8 @@ import Quiet
import Safe
( readMay, toEnumMay )

import Cardano.Address.Script
( KeyHash (KeyHash), KeyRole )
import qualified Codec.CBOR.Encoding as CBOR
import qualified Codec.CBOR.Write as CBOR
import qualified Crypto.Scrypt as Scrypt
Expand All @@ -163,11 +166,28 @@ import qualified Data.Text.Encoding as T
HD Hierarchy
-------------------------------------------------------------------------------}

-- | Key Depth in the derivation path, according to BIP-0044 / CIP-1852
-- | Typically used as a phantom type parameter, a witness to the type of the
-- key being used.
--
-- For example, @key 'RootK XPrv@, represents the private key at the root of the
-- HD hierarchy.
--
-- According to BIP-0044 / CIP-1852, we have the following keys in our HD
-- hierarchy:
--
-- @m | purpose' | cointype' | account' | role | address@
--
-- Plus, we also have script keys (which are used in shared wallets) and policy
-- keys (which are used in minting and burning).
data Depth
= RootK | PurposeK | CoinTypeK | AccountK | RoleK | AddressK | ScriptK
= RootK
| PurposeK
| CoinTypeK
| AccountK
| RoleK
| AddressK
| ScriptK
| PolicyK
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better update all the comments about Depth, starting with the one just above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmmm... not ideal. Kinda brutalizing the abstraction here, but 'ScriptK did it first and it's the path of least resistance...

Copy link
Contributor Author

@sevanspowell sevanspowell Jul 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated doco.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah if it weren't for ScriptK I would have asked you to try and find a better way of representing depth.


-- | Marker for addresses type engaged. We want to handle four cases here.
-- The first two are pertinent to UTxO accounting,
Expand Down Expand Up @@ -492,6 +512,14 @@ deriveRewardAccount pwd rootPrv =
let accPrv = deriveAccountPrivateKey pwd rootPrv minBound
in deriveAddressPrivateKey pwd accPrv MutableAccount minBound

hashVerificationKey
:: WalletKey key
=> KeyRole
-> key depth XPub
-> KeyHash
hashVerificationKey keyRole =
KeyHash keyRole . blake2b224 . xpubPublicKey . getRawKey

{-------------------------------------------------------------------------------
Passphrases
-------------------------------------------------------------------------------}
Expand Down
100 changes: 100 additions & 0 deletions lib/core/src/Cardano/Wallet/Primitive/AddressDerivation/MintBurn.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
{-# 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:/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
, derivePolicyKeyAndHash
, 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 (..)
, DerivationType (..)
, Index (..)
, Passphrase (..)
, WalletKey
, getIndex
, getRawKey
, hashVerificationKey
, liftRawKey
, publicKey
)
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.
derivePolicyKeyAndHash
:: 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
derivePolicyKeyAndHash pwd rootPrv policyIx = (policyK, vkeyHash)
where
policyK = liftRawKey policyPrv
policyPrv = derivePolicyPrivateKey pwd (getRawKey rootPrv) policyIx
vkeyHash = hashVerificationKey CA.Payment (publicKey policyK)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Purpose field here doesn't look right ... right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean the CA.Payment value? I stripped out the Role -> KeyRole indirection because I found that confusing when initially using the function. As someone who wasn't familiar with the wallet, Payment | Delegation :: KeyRole made a lot more sense than UtxoExternal | UtxoInternal | MutableAccount :: Role, and I figured since I was the only one using it, I might as well use the simpler option. Also it doesn't feel like there's much need for abstraction here, we're literally reaching in and manipulating bytes. But I'm not strongly opposed to putting the Role -> KeyRole indirection back in.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's all fine, but KeyHash { role = Payment } ? Actually I suppose Payment is suitable for this.

Loading