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