schnorrkel/derive.rs
1// -*- mode: rust; -*-
2//
3// This file is part of schnorrkel.
4// Copyright (c) 2019 Web 3 Foundation
5// See LICENSE for licensing information.
6//
7// Authors:
8// - Jeffrey Burdges <jeff@web3.foundation>
9
10//! ### Implementation of "hierarchical deterministic key derivation" (HDKD) for Schnorr signatures on Ristretto
11//!
12//! *Warning* We warn that our VRF construction in vrf.rs supports
13//! malleable VRF outputs via the `Malleable` type, which becomes
14//! insecure when used in conjunction with our hierarchical key
15//! derivation methods here.
16//! Attackers could translate malleable VRF outputs from one soft subkey
17//! to another soft subkey, gaining early knowledge of the VRF output.
18//! We think most VRF applications for which HDKH sounds suitable
19//! benefit from using implicit certificates instead of HDKD anyways,
20//! which should also be secure in combination with HDKH.
21//! We always use non-malleable VRF inputs in our convenience methods.
22
23//! We suggest using implicit certificates instead of HDKD when
24//! using VRFs.
25//!
26//!
27
28// use curve25519_dalek::digest::generic_array::typenum::U64;
29// use curve25519_dalek::digest::Digest;
30
31use curve25519_dalek::constants;
32use curve25519_dalek::scalar::Scalar;
33
34use super::*;
35use crate::context::{SigningTranscript};
36
37/// Length in bytes of our chain codes.
38///
39/// In fact, only 16 bytes sounds safe, but this never appears on chain,
40/// so no downsides to using 32 bytes.
41pub const CHAIN_CODE_LENGTH: usize = 32;
42
43/// We cannot assume the original public key is secret and additional
44/// inputs might have low entropy, like `i` in BIP32. As in BIP32,
45/// chain codes fill this gap by being a high entropy secret shared
46/// between public and private key holders. These are produced by
47/// key derivations and can be incorporated into subsequence key
48/// derivations.
49/// See https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#extended-keys
50#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
51pub struct ChainCode(pub [u8; CHAIN_CODE_LENGTH]);
52
53/// Key types that support "hierarchical deterministic" key derivation
54pub trait Derivation: Sized {
55 /// Derive key with subkey identified by a byte array
56 /// presented via a `SigningTranscript`, and a chain code.
57 fn derived_key<T>(&self, t: T, cc: ChainCode) -> (Self, ChainCode)
58 where
59 T: SigningTranscript;
60
61 /// Derive key with subkey identified by a byte array
62 /// and a chain code. We do not include a context here
63 /// because the chain code could serve this purpose.
64 fn derived_key_simple<B: AsRef<[u8]>>(&self, cc: ChainCode, i: B) -> (Self, ChainCode) {
65 let mut t = merlin::Transcript::new(b"SchnorrRistrettoHDKD");
66 t.append_message(b"sign-bytes", i.as_ref());
67 self.derived_key(t, cc)
68 }
69
70 /// Derive key with subkey identified by a byte array
71 /// and a chain code, and with external randomnesses.
72 fn derived_key_simple_rng<B, R>(&self, cc: ChainCode, i: B, rng: R) -> (Self, ChainCode)
73 where
74 B: AsRef<[u8]>,
75 R: RngCore + CryptoRng,
76 {
77 let mut t = merlin::Transcript::new(b"SchnorrRistrettoHDKD");
78 t.append_message(b"sign-bytes", i.as_ref());
79 self.derived_key(super::context::attach_rng(t, rng), cc)
80 }
81}
82
83impl PublicKey {
84 /// Derive a mutating scalar and new chain code from a public key and chain code.
85 ///
86 /// If `i` is the "index", `c` is the chain code, and `pk` the public key,
87 /// then we compute `H(i ++ c ++ pk)` and define our mutating scalar
88 /// to be the 512 bits of output reduced mod l, and define the next chain
89 /// code to be next 256 bits.
90 ///
91 /// We update the signing transcript as a side effect.
92 fn derive_scalar_and_chaincode<T>(&self, t: &mut T, cc: ChainCode) -> (Scalar, ChainCode)
93 where
94 T: SigningTranscript,
95 {
96 t.commit_bytes(b"chain-code", &cc.0);
97 t.commit_point(b"public-key", self.as_compressed());
98
99 let scalar = t.challenge_scalar(b"HDKD-scalar");
100
101 let mut chaincode = [0u8; 32];
102 t.challenge_bytes(b"HDKD-chaincode", &mut chaincode);
103
104 (scalar, ChainCode(chaincode))
105 }
106}
107
108impl SecretKey {
109 /// Vaguely BIP32-like "hard" derivation of a `MiniSecretKey` from a `SecretKey`
110 ///
111 /// We do not envision any "good reasons" why these "hard"
112 /// derivations should ever be used after the soft `Derivation`
113 /// trait. We similarly do not believe hard derivations
114 /// make any sense for `ChainCode`s or `ExtendedKey`s types.
115 /// Yet, some existing BIP32 workflows might do these things,
116 /// due to BIP32's de facto standardization and poor design.
117 /// In consequence, we provide this method to do "hard" derivations
118 /// in a way that should work with all BIP32 workflows and any
119 /// permissible mutations of `SecretKey`. This means only that
120 /// we hash the `SecretKey`'s scalar, but not its nonce because
121 /// the secret key remains valid if the nonce is changed.
122 pub fn hard_derive_mini_secret_key<B: AsRef<[u8]>>(
123 &self,
124 cc: Option<ChainCode>,
125 i: B,
126 ) -> (MiniSecretKey, ChainCode) {
127 let mut t = merlin::Transcript::new(b"SchnorrRistrettoHDKD");
128 t.append_message(b"sign-bytes", i.as_ref());
129
130 if let Some(c) = cc {
131 t.append_message(b"chain-code", &c.0);
132 }
133 t.append_message(b"secret-key", &self.key.to_bytes() as &[u8]);
134
135 let mut msk = [0u8; MINI_SECRET_KEY_LENGTH];
136 t.challenge_bytes(b"HDKD-hard", &mut msk);
137
138 let mut chaincode = [0u8; 32];
139 t.challenge_bytes(b"HDKD-chaincode", &mut chaincode);
140
141 (MiniSecretKey(msk), ChainCode(chaincode))
142 }
143}
144
145impl MiniSecretKey {
146 /// Vaguely BIP32-like "hard" derivation of a `MiniSecretKey` from a `SecretKey`
147 ///
148 /// We do not envision any "good reasons" why these "hard"
149 /// derivations should ever be used after the soft `Derivation`
150 /// trait. We similarly do not believe hard derivations
151 /// make any sense for `ChainCode`s or `ExtendedKey`s types.
152 /// Yet, some existing BIP32 workflows might do these things,
153 /// due to BIP32's de facto standardization and poor design.
154 /// In consequence, we provide this method to do "hard" derivations
155 /// in a way that should work with all BIP32 workflows and any
156 /// permissible mutations of `SecretKey`. This means only that
157 /// we hash the `SecretKey`'s scalar, but not its nonce because
158 /// the secret key remains valid if the nonce is changed.
159 pub fn hard_derive_mini_secret_key<B: AsRef<[u8]>>(
160 &self,
161 cc: Option<ChainCode>,
162 i: B,
163 mode: ExpansionMode,
164 ) -> (MiniSecretKey, ChainCode) {
165 self.expand(mode).hard_derive_mini_secret_key(cc, i)
166 }
167}
168
169impl Keypair {
170 /// Vaguely BIP32-like "hard" derivation of a `MiniSecretKey` from a `SecretKey`
171 ///
172 /// We do not envision any "good reasons" why these "hard"
173 /// derivations should ever be used after the soft `Derivation`
174 /// trait. We similarly do not believe hard derivations
175 /// make any sense for `ChainCode`s or `ExtendedKey`s types.
176 /// Yet, some existing BIP32 workflows might do these things,
177 /// due to BIP32's de facto standardization and poor design.
178 /// In consequence, we provide this method to do "hard" derivations
179 /// in a way that should work with all BIP32 workflows and any
180 /// permissible mutations of `SecretKey`. This means only that
181 /// we hash the `SecretKey`'s scalar, but not its nonce because
182 /// the secret key remains valid if the nonce is changed.
183 pub fn hard_derive_mini_secret_key<B: AsRef<[u8]>>(
184 &self,
185 cc: Option<ChainCode>,
186 i: B,
187 ) -> (MiniSecretKey, ChainCode) {
188 self.secret.hard_derive_mini_secret_key(cc, i)
189 }
190
191 /// Derive a secret key and new chain code from a key pair and chain code.
192 ///
193 /// We expect the trait methods of `Keypair as Derivation` to be
194 /// more useful since signing anything requires the public key too.
195 pub fn derive_secret_key<T>(&self, mut t: T, cc: ChainCode) -> (SecretKey, ChainCode)
196 where
197 T: SigningTranscript,
198 {
199 let (scalar, chaincode) = self.public.derive_scalar_and_chaincode(&mut t, cc);
200
201 // We can define the nonce however we like here since it only protects
202 // the signature from bad random number generators. It need not be
203 // specified by any specification or standard. It must however be
204 // independent from the mutating scalar and new chain code.
205 // We employ the witness mechanism here so that CSPRNG associated to our
206 // `SigningTranscript` makes our new nonce seed independent from everything.
207 let mut nonce = [0u8; 32];
208 t.witness_bytes(
209 b"HDKD-nonce",
210 &mut nonce,
211 &[&self.secret.nonce, &self.secret.to_bytes() as &[u8]],
212 );
213
214 (SecretKey { key: self.secret.key + scalar, nonce }, chaincode)
215 }
216}
217
218impl Derivation for Keypair {
219 fn derived_key<T>(&self, t: T, cc: ChainCode) -> (Keypair, ChainCode)
220 where
221 T: SigningTranscript,
222 {
223 let (secret, chaincode) = self.derive_secret_key(t, cc);
224 let public = secret.to_public();
225 (Keypair { secret, public }, chaincode)
226 }
227}
228
229impl Derivation for SecretKey {
230 fn derived_key<T>(&self, t: T, cc: ChainCode) -> (SecretKey, ChainCode)
231 where
232 T: SigningTranscript,
233 {
234 self.clone().to_keypair().derive_secret_key(t, cc)
235 }
236}
237
238impl Derivation for PublicKey {
239 fn derived_key<T>(&self, mut t: T, cc: ChainCode) -> (PublicKey, ChainCode)
240 where
241 T: SigningTranscript,
242 {
243 let (scalar, chaincode) = self.derive_scalar_and_chaincode(&mut t, cc);
244 let point = self.as_point() + (&scalar * constants::RISTRETTO_BASEPOINT_TABLE);
245 (PublicKey::from_point(point), chaincode)
246 }
247}
248
249/// A convenience wraper that combines derivable key and a chain code.
250#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
251pub struct ExtendedKey<K> {
252 /// Appropriate key type
253 pub key: K,
254 /// We cannot assume the original public key is secret and additional
255 /// inputs might have low entropy, like `i` in BIP32. As in BIP32,
256 /// chain codes fill this gap by being a high entropy secret shared
257 /// between public and private key holders. These are produced by
258 /// key derivations and can be incorporated into subsequence key
259 /// derivations.
260 pub chaincode: ChainCode,
261}
262// TODO: Serialization
263
264impl<K: Derivation> ExtendedKey<K> {
265 /// Derive key with subkey identified by a byte array
266 /// presented as a hash, and a chain code.
267 pub fn derived_key<T>(&self, t: T) -> ExtendedKey<K>
268 where
269 T: SigningTranscript,
270 {
271 let (key, chaincode) = self.key.derived_key(t, self.chaincode);
272 ExtendedKey { key, chaincode }
273 }
274
275 /// Derive key with subkey identified by a byte array and
276 /// a chain code in the extended key.
277 pub fn derived_key_simple<B: AsRef<[u8]>>(&self, i: B) -> ExtendedKey<K> {
278 let (key, chaincode) = self.key.derived_key_simple(self.chaincode, i);
279 ExtendedKey { key, chaincode }
280 }
281}
282
283impl ExtendedKey<SecretKey> {
284 /// Vaguely BIP32-like "hard" derivation of a `MiniSecretKey` from a `SecretKey`
285 ///
286 /// We do not envision any "good reasons" why these "hard"
287 /// derivations should ever be used after the soft `Derivation`
288 /// trait. We similarly do not believe hard derivations
289 /// make any sense for `ChainCode`s or `ExtendedKey`s types.
290 /// Yet, some existing BIP32 workflows might do these things,
291 /// due to BIP32's de facto standardization and poor design.
292 /// In consequence, we provide this method to do "hard" derivations
293 /// in a way that should work with all BIP32 workflows and any
294 /// permissible mutations of `SecretKey`. This means only that
295 /// we hash the `SecretKey`'s scalar, but not its nonce because
296 /// the secret key remains valid if the nonce is changed.
297 pub fn hard_derive_mini_secret_key<B: AsRef<[u8]>>(
298 &self,
299 i: B,
300 mode: ExpansionMode,
301 ) -> ExtendedKey<SecretKey> {
302 let (key, chaincode) = self.key.hard_derive_mini_secret_key(Some(self.chaincode), i);
303 let key = key.expand(mode);
304 ExtendedKey { key, chaincode }
305 }
306}
307
308#[cfg(test)]
309mod tests {
310 use sha3::digest::{Update}; // ExtendableOutput,XofReader
311 use sha3::{Shake128};
312
313 use super::*;
314
315 #[cfg(feature = "getrandom")]
316 #[test]
317 fn derive_key_public_vs_private_paths() {
318 let chaincode = ChainCode([0u8; CHAIN_CODE_LENGTH]);
319 let msg: &'static [u8] = b"Just some test message!";
320 let mut h = Shake128::default().chain(msg);
321
322 let mut csprng = rand_core::OsRng;
323 let key = Keypair::generate_with(&mut csprng);
324
325 let mut extended_public_key = ExtendedKey { key: key.public.clone(), chaincode };
326 let mut extended_keypair = ExtendedKey { key, chaincode };
327
328 let ctx = signing_context(b"testing testing 1 2 3");
329
330 for i in 0..30 {
331 let extended_keypair1 = extended_keypair.derived_key_simple(msg);
332 let extended_public_key1 = extended_public_key.derived_key_simple(msg);
333 assert_eq!(
334 extended_keypair1.chaincode, extended_public_key1.chaincode,
335 "Chain code derivation failed!"
336 );
337 assert_eq!(
338 extended_keypair1.key.public, extended_public_key1.key,
339 "Public and secret key derivation missmatch!"
340 );
341 extended_keypair = extended_keypair1;
342 extended_public_key = extended_public_key1;
343
344 h.update(b"Another");
345
346 if i % 5 == 0 {
347 let good_sig = extended_keypair.key.sign(ctx.xof(h.clone()));
348 let h_bad = h.clone().chain(b"oops");
349 let bad_sig = extended_keypair.key.sign(ctx.xof(h_bad.clone()));
350
351 assert!(
352 extended_public_key.key.verify(ctx.xof(h.clone()), &good_sig).is_ok(),
353 "Verification of a valid signature failed!"
354 );
355 assert!(
356 !extended_public_key.key.verify(ctx.xof(h.clone()), &bad_sig).is_ok(),
357 "Verification of a signature on a different message passed!"
358 );
359 assert!(
360 !extended_public_key.key.verify(ctx.xof(h_bad), &good_sig).is_ok(),
361 "Verification of a signature on a different message passed!"
362 );
363 }
364 }
365 }
366}