-
Notifications
You must be signed in to change notification settings - Fork 42
/
Derivation.hs
414 lines (362 loc) · 10.8 KB
/
Derivation.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# OPTIONS_HADDOCK prune #-}
module Cardano.Address.Derivation
(
-- * Overview
-- $overview
-- * Key Derivation
-- ** Types
Index
, Depth (..)
, DerivationType (..)
-- * Abstractions
, GenMasterKey (..)
, HardDerivation (..)
, SoftDerivation (..)
-- * Low-Level Cryptography Primitives
-- ** XPrv
, XPrv
, xprvFromBytes
, xprvToBytes
, xprvPrivateKey
, xprvChainCode
, toXPub
-- ** XPub
, XPub
, xpubFromBytes
, xpubToBytes
, xpubPublicKey
, xpubChainCode
-- ** XSignature
, XSignature
, sign
, verify
-- Internal / Not exposed by Haddock
, DerivationScheme (..)
, deriveXPrv
, deriveXPub
, generate
, generateNew
------------------
) where
import Prelude
import Cardano.Crypto.Wallet
( DerivationScheme (..) )
import Cardano.Mnemonic
( SomeMnemonic )
import Control.DeepSeq
( NFData )
import Crypto.Error
( eitherCryptoError )
import Data.ByteArray
( ByteArrayAccess, ScrubbedBytes )
import Data.ByteString
( ByteString )
import Data.Either.Extra
( eitherToMaybe )
import Data.String
( fromString )
import Data.Word
( Word32 )
import Fmt
( Buildable (..) )
import GHC.Generics
( Generic )
import GHC.Stack
( HasCallStack )
import qualified Cardano.Crypto.Wallet as CC
import qualified Crypto.ECC.Edwards25519 as Ed25519
import qualified Data.ByteString as BS
-- $overview
--
-- These abstractions allow generating root private key, also called /Master Key/
-- and then basing on it enable address derivation
--
-- Low-Level Cryptography Primitives
--
-- | An opaque type representing an extended private key.
--
-- __Properties:__
--
-- ===== Roundtripping
--
-- @forall xprv. 'xprvFromBytes' ('xprvToBytes' xprv) == 'Just' xprv@
--
-- ===== Chain Code Invariance
--
-- @forall xprv. 'xprvChainCode' xprv == 'xpubChainCode' ('toXPub' xprv)@
--
-- ===== Public Key Signature
--
-- @forall xprv msg. 'verify' ('toXPub' xprv) msg ('sign' xprv msg) == 'True'@
--
-- @since 1.0.0
type XPrv = CC.XPrv
-- | An opaque type representing an extended public key.
--
-- __Properties:__
--
-- ===== Roundtripping
--
-- @forall xpub. 'xpubFromBytes' ('xpubToBytes' xpub) == 'Just' xpub@
--
-- @since 1.0.0
type XPub = CC.XPub
-- | An opaque type representing a signature made from an 'XPrv'.
--
-- @since 1.0.0
type XSignature = CC.XSignature
-- | Construct an 'XPub' from raw 'ByteString' (64 bytes).
--
-- @since 1.0.0
xpubFromBytes :: ByteString -> Maybe XPub
xpubFromBytes = eitherToMaybe . CC.xpub
-- | Convert an 'XPub' to a raw 'ByteString' (64 bytes).
--
-- @since 1.0.0
xpubToBytes :: XPub -> ByteString
xpubToBytes xpub = xpubPublicKey xpub <> xpubChainCode xpub
-- | Extract the public key from an 'XPub' as a raw 'ByteString' (32 bytes).
--
-- @since 2.0.0
xpubPublicKey :: XPub -> ByteString
xpubPublicKey (CC.XPub pub _cc) = pub
-- | Extract the chain code from an 'XPub' as a raw 'ByteString' (32 bytes).
--
-- @since 2.0.0
xpubChainCode :: XPub -> ByteString
xpubChainCode (CC.XPub _pub (CC.ChainCode cc)) = cc
-- | Construct an 'XPrv' from raw 'ByteString' (96 bytes).
--
-- @since 1.0.0
xprvFromBytes :: ByteString -> Maybe XPrv
xprvFromBytes bytes
| BS.length bytes /= 96 = Nothing
| otherwise = do
let (prv, cc) = BS.splitAt 64 bytes
pub <- ed25519ScalarMult (BS.take 32 prv)
eitherToMaybe $ CC.xprv $ prv <> pub <> cc
where
ed25519ScalarMult :: ByteString -> Maybe ByteString
ed25519ScalarMult bs = do
scalar <- eitherToMaybe $ eitherCryptoError $ Ed25519.scalarDecodeLong bs
pure $ Ed25519.pointEncode $ Ed25519.toPoint scalar
-- From < xprv | pub | cc >
-- ↳ To < xprv | | cc >
--
-- | Convert an 'XPrv' to a raw 'ByteString' (96 bytes).
--
-- @since 1.0.0
xprvToBytes :: XPrv -> ByteString
xprvToBytes xprv =
xprvPrivateKey xprv <> xprvChainCode xprv
-- | Extract the private key from an 'XPrv' as a raw 'ByteString' (64 bytes).
--
-- @since 2.0.0
xprvPrivateKey :: XPrv -> ByteString
xprvPrivateKey = BS.take 64 . CC.unXPrv
-- | Extract the chain code from an 'XPrv' as a raw 'ByteString' (32 bytes).
--
-- @since 2.0.0
xprvChainCode :: XPrv -> ByteString
xprvChainCode = BS.drop 96 . CC.unXPrv
-- | Derive the 'XPub' associated with an 'XPrv'.
--
-- @since 1.0.0
toXPub :: HasCallStack => XPrv -> XPub
toXPub = CC.toXPub
-- | Produce a signature of the given 'msg' from an 'XPrv'.
--
-- @since 1.0.0
sign
:: ByteArrayAccess msg
=> XPrv
-> msg
-> XSignature
sign =
CC.sign (mempty :: ScrubbedBytes)
-- | Verify the 'XSignature' of a 'msg' with the 'XPub' associated with the
-- 'XPrv' used for signing.
--
-- @since 1.0.0
verify
:: ByteArrayAccess msg
=> XPub
-> msg
-> XSignature
-> Bool
verify =
CC.verify -- re-exported for the sake of documentation.
-- Derive a child extended private key from an extended private key
--
-- __internal__
deriveXPrv
:: DerivationScheme
-> XPrv
-> Index derivationType depth
-> XPrv
deriveXPrv ds prv (Index ix) =
CC.deriveXPrv ds (mempty :: ScrubbedBytes) prv ix
-- Derive a child extended public key from an extended public key
--
-- __internal__
deriveXPub
:: DerivationScheme
-> XPub
-> Index derivationType depth
-> Maybe XPub
deriveXPub ds pub (Index ix) =
CC.deriveXPub ds pub ix
-- Generate an XPrv using the legacy method (Byron).
--
-- The seed needs to be at least 32 bytes, otherwise an asynchronous error is thrown.
--
-- __internal__
generate
:: ByteArrayAccess seed
=> seed
-> XPrv
generate seed =
CC.generate seed (mempty :: ScrubbedBytes)
-- Generate an XPrv using the new method (Icarus).
--
-- The seed needs to be at least 16 bytes.
--
-- __internal__
generateNew
:: (ByteArrayAccess seed, ByteArrayAccess sndFactor)
=> seed
-> sndFactor
-> XPrv
generateNew seed sndFactor =
CC.generateNew seed sndFactor (mempty :: ScrubbedBytes)
--
-- Key Derivation
--
-- | Key Depth in the derivation path, according to BIP-0039 / BIP-0044
--
-- @
-- root | purpose' | cointype' | account' | change | address@
-- 0th 1st 2nd 3rd 4th 5th
-- @
--
-- We do not manipulate purpose, cointype and change paths directly, so there
-- are no constructors for these.
--
-- @since 1.0.0
data Depth = RootK | AccountK | AddressK | StakingK | MultisigK
-- | A derivation index, with phantom-types to disambiguate derivation type.
--
-- @
-- let accountIx = Index 'Hardened 'AccountK
-- let addressIx = Index 'Soft 'AddressK
-- @
--
-- @since 1.0.0
newtype Index (derivationType :: DerivationType) (depth :: Depth) = Index
{ getIndex :: Word32 }
deriving stock (Generic, Show, Eq, Ord)
instance NFData (Index derivationType depth)
instance Bounded (Index 'Hardened depth) where
minBound = Index 0x80000000
maxBound = Index maxBound
instance Bounded (Index 'Soft depth) where
minBound = Index minBound
maxBound = let (Index ix) = minBound @(Index 'Hardened _) in Index (ix - 1)
instance Bounded (Index 'WholeDomain depth) where
minBound = Index minBound
maxBound = Index maxBound
instance Enum (Index 'Hardened depth) 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 depth) where
fromEnum (Index ix) = fromIntegral ix
toEnum ix
| Index (fromIntegral ix) > maxBound @(Index 'Soft _) =
error "Index@Soft.toEnum: bad argument"
| otherwise =
Index (fromIntegral ix)
instance Enum (Index 'WholeDomain depth) where
fromEnum (Index ix) = fromIntegral ix
toEnum ix
| Index (fromIntegral ix) > maxBound @(Index 'WholeDomain _) =
error "Index@WholeDomain.toEnum: bad argument"
| otherwise =
Index (fromIntegral ix)
instance Buildable (Index derivationType depth) where
build (Index ix) = fromString (show ix)
-- | Type of derivation that should be used with the given indexes.
--
-- In theory, we should only consider two derivation types: soft and hard.
--
-- However, historically, addresses in Cardano used to be generated across both
-- the soft and the hard domain. We therefore introduce a 'WholeDomain' derivation
-- type that is the exact union of `Hardened` and `Soft`.
--
-- @since 1.0.0
data DerivationType = Hardened | Soft | WholeDomain
-- | An interface for doing hard derivations from the root private key, /Master Key/
--
-- @since 1.0.0
class HardDerivation (key :: Depth -> * -> *) where
type AccountIndexDerivationType key :: DerivationType
type AddressIndexDerivationType key :: DerivationType
type WithRole key :: *
-- | 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).
--
-- @since 1.0.0
deriveAccountPrivateKey
:: key 'RootK XPrv
-> Index (AccountIndexDerivationType key) 'AccountK
-> key 'AccountK XPrv
-- | 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).
--
-- @since 1.0.0
deriveAddressPrivateKey
:: key 'AccountK XPrv
-> WithRole key
-> Index (AddressIndexDerivationType key) 'AddressK
-> key 'AddressK XPrv
-- | An interface for doing soft derivations from an account public key
class HardDerivation key => SoftDerivation (key :: Depth -> * -> *) where
-- | 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.
--
-- @since 1.0.0
deriveAddressPublicKey
:: key 'AccountK XPub
-> WithRole key
-> Index 'Soft 'AddressK
-> key 'AddressK XPub
-- | Abstract interface for constructing a /Master Key/.
--
-- @since 1.0.0
class GenMasterKey (key :: Depth -> * -> *) where
type SecondFactor key :: *
-- | Generate a root key from a corresponding mnemonic.
--
-- @since 1.0.0
genMasterKeyFromMnemonic
:: SomeMnemonic -> SecondFactor key -> key 'RootK XPrv
-- | Generate a root key from a corresponding root 'XPrv'
--
-- @since 1.0.0
genMasterKeyFromXPrv
:: XPrv -> key 'RootK XPrv