fedimint_derive_secret/
lib.rs

1//! Scheme for deriving deterministic secret keys
2//!
3//! `DerivableSecret` represents a secret key that can be used to derive child
4//! secret keys. A root key secret can be used to derives child
5//! keys from it, which can have child keys derived from them, recursively.
6//!
7//! The `DerivableSecret` struct in this implementation is only used for
8//! deriving secret keys, not public keys. This allows supporting multiple
9//! crypto schemes for the different cryptographic operations used across the
10//! different modules:
11//!
12//! * secp256k1 for bitcoin deposit addresses, redeem keys and contract keys for
13//!   lightning,
14//! * bls12-381 for the guardians' threshold signature scheme,
15//! * chacha20-poly1305 for symmetric encryption used for backups.
16use std::fmt::Formatter;
17
18use bls12_381::Scalar;
19use fedimint_core::config::FederationId;
20use fedimint_core::encoding::{Decodable, Encodable};
21use fedimint_core::secp256k1::{Keypair, Secp256k1, Signing};
22use hkdf::hashes::Sha512;
23use hkdf::{BitcoinHash, Hkdf};
24use ring::aead;
25
26const CHILD_TAG: &[u8; 8] = b"childkey";
27const SECP256K1_TAG: &[u8; 8] = b"secp256k";
28const BLS12_381_TAG: &[u8; 8] = b"bls12381";
29const CHACHA20_POLY1305: &[u8; 8] = b"c20p1305";
30const RAW_BYTES: &[u8; 8] = b"rawbytes";
31
32/// Describes a child key of a [`DerivableSecret`]
33#[derive(Debug, Copy, Clone, Encodable, Decodable)]
34pub struct ChildId(pub u64);
35
36/// A secret that can have child-subkey derived from it.
37#[derive(Clone)]
38pub struct DerivableSecret {
39    /// Derivation level, root = 0, every `child_key` increments it
40    level: usize,
41    /// An instance of the HKDF (Hash-based Key Derivation
42    ///   Function) with SHA-512 as the underlying hash function. It is used to
43    ///   derive child keys.
44    // TODO: wrap in some secret protecting wrappers maybe?
45    kdf: Hkdf<Sha512>,
46}
47
48impl DerivableSecret {
49    /// Derive root secret key from a secret material and salt.
50    ///
51    /// The `salt` is just additional data t used
52    /// as an additional input to the HKDF.
53    pub fn new_root(root_key: &[u8], salt: &[u8]) -> Self {
54        DerivableSecret {
55            level: 0,
56            kdf: Hkdf::new(root_key, Some(salt)),
57        }
58    }
59
60    /// Get derivation level
61    ///
62    /// This is useful for ensuring a correct derivation level is used,
63    /// in various places.
64    ///
65    /// Root keys start at `0`, and every derived key increments it.
66    pub fn level(&self) -> usize {
67        self.level
68    }
69
70    pub fn child_key(&self, cid: ChildId) -> DerivableSecret {
71        DerivableSecret {
72            level: self.level + 1,
73            kdf: Hkdf::from_prk(self.kdf.derive_hmac(&tagged_derive(CHILD_TAG, cid))),
74        }
75    }
76
77    /// Derive a federation-ID-based child key from self.
78    ///
79    /// This is useful to ensure that the same root secret is not reused
80    /// across multiple `fedimint-client` instances for different federations.
81    ///
82    /// We reset the level to 0 here since `fedimint-client` expects its root
83    /// secret to be at that level.
84    pub fn federation_key(&self, federation_id: &FederationId) -> DerivableSecret {
85        DerivableSecret {
86            level: 0,
87            kdf: Hkdf::from_prk(
88                self.kdf.derive_hmac(&tagged_derive(
89                    &federation_id.0.to_byte_array()[..8]
90                        .try_into()
91                        .expect("Slice with length 8"),
92                    ChildId(0),
93                )),
94            ),
95        }
96    }
97
98    /// secp256k1 keys are used for bitcoin deposit addresses, redeem keys and
99    /// contract keys for lightning.
100    pub fn to_secp_key<C: Signing>(self, ctx: &Secp256k1<C>) -> Keypair {
101        for key_try in 0u64.. {
102            let secret = self
103                .kdf
104                .derive::<32>(&tagged_derive(SECP256K1_TAG, ChildId(key_try)));
105            // The secret not forming a valid key is highly unlikely, this approach is the
106            // same used when generating a random secp key.
107            if let Ok(key) = Keypair::from_seckey_slice(ctx, &secret) {
108                return key;
109            }
110        }
111
112        unreachable!("If key generation fails this often something else has to be wrong.")
113    }
114
115    /// bls12-381 keys are used for the guardians' threshold signature scheme,
116    /// and most importantly for its use for the blinding keys for e-cash notes.
117    pub fn to_bls12_381_key(&self) -> Scalar {
118        Scalar::from_bytes_wide(&self.kdf.derive(&tagged_derive(BLS12_381_TAG, ChildId(0))))
119    }
120
121    // `ring` does not support any way to get raw bytes from a key,
122    // so we need to be able to get just the raw bytes here, so we can serialize
123    // them, and convert to ring type from it.
124    pub fn to_chacha20_poly1305_key_raw(&self) -> [u8; 32] {
125        self.kdf
126            .derive::<32>(&tagged_derive(CHACHA20_POLY1305, ChildId(0)))
127    }
128
129    pub fn to_chacha20_poly1305_key(&self) -> aead::UnboundKey {
130        aead::UnboundKey::new(
131            &aead::CHACHA20_POLY1305,
132            &self.to_chacha20_poly1305_key_raw(),
133        )
134        .expect("created key")
135    }
136
137    /// Generate a pseudo-random byte array from the derivable secret.
138    pub fn to_random_bytes<const LEN: usize>(&self) -> [u8; LEN] {
139        self.kdf.derive(&tagged_derive(RAW_BYTES, ChildId(0)))
140    }
141}
142
143fn tagged_derive(tag: &[u8; 8], derivation: ChildId) -> [u8; 16] {
144    let mut derivation_info = [0u8; 16];
145    derivation_info[0..8].copy_from_slice(&tag[..]);
146    // The endianness isn't important here because we just need some bytes, but
147    // let's use the default for this project (big endian)
148    derivation_info[8..16].copy_from_slice(&derivation.0.to_be_bytes()[..]);
149    derivation_info
150}
151
152impl std::fmt::Debug for DerivableSecret {
153    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
154        write!(f, "DerivableSecret#")?;
155        fedimint_core::format_hex(
156            &self
157                .kdf
158                .derive::<8>(b"just a debug fingerprint derivation salt"),
159            f,
160        )
161    }
162}