-
Notifications
You must be signed in to change notification settings - Fork 213
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
Changes from 7 commits
1cb2ff7
726386f
35f6d12
a14f6cb
fb7d7ef
c1015f7
c149e55
1a74768
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Purpose field here doesn't look right ... right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You mean the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah that's all fine, but |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,145 @@ | ||||||||
{-# 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 | ||||||||
( derivePolicyKeyAndHash, 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 | ||||||||
it "The same index always returns the same private key" $ | ||||||||
property prop_keyDerivationSameIndexSameKey | ||||||||
it "A different index always returns a different private key" $ | ||||||||
property prop_keyDerivationDiffIndexDiffKey | ||||||||
it "Using derivePolicyKeyAndHash returns same private key as using derivePolicyPrivateKey" $ | ||||||||
property prop_keyDerivationRelation | ||||||||
|
||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps one or two golden tests that you have generated manually with cardano-cli? |
||||||||
{------------------------------------------------------------------------------- | ||||||||
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) = derivePolicyKeyAndHash pwd masterkey policyIx | ||||||||
|
||||||||
getPublicKey | ||||||||
:: ShelleyKey 'PolicyK XPrv | ||||||||
-> ShelleyKey 'ScriptK XPub | ||||||||
getPublicKey = | ||||||||
publicKey . (liftRawKey :: XPrv -> ShelleyKey 'ScriptK XPrv) . getRawKey | ||||||||
|
||||||||
prop_keyDerivationSameIndexSameKey | ||||||||
:: Passphrase "encryption" | ||||||||
-> XPrv | ||||||||
-> Index 'Hardened 'PolicyK | ||||||||
-> Property | ||||||||
prop_keyDerivationSameIndexSameKey pwd masterkey policyIx = | ||||||||
key1 === key2 | ||||||||
where | ||||||||
key1 :: XPrv | ||||||||
key2 :: XPrv | ||||||||
key1 = derivePolicyPrivateKey pwd masterkey policyIx | ||||||||
key2 = derivePolicyPrivateKey pwd masterkey policyIx | ||||||||
|
||||||||
prop_keyDerivationDiffIndexDiffKey | ||||||||
:: Passphrase "encryption" | ||||||||
-> XPrv | ||||||||
-> Index 'Hardened 'PolicyK | ||||||||
-> Index 'Hardened 'PolicyK | ||||||||
-> Property | ||||||||
prop_keyDerivationDiffIndexDiffKey pwd masterkey policyIx1 policyIx2 = | ||||||||
key1 =/= key2 | ||||||||
where | ||||||||
key1 :: XPrv | ||||||||
key2 :: XPrv | ||||||||
Comment on lines
+147
to
+148
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
... or just leave out the type annotations. |
||||||||
key1 = derivePolicyPrivateKey pwd masterkey policyIx1 | ||||||||
key2 = derivePolicyPrivateKey pwd masterkey policyIx2 | ||||||||
|
||||||||
prop_keyDerivationRelation | ||||||||
:: Passphrase "encryption" | ||||||||
-> XPrv | ||||||||
-> Index 'Hardened 'PolicyK | ||||||||
-> Property | ||||||||
prop_keyDerivationRelation pwd masterkey policyIx = | ||||||||
key1 === key2 | ||||||||
where | ||||||||
key1 :: XPrv | ||||||||
key1 = derivePolicyPrivateKey pwd masterkey policyIx | ||||||||
|
||||||||
keyAndHash :: (ShelleyKey 'PolicyK XPrv, KeyHash) | ||||||||
keyAndHash = derivePolicyKeyAndHash pwd (liftRawKey masterkey) policyIx | ||||||||
|
||||||||
key2 :: XPrv | ||||||||
key2 = getRawKey $ fst keyAndHash | ||||||||
|
||||||||
instance Arbitrary XPrv where | ||||||||
arbitrary = unsafeXPrv . BS.pack <$> vector 128 | ||||||||
|
||||||||
instance Arbitrary (Index 'Hardened 'PolicyK) where | ||||||||
shrink _ = [] | ||||||||
arbitrary = arbitraryBoundedEnum |
There was a problem hiding this comment.
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.There was a problem hiding this comment.
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...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated doco.
There was a problem hiding this comment.
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.