@@ -10,7 +10,7 @@ use p256::{
10
10
generic_array:: { typenum:: Unsigned , GenericArray } ,
11
11
Curve ,
12
12
} ,
13
- NistP256 ,
13
+ AffinePoint , NistP256 , Scalar ,
14
14
} ;
15
15
use rand:: { CryptoRng , RngCore } ;
16
16
use zeroize:: ZeroizeOnDrop ;
@@ -35,6 +35,138 @@ lazy_static::lazy_static! {
35
35
static ref SECP256R1_OID : simple_asn1:: OID = simple_asn1:: oid!( 1 , 2 , 840 , 10045 , 3 , 1 , 7 ) ;
36
36
}
37
37
38
+ /// A component of a derivation path
39
+ #[ derive( Clone , Debug ) ]
40
+ pub struct DerivationIndex ( pub Vec < u8 > ) ;
41
+
42
+ /// Derivation Path
43
+ ///
44
+ /// A derivation path is simply a sequence of DerivationIndex
45
+ #[ derive( Clone , Debug ) ]
46
+ pub struct DerivationPath {
47
+ path : Vec < DerivationIndex > ,
48
+ }
49
+
50
+ impl DerivationPath {
51
+ /// Create a BIP32-style derivation path
52
+ ///
53
+ /// See SLIP-10 <https://github.com/satoshilabs/slips/blob/master/slip-0010.md>
54
+ /// for details of derivation paths
55
+ pub fn new_bip32 ( bip32 : & [ u32 ] ) -> Self {
56
+ let mut path = Vec :: with_capacity ( bip32. len ( ) ) ;
57
+ for n in bip32 {
58
+ path. push ( DerivationIndex ( n. to_be_bytes ( ) . to_vec ( ) ) ) ;
59
+ }
60
+ Self :: new ( path)
61
+ }
62
+
63
+ /// Create a free-form derivation path
64
+ pub fn new ( path : Vec < DerivationIndex > ) -> Self {
65
+ Self { path }
66
+ }
67
+
68
+ /// Create a path from a canister ID and a user provided path
69
+ pub fn from_canister_id_and_path ( canister_id : & [ u8 ] , path : & [ Vec < u8 > ] ) -> Self {
70
+ let mut vpath = Vec :: with_capacity ( 1 + path. len ( ) ) ;
71
+ vpath. push ( DerivationIndex ( canister_id. to_vec ( ) ) ) ;
72
+
73
+ for n in path {
74
+ vpath. push ( DerivationIndex ( n. to_vec ( ) ) ) ;
75
+ }
76
+ Self :: new ( vpath)
77
+ }
78
+
79
+ /// Return the length of this path
80
+ pub fn len ( & self ) -> usize {
81
+ self . path . len ( )
82
+ }
83
+
84
+ /// Return if this path is empty
85
+ pub fn is_empty ( & self ) -> bool {
86
+ self . len ( ) == 0
87
+ }
88
+
89
+ /// Return the components of the derivation path
90
+ pub fn path ( & self ) -> & [ DerivationIndex ] {
91
+ & self . path
92
+ }
93
+
94
+ fn ckd ( idx : & [ u8 ] , input : & [ u8 ] , chain_code : & [ u8 ; 32 ] ) -> ( [ u8 ; 32 ] , Scalar ) {
95
+ use hmac:: { Hmac , Mac } ;
96
+ use p256:: elliptic_curve:: ops:: Reduce ;
97
+ use sha2:: Sha512 ;
98
+
99
+ let mut hmac = Hmac :: < Sha512 > :: new_from_slice ( chain_code)
100
+ . expect ( "HMAC-SHA-512 should accept 256 bit key" ) ;
101
+
102
+ hmac. update ( input) ;
103
+ hmac. update ( idx) ;
104
+
105
+ let hmac_output: [ u8 ; 64 ] = hmac. finalize ( ) . into_bytes ( ) . into ( ) ;
106
+
107
+ let fb = p256:: FieldBytes :: from_slice ( & hmac_output[ ..32 ] ) ;
108
+ let next_offset = <p256:: Scalar as Reduce < p256:: U256 > >:: reduce_bytes ( fb) ;
109
+ let next_chain_key: [ u8 ; 32 ] = hmac_output[ 32 ..] . to_vec ( ) . try_into ( ) . expect ( "Correct size" ) ;
110
+
111
+ // If iL >= order, try again with the "next" index as described in SLIP-10
112
+ if next_offset. to_bytes ( ) . to_vec ( ) != hmac_output[ ..32 ] {
113
+ let mut next_input = [ 0u8 ; 33 ] ;
114
+ next_input[ 0 ] = 0x01 ;
115
+ next_input[ 1 ..] . copy_from_slice ( & next_chain_key) ;
116
+ Self :: ckd ( idx, & next_input, chain_code)
117
+ } else {
118
+ ( next_chain_key, next_offset)
119
+ }
120
+ }
121
+
122
+ fn ckd_pub (
123
+ idx : & [ u8 ] ,
124
+ pt : AffinePoint ,
125
+ chain_code : & [ u8 ; 32 ] ,
126
+ ) -> ( [ u8 ; 32 ] , Scalar , AffinePoint ) {
127
+ use p256:: elliptic_curve:: { group:: GroupEncoding , ops:: MulByGenerator } ;
128
+ use p256:: ProjectivePoint ;
129
+
130
+ let mut ckd_input = pt. to_bytes ( ) ;
131
+
132
+ let pt: ProjectivePoint = pt. into ( ) ;
133
+
134
+ loop {
135
+ let ( next_chain_code, next_offset) = Self :: ckd ( idx, & ckd_input, chain_code) ;
136
+
137
+ let next_pt = ( pt + ProjectivePoint :: mul_by_generator ( & next_offset) ) . to_affine ( ) ;
138
+
139
+ // If the new key is not infinity, we're done: return the new key
140
+ if !bool:: from ( next_pt. is_identity ( ) ) {
141
+ return ( next_chain_code, next_offset, next_pt) ;
142
+ }
143
+
144
+ // Otherwise set up the next input as defined by SLIP-0010
145
+ ckd_input[ 0 ] = 0x01 ;
146
+ ckd_input[ 1 ..] . copy_from_slice ( & next_chain_code) ;
147
+ }
148
+ }
149
+
150
+ fn derive_offset (
151
+ & self ,
152
+ pt : AffinePoint ,
153
+ chain_code : & [ u8 ; 32 ] ,
154
+ ) -> ( AffinePoint , Scalar , [ u8 ; 32 ] ) {
155
+ let mut offset = Scalar :: ZERO ;
156
+ let mut pt = pt;
157
+ let mut chain_code = * chain_code;
158
+
159
+ for idx in self . path ( ) {
160
+ let ( next_chain_code, next_offset, next_pt) = Self :: ckd_pub ( & idx. 0 , pt, & chain_code) ;
161
+ chain_code = next_chain_code;
162
+ pt = next_pt;
163
+ offset = offset. add ( & next_offset) ;
164
+ }
165
+
166
+ ( pt, offset, chain_code)
167
+ }
168
+ }
169
+
38
170
const PEM_HEADER_PKCS8 : & str = "PRIVATE KEY" ;
39
171
const PEM_HEADER_RFC5915 : & str = "EC PRIVATE KEY" ;
40
172
@@ -305,6 +437,45 @@ impl PrivateKey {
305
437
let key = self . key . verifying_key ( ) ;
306
438
PublicKey { key : * key }
307
439
}
440
+
441
+ /// Derive a private key from this private key using a derivation path
442
+ ///
443
+ /// As long as each index of the derivation path is a 4-byte input with the highest
444
+ /// bit cleared, this derivation scheme matches SLIP-10
445
+ ///
446
+ pub fn derive_subkey ( & self , derivation_path : & DerivationPath ) -> ( Self , [ u8 ; 32 ] ) {
447
+ let chain_code = [ 0u8 ; 32 ] ;
448
+ self . derive_subkey_with_chain_code ( derivation_path, & chain_code)
449
+ }
450
+
451
+ /// Derive a private key from this private key using a derivation path
452
+ /// and chain code
453
+ ///
454
+ /// As long as each index of the derivation path is a 4-byte input with the highest
455
+ /// bit cleared, this derivation scheme matches SLIP-10
456
+ ///
457
+ pub fn derive_subkey_with_chain_code (
458
+ & self ,
459
+ derivation_path : & DerivationPath ,
460
+ chain_code : & [ u8 ; 32 ] ,
461
+ ) -> ( Self , [ u8 ; 32 ] ) {
462
+ use p256:: NonZeroScalar ;
463
+
464
+ let public_key: AffinePoint = * self . key . verifying_key ( ) . as_affine ( ) ;
465
+ let ( _pt, offset, derived_chain_code) =
466
+ derivation_path. derive_offset ( public_key, chain_code) ;
467
+
468
+ let derived_scalar = self . key . as_nonzero_scalar ( ) . as_ref ( ) . add ( & offset) ;
469
+
470
+ let nz_ds =
471
+ NonZeroScalar :: new ( derived_scalar) . expect ( "Derivation always produces non-zero sum" ) ;
472
+
473
+ let derived_key = Self {
474
+ key : p256:: ecdsa:: SigningKey :: from ( nz_ds) ,
475
+ } ;
476
+
477
+ ( derived_key, derived_chain_code)
478
+ }
308
479
}
309
480
310
481
/// An ECDSA public key
@@ -399,4 +570,32 @@ impl PublicKey {
399
570
400
571
self . key . verify_prehash ( digest, & signature) . is_ok ( )
401
572
}
573
+
574
+ /// Derive a public key from this public key using a derivation path
575
+ ///
576
+ pub fn derive_subkey ( & self , derivation_path : & DerivationPath ) -> ( Self , [ u8 ; 32 ] ) {
577
+ let chain_code = [ 0u8 ; 32 ] ;
578
+ self . derive_subkey_with_chain_code ( derivation_path, & chain_code)
579
+ }
580
+
581
+ /// Derive a public key from this public key using a derivation path
582
+ /// and chain code
583
+ ///
584
+ /// This derivation matches SLIP-10
585
+ pub fn derive_subkey_with_chain_code (
586
+ & self ,
587
+ derivation_path : & DerivationPath ,
588
+ chain_code : & [ u8 ; 32 ] ,
589
+ ) -> ( Self , [ u8 ; 32 ] ) {
590
+ let public_key: AffinePoint = * self . key . as_affine ( ) ;
591
+ let ( pt, _offset, chain_code) = derivation_path. derive_offset ( public_key, chain_code) ;
592
+
593
+ let derived_key = Self {
594
+ key : p256:: ecdsa:: VerifyingKey :: from (
595
+ p256:: PublicKey :: from_affine ( pt) . expect ( "Derived point is valid" ) ,
596
+ ) ,
597
+ } ;
598
+
599
+ ( derived_key, chain_code)
600
+ }
402
601
}
0 commit comments