crypto/keys/
slip10.rs

1// Copyright 2024 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4#![allow(clippy::from_over_into)]
5
6use alloc::vec::Vec;
7use core::convert::TryFrom;
8use core::fmt;
9
10use zeroize::{Zeroize, ZeroizeOnDrop};
11
12use crate::macs::hmac::HMAC_SHA512;
13
14// https://github.com/satoshilabs/slips/blob/master/slip-0010.md
15// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
16// https://en.bitcoin.it/wiki/BIP_0039
17// https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
18// https://en.bitcoin.it/wiki/BIP_0044
19
20/// The traits in hazmat module are implementation internals.
21/// The traits are made public due to other public API requiring them.
22/// The traits are not exported to prevent third parties from implementing them outside this crate. This prevents third
23/// parties from importing and using them in polymorphic contexts.
24mod hazmat {
25    use super::Segment;
26
27    /// Prevent external crates from deriving hazmat traits.
28    pub trait Sealed {}
29    /// Derivable secret and public keys.
30    pub trait Derivable: Sealed {
31        fn is_key_valid(key_bytes: &[u8; 33]) -> bool;
32        fn to_key(key_bytes: &[u8; 33]) -> Self;
33        fn add_key(key_bytes: &mut [u8; 33], parent_key: &[u8; 33]) -> bool;
34    }
35    /// Derivable secret key.
36    pub trait IsSecretKey: Derivable {
37        const SEEDKEY: &'static [u8];
38        /// Type of corresponding public key; PublicKey type may not be Derivable as is the case with ed25519.
39        type PublicKey;
40    }
41    /// Derivable public key.
42    pub trait IsPublicKey: Derivable {
43        /// Corresponding derivable secret key type
44        type SecretKey: IsSecretKey;
45    }
46    /// Derivable secret key whose corresponding public key is also derivable.
47    // The trait should have been defined as `trait ToPublic: IsSecretKey where Self::PublicKey: IsPublicKey<SecretKey =
48    // Self>`. It makes generic arguments more complex and seems like overkill.
49    pub trait ToPublic: IsSecretKey {
50        fn to_public(sk_bytes: &[u8; 33]) -> [u8; 33];
51    }
52    /// Keys that can be used to compute "data" argument of SLIP10 derivation algorithm for a specific segment type.
53    pub trait WithSegment<S: Segment>: Sealed {
54        fn calc_data(key_bytes: &[u8; 33], segment: S) -> [u8; 33];
55    }
56    /// Keys that convert a prechain (BIP44) to a compatible chain.
57    pub trait ToChain<C> {
58        type Chain;
59        fn to_chain(pre_chain: &C) -> Self::Chain;
60    }
61}
62
63pub use hazmat::{Derivable, IsPublicKey, IsSecretKey, ToChain, ToPublic, WithSegment};
64
65#[cfg(feature = "ed25519")]
66pub mod ed25519 {
67    use super::{hazmat::*, Hardened};
68    use crate::signatures::ed25519;
69
70    impl Sealed for ed25519::SecretKey {}
71
72    impl Derivable for ed25519::SecretKey {
73        fn is_key_valid(key_bytes: &[u8; 33]) -> bool {
74            key_bytes[0] == 0
75        }
76        fn to_key(key_bytes: &[u8; 33]) -> Self {
77            debug_assert_eq!(0, key_bytes[0]);
78            let sk_bytes: &[u8; 32] = unsafe { &*(key_bytes[1..].as_ptr() as *const [u8; 32]) };
79            ed25519::SecretKey::from_bytes(sk_bytes)
80        }
81        fn add_key(_key_bytes: &mut [u8; 33], _parent_key: &[u8; 33]) -> bool {
82            true
83        }
84    }
85
86    impl IsSecretKey for ed25519::SecretKey {
87        const SEEDKEY: &'static [u8] = b"ed25519 seed";
88        type PublicKey = ed25519::PublicKey;
89    }
90
91    impl WithSegment<Hardened> for ed25519::SecretKey {
92        fn calc_data(key_bytes: &[u8; 33], _segment: Hardened) -> [u8; 33] {
93            *key_bytes
94        }
95    }
96}
97
98#[cfg(feature = "secp256k1")]
99pub mod secp256k1 {
100    use super::{hazmat::*, Hardened, NonHardened, Segment};
101    use crate::signatures::secp256k1_ecdsa;
102
103    impl Sealed for secp256k1_ecdsa::SecretKey {}
104
105    impl Derivable for secp256k1_ecdsa::SecretKey {
106        fn is_key_valid(key_bytes: &[u8; 33]) -> bool {
107            debug_assert_eq!(0, key_bytes[0]);
108            let sk_bytes: &[u8; 32] = unsafe { &*(key_bytes[1..].as_ptr() as *const [u8; 32]) };
109            k256::SecretKey::from_bytes(sk_bytes.into()).is_ok()
110        }
111        fn to_key(key_bytes: &[u8; 33]) -> Self {
112            debug_assert_eq!(0, key_bytes[0]);
113            let sk_bytes: &[u8; 32] = unsafe { &*(key_bytes[1..].as_ptr() as *const [u8; 32]) };
114            secp256k1_ecdsa::SecretKey::try_from_bytes(sk_bytes).expect("valid extended secret key")
115        }
116        fn add_key(key_bytes: &mut [u8; 33], parent_key: &[u8; 33]) -> bool {
117            debug_assert_eq!(0, parent_key[0]);
118            debug_assert_eq!(0, key_bytes[0]);
119            let sk_bytes: &[u8; 32] = unsafe { &*(key_bytes[1..].as_ptr() as *const [u8; 32]) };
120
121            if let Ok(sk_delta) = k256::SecretKey::from_bytes(sk_bytes.into()) {
122                let sk =
123                    k256::SecretKey::from_bytes((&parent_key[1..]).into()).expect("valid Secp256k1 parent secret key");
124
125                let scalar_delta = sk_delta.to_nonzero_scalar();
126                let mut scalar = *sk.to_nonzero_scalar().as_ref();
127                scalar += scalar_delta.as_ref();
128
129                if scalar.is_zero().into() {
130                    false
131                } else {
132                    key_bytes[1..].copy_from_slice(&scalar.to_bytes());
133                    true
134                }
135            } else {
136                false
137            }
138        }
139    }
140
141    impl IsSecretKey for secp256k1_ecdsa::SecretKey {
142        const SEEDKEY: &'static [u8] = b"Bitcoin seed";
143        type PublicKey = secp256k1_ecdsa::PublicKey;
144    }
145
146    impl ToPublic for secp256k1_ecdsa::SecretKey {
147        fn to_public(key_bytes: &[u8; 33]) -> [u8; 33] {
148            use k256::elliptic_curve::sec1::ToEncodedPoint;
149            debug_assert_eq!(0, key_bytes[0]);
150            let sk_bytes: &[u8; 32] = unsafe { &*(key_bytes[1..].as_ptr() as *const [u8; 32]) };
151            let sk = k256::SecretKey::from_bytes(sk_bytes.into()).expect("valid Secp256k1 parent secret key");
152            let pk = sk.public_key();
153            let mut pk_bytes = [0_u8; 33];
154            pk_bytes.copy_from_slice(pk.to_encoded_point(true).as_bytes());
155            pk_bytes
156        }
157    }
158
159    impl WithSegment<Hardened> for secp256k1_ecdsa::SecretKey {
160        fn calc_data(key_bytes: &[u8; 33], _segment: Hardened) -> [u8; 33] {
161            *key_bytes
162        }
163    }
164
165    impl WithSegment<NonHardened> for secp256k1_ecdsa::SecretKey {
166        fn calc_data(key_bytes: &[u8; 33], _segment: NonHardened) -> [u8; 33] {
167            Self::to_public(key_bytes)
168        }
169    }
170
171    impl WithSegment<u32> for secp256k1_ecdsa::SecretKey {
172        fn calc_data(key_bytes: &[u8; 33], segment: u32) -> [u8; 33] {
173            if segment.is_hardened() {
174                Self::calc_data(key_bytes, Hardened(segment))
175            } else {
176                Self::calc_data(key_bytes, NonHardened(segment))
177            }
178        }
179    }
180
181    impl Sealed for secp256k1_ecdsa::PublicKey {}
182
183    impl Derivable for secp256k1_ecdsa::PublicKey {
184        fn is_key_valid(key_bytes: &[u8; 33]) -> bool {
185            (key_bytes[0] == 2 || key_bytes[0] == 3) && k256::PublicKey::from_sec1_bytes(key_bytes).is_ok()
186        }
187        fn to_key(key_bytes: &[u8; 33]) -> Self {
188            secp256k1_ecdsa::PublicKey::try_from_bytes(key_bytes)
189                // implementation guarantees that it always succeeds
190                .expect("valid extended public key")
191        }
192        fn add_key(key_bytes: &mut [u8; 33], parent_key: &[u8; 33]) -> bool {
193            use k256::{
194                elliptic_curve::{group::prime::PrimeCurveAffine, sec1::ToEncodedPoint},
195                AffinePoint, ProjectivePoint,
196            };
197            debug_assert_eq!(0, key_bytes[0]);
198            let sk_bytes: &[u8; 32] = unsafe { &*(key_bytes[1..].as_ptr() as *const [u8; 32]) };
199
200            if let Ok(sk_delta) = k256::SecretKey::from_bytes(sk_bytes.into()) {
201                let pk_delta = sk_delta.public_key();
202                let pk_parent =
203                    k256::PublicKey::from_sec1_bytes(parent_key).expect("valid Secp256k1 parent public key");
204
205                let mut point: ProjectivePoint = pk_parent.as_affine().into();
206                point += pk_delta.as_affine();
207                let point_sum: AffinePoint = point.into();
208
209                if point_sum.is_identity().into() {
210                    false
211                } else {
212                    key_bytes.copy_from_slice(point_sum.to_encoded_point(true).as_bytes());
213                    true
214                }
215            } else {
216                false
217            }
218        }
219    }
220
221    impl IsPublicKey for secp256k1_ecdsa::PublicKey {
222        type SecretKey = secp256k1_ecdsa::SecretKey;
223    }
224
225    impl WithSegment<NonHardened> for secp256k1_ecdsa::PublicKey {
226        fn calc_data(key_bytes: &[u8; 33], _segment: NonHardened) -> [u8; 33] {
227            *key_bytes
228        }
229    }
230}
231
232/// A seed is an arbitrary bytestring used to create the root of the tree.
233///
234/// Several standards generate and/or restricts the size of the seed:
235/// BIP39: 512 bit seeds
236/// BIP32: between 128 and 512 bits; 256 bits is advised
237/// SLIP10: follows BIP32
238///
239/// But since the seed entropy is always passed through HMAC-SHA512 any bytesequence is acceptable,
240/// therefore formally the size requirement is context sensitive.
241#[derive(Zeroize, ZeroizeOnDrop)]
242pub struct Seed(Vec<u8>);
243
244impl Seed {
245    pub fn from_bytes(bs: &[u8]) -> Self {
246        Self(bs.to_vec())
247    }
248
249    pub fn to_master_key<K: hazmat::IsSecretKey>(&self) -> Slip10<K> {
250        Slip10::from_seed(self)
251    }
252
253    pub fn derive<K, I>(&self, chain: I) -> Slip10<K>
254    where
255        K: hazmat::IsSecretKey + hazmat::WithSegment<<I as Iterator>::Item>,
256        I: Iterator,
257        <I as Iterator>::Item: Segment,
258    {
259        self.to_master_key().derive(chain)
260    }
261}
262
263impl fmt::Debug for Seed {
264    #[inline]
265    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
266        "<slip10::Seed>".fmt(f)
267    }
268}
269
270impl AsRef<[u8]> for Seed {
271    fn as_ref(&self) -> &[u8] {
272        &self.0
273    }
274}
275
276#[cfg(feature = "bip39")]
277impl From<super::bip39::Seed> for Seed {
278    fn from(seed: super::bip39::Seed) -> Self {
279        Self::from_bytes(seed.as_ref())
280    }
281}
282
283/// Public bytestring that uniquely distinguishes different extended keys for the same key.
284pub type ChainCode = [u8; 32];
285
286/// Extended secret or public key, ie. a key extended with a chain code.
287///
288/// Extended keys must be handled with care. Security implications are explained in BIP32.
289#[derive(ZeroizeOnDrop)]
290pub struct Slip10<K> {
291    key: core::marker::PhantomData<K>,
292    ext: [u8; 65],
293}
294
295impl<K> Clone for Slip10<K> {
296    fn clone(&self) -> Self {
297        Self {
298            key: core::marker::PhantomData,
299            ext: self.ext,
300        }
301    }
302}
303
304impl<K> fmt::Debug for Slip10<K> {
305    #[inline]
306    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
307        write!(f, "<slip10::Slip10<{}>>", core::any::type_name::<K>())
308    }
309}
310
311impl<K> Zeroize for Slip10<K> {
312    fn zeroize(&mut self) {
313        self.ext.zeroize()
314    }
315}
316
317impl<K: hazmat::IsSecretKey> Slip10<K> {
318    pub fn from_seed<S: AsRef<[u8]>>(seed: &S) -> Self {
319        let mut key = Self::new();
320        HMAC_SHA512(seed.as_ref(), K::SEEDKEY, key.ext_mut());
321        while !key.is_key_valid() {
322            let mut tmp = [0_u8; 64];
323            tmp.copy_from_slice(&key.ext[1..]);
324            HMAC_SHA512(&tmp, K::SEEDKEY, key.ext_mut());
325            tmp.zeroize();
326        }
327        key
328    }
329
330    pub fn secret_key(&self) -> K {
331        self.key()
332    }
333
334    pub fn to_extended_public_key(&self) -> Slip10<K::PublicKey>
335    where
336        K::PublicKey: hazmat::IsPublicKey<SecretKey = K>,
337        K: hazmat::ToPublic,
338    {
339        Slip10::from_extended_secret_key(self)
340    }
341}
342
343impl<K: hazmat::IsSecretKey> From<&Seed> for Slip10<K> {
344    fn from(seed: &Seed) -> Self {
345        Self::from_seed(seed)
346    }
347}
348
349impl<K> Slip10<K>
350where
351    K: hazmat::IsPublicKey,
352    K::SecretKey: hazmat::ToPublic,
353{
354    pub fn from_extended_secret_key(esk: &Slip10<K::SecretKey>) -> Self {
355        let mut k = Self::new();
356        k.ext[..33].copy_from_slice(&<K::SecretKey as hazmat::ToPublic>::to_public(esk.key_bytes()));
357        k.ext[33..].copy_from_slice(esk.chain_code());
358        k
359    }
360}
361
362impl<K> From<&Slip10<K::SecretKey>> for Slip10<K>
363where
364    K: hazmat::IsPublicKey,
365    K::SecretKey: hazmat::ToPublic,
366{
367    fn from(esk: &Slip10<K::SecretKey>) -> Self {
368        Self::from_extended_secret_key(esk)
369    }
370}
371
372impl<K: hazmat::IsPublicKey> Slip10<K> {
373    pub fn public_key(&self) -> K {
374        self.key()
375    }
376}
377
378impl<K> Slip10<K> {
379    pub(crate) fn new() -> Self {
380        Self {
381            key: core::marker::PhantomData,
382            ext: [0_u8; 65],
383        }
384    }
385}
386
387impl<K> Slip10<K> {
388    pub fn extended_bytes(&self) -> &[u8; 65] {
389        &self.ext
390    }
391
392    pub fn chain_code(&self) -> &[u8; 32] {
393        unsafe { &*(self.ext[33..].as_ptr() as *const [u8; 32]) }
394    }
395}
396
397impl<K: hazmat::Derivable> Slip10<K> {
398    fn key(&self) -> K {
399        K::to_key(self.key_bytes())
400    }
401
402    pub fn try_from_extended_bytes(ext_bytes: &[u8; 65]) -> crate::Result<Self> {
403        let key_bytes: &[u8; 33] = unsafe { &*(ext_bytes[..33].as_ptr() as *const [u8; 33]) };
404        if K::is_key_valid(key_bytes) {
405            Ok(Self {
406                key: core::marker::PhantomData,
407                ext: *ext_bytes,
408            })
409        } else {
410            Err(crate::Error::InvalidArgumentError {
411                alg: "SLIP10",
412                expected: "valid extended key bytes",
413            })
414        }
415    }
416
417    pub fn derive<I>(&self, chain: I) -> Self
418    where
419        K: hazmat::WithSegment<<I as Iterator>::Item>,
420        I: Iterator,
421        <I as Iterator>::Item: Segment,
422    {
423        chain.fold(self.clone(), |key, segment| key.child_key(segment))
424    }
425
426    fn ext_mut(&mut self) -> &mut [u8; 64] {
427        unsafe { &mut *(self.ext[1..].as_mut_ptr() as *mut [u8; 64]) }
428    }
429
430    fn key_bytes(&self) -> &[u8; 33] {
431        unsafe { &*(self.ext[..33].as_ptr() as *const [u8; 33]) }
432    }
433
434    fn key_bytes_mut(&mut self) -> &mut [u8; 33] {
435        unsafe { &mut *(self.ext[..33].as_mut_ptr() as *mut [u8; 33]) }
436    }
437
438    fn add_key(&mut self, parent_key: &[u8; 33]) -> bool {
439        K::add_key(self.key_bytes_mut(), parent_key)
440    }
441
442    fn is_key_valid(&self) -> bool {
443        K::is_key_valid(self.key_bytes())
444    }
445
446    fn calc_data<S>(&self, segment: S) -> [u8; 33]
447    where
448        S: Segment,
449        K: hazmat::WithSegment<S>,
450    {
451        K::calc_data(self.key_bytes(), segment)
452    }
453
454    pub fn child_key<S>(&self, segment: S) -> Self
455    where
456        S: Segment,
457        K: hazmat::WithSegment<S>,
458    {
459        let mut data = [0u8; 33 + 4];
460        data[..33].copy_from_slice(&self.calc_data(segment));
461        data[33..].copy_from_slice(&segment.ser32());
462
463        let mut key = Self::new();
464        HMAC_SHA512(&data, self.chain_code(), key.ext_mut());
465        while !key.add_key(self.key_bytes()) {
466            data[0] = 1;
467            data[1..1 + 32].copy_from_slice(key.key_bytes());
468            HMAC_SHA512(&data, self.chain_code(), key.ext_mut());
469        }
470
471        data.zeroize();
472        key
473    }
474
475    pub fn children<I>(&self, child_segments: I) -> Children<K, I>
476    where
477        K: hazmat::WithSegment<<I as IntoIterator>::Item>,
478        I: Iterator,
479        <I as Iterator>::Item: Segment,
480    {
481        Children {
482            mk: self.clone(),
483            child_segments,
484        }
485    }
486}
487
488pub struct Children<K, I> {
489    mk: Slip10<K>,
490    child_segments: I,
491}
492
493impl<K, I> Iterator for Children<K, I>
494where
495    K: hazmat::Derivable + hazmat::WithSegment<<I as IntoIterator>::Item>,
496    I: Iterator,
497    <I as Iterator>::Item: Segment,
498{
499    type Item = Slip10<K>;
500    fn next(&mut self) -> Option<Slip10<K>> {
501        self.child_segments.next().map(|segment| self.mk.child_key(segment))
502    }
503}
504
505impl<K, I> core::iter::FusedIterator for Children<K, I>
506where
507    K: hazmat::Derivable + hazmat::WithSegment<<I as IntoIterator>::Item>,
508    I: core::iter::FusedIterator,
509    <I as Iterator>::Item: Segment,
510{
511}
512
513impl<K, I> core::iter::ExactSizeIterator for Children<K, I>
514where
515    K: hazmat::Derivable + hazmat::WithSegment<<I as IntoIterator>::Item>,
516    I: core::iter::ExactSizeIterator,
517    <I as Iterator>::Item: Segment,
518{
519    fn len(&self) -> usize {
520        self.child_segments.len()
521    }
522}
523
524impl<K: hazmat::Derivable> TryFrom<&[u8; 65]> for Slip10<K> {
525    type Error = crate::Error;
526    fn try_from(ext_bytes: &[u8; 65]) -> crate::Result<Self> {
527        Self::try_from_extended_bytes(ext_bytes)
528    }
529}
530
531/// Segment of a derivation chain.
532pub trait Segment: Copy + Into<u32> {
533    fn is_hardened(self) -> bool;
534    fn ser32(self) -> [u8; 4] {
535        self.into().to_be_bytes()
536    }
537    fn harden(self) -> Hardened;
538    fn unharden(self) -> NonHardened;
539}
540
541/// Error indicating unexpected/invalid segment hardening.
542/// Some keys only accept certain segments: either hardened or non-hardened.
543#[derive(Clone, Copy, Debug, Eq, PartialEq)]
544#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
545pub enum SegmentHardeningError {
546    /// Input segment is hardened, expected non-hardened segment only.
547    Hardened,
548    /// Input segment is non-hardened, expected hardened segment only.
549    NonHardened,
550}
551
552impl From<SegmentHardeningError> for crate::Error {
553    fn from(inner: SegmentHardeningError) -> Self {
554        crate::Error::Slip10Error(inner)
555    }
556}
557
558pub const HARDEN_MASK: u32 = 1 << 31;
559
560/// `u32` type can represent both hardened and non-hardened segments.
561impl Segment for u32 {
562    fn is_hardened(self) -> bool {
563        self & HARDEN_MASK != 0
564    }
565    fn harden(self) -> Hardened {
566        Hardened(self | HARDEN_MASK)
567    }
568    fn unharden(self) -> NonHardened {
569        NonHardened(self & !HARDEN_MASK)
570    }
571}
572
573/// Type of hardened segments.
574#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
575#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
576#[repr(transparent)]
577pub struct Hardened(u32);
578
579impl From<Hardened> for u32 {
580    fn from(segment: Hardened) -> u32 {
581        segment.0
582    }
583}
584
585impl TryFrom<u32> for Hardened {
586    type Error = SegmentHardeningError;
587    fn try_from(segment: u32) -> Result<Self, SegmentHardeningError> {
588        if segment.is_hardened() {
589            Ok(Hardened(segment))
590        } else {
591            Err(SegmentHardeningError::NonHardened)
592        }
593    }
594}
595
596impl Segment for Hardened {
597    fn is_hardened(self) -> bool {
598        true
599    }
600    fn harden(self) -> Hardened {
601        self
602    }
603    fn unharden(self) -> NonHardened {
604        NonHardened(self.0 ^ HARDEN_MASK)
605    }
606}
607
608/// Type of non-hardened segments.
609#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
610#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
611#[repr(transparent)]
612pub struct NonHardened(u32);
613
614impl From<NonHardened> for u32 {
615    fn from(segment: NonHardened) -> u32 {
616        segment.0
617    }
618}
619
620impl TryFrom<u32> for NonHardened {
621    type Error = SegmentHardeningError;
622    fn try_from(segment: u32) -> Result<Self, SegmentHardeningError> {
623        if !segment.is_hardened() {
624            Ok(NonHardened(segment))
625        } else {
626            Err(SegmentHardeningError::Hardened)
627        }
628    }
629}
630
631impl Segment for NonHardened {
632    fn is_hardened(self) -> bool {
633        false
634    }
635    fn harden(self) -> Hardened {
636        Hardened(self.0 ^ HARDEN_MASK)
637    }
638    fn unharden(self) -> NonHardened {
639        self
640    }
641}