fedimint_derive_secret/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
//! Scheme for deriving deterministic secret keys
//!
//! `DerivableSecret` represents a secret key that can be used to derive child
//! secret keys. A root key secret can be used to derives child
//! keys from it, which can have child keys derived from them, recursively.
//!
//! The `DerivableSecret` struct in this implementation is only used for
//! deriving secret keys, not public keys. This allows supporting multiple
//! crypto schemes for the different cryptographic operations used across the
//! different modules:
//!
//! * secp256k1 for bitcoin deposit addresses, redeem keys and contract keys for
//!   lightning,
//! * bls12-381 for the guardians' threshold signature scheme,
//! * chacha20-poly1305 for symmetric encryption used for backups.
use std::fmt::Formatter;

use bls12_381::Scalar;
use fedimint_core::config::FederationId;
use fedimint_core::encoding::{Decodable, Encodable};
use fedimint_core::secp256k1::{KeyPair, Secp256k1, Signing};
use fedimint_core::BitcoinHash;
use hkdf::hashes::Sha512;
use hkdf::Hkdf;
use ring::aead;

const CHILD_TAG: &[u8; 8] = b"childkey";
const SECP256K1_TAG: &[u8; 8] = b"secp256k";
const BLS12_381_TAG: &[u8; 8] = b"bls12381";
const CHACHA20_POLY1305: &[u8; 8] = b"c20p1305";
const RAW_BYTES: &[u8; 8] = b"rawbytes";

/// Describes a child key of a [`DerivableSecret`]
#[derive(Debug, Copy, Clone, Encodable, Decodable)]
pub struct ChildId(pub u64);

/// A secret that can have child-subkey derived from it.
#[derive(Clone)]
pub struct DerivableSecret {
    /// Derivation level, root = 0, every `child_key` increments it
    level: usize,
    /// An instance of the HKDF (Hash-based Key Derivation
    ///   Function) with SHA-512 as the underlying hash function. It is used to
    ///   derive child keys.
    // TODO: wrap in some secret protecting wrappers maybe?
    kdf: Hkdf<Sha512>,
}

impl DerivableSecret {
    /// Derive root secret key from a secret material and salt.
    ///
    /// The `salt` is just additional data t used
    /// as an additional input to the HKDF.
    pub fn new_root(root_key: &[u8], salt: &[u8]) -> Self {
        DerivableSecret {
            level: 0,
            kdf: Hkdf::new(root_key, Some(salt)),
        }
    }

    /// Get derivation level
    ///
    /// This is useful for ensuring a correct derivation level is used,
    /// in various places.
    ///
    /// Root keys start at `0`, and every derived key increments it.
    pub fn level(&self) -> usize {
        self.level
    }

    pub fn child_key(&self, cid: ChildId) -> DerivableSecret {
        DerivableSecret {
            level: self.level + 1,
            kdf: Hkdf::from_prk(self.kdf.derive_hmac(&tagged_derive(CHILD_TAG, cid))),
        }
    }

    /// Derive a federation-ID-based child key from self.
    ///
    /// This is useful to ensure that the same root secret is not reused
    /// across multiple `fedimint-client` instances for different federations.
    ///
    /// We reset the level to 0 here since `fedimint-client` expects its root
    /// secret to be at that level.
    pub fn federation_key(&self, federation_id: &FederationId) -> DerivableSecret {
        DerivableSecret {
            level: 0,
            kdf: Hkdf::from_prk(
                self.kdf.derive_hmac(&tagged_derive(
                    &federation_id.0.to_byte_array()[..8]
                        .try_into()
                        .expect("Slice with length 8"),
                    ChildId(0),
                )),
            ),
        }
    }

    /// secp256k1 keys are used for bitcoin deposit addresses, redeem keys and
    /// contract keys for lightning.
    pub fn to_secp_key<C: Signing>(self, ctx: &Secp256k1<C>) -> KeyPair {
        for key_try in 0u64.. {
            let secret = self
                .kdf
                .derive::<32>(&tagged_derive(SECP256K1_TAG, ChildId(key_try)));
            // The secret not forming a valid key is highly unlikely, this approach is the
            // same used when generating a random secp key.
            if let Ok(key) = KeyPair::from_seckey_slice(ctx, &secret) {
                return key;
            }
        }

        unreachable!("If key generation fails this often something else has to be wrong.")
    }

    /// bls12-381 keys are used for the guardians' threshold signature scheme,
    /// and most importantly for its use for the blinding keys for e-cash notes.
    pub fn to_bls12_381_key(&self) -> Scalar {
        Scalar::from_bytes_wide(&self.kdf.derive(&tagged_derive(BLS12_381_TAG, ChildId(0))))
    }

    // `ring` does not support any way to get raw bytes from a key,
    // so we need to be able to get just the raw bytes here, so we can serialize
    // them, and convert to ring type from it.
    pub fn to_chacha20_poly1305_key_raw(&self) -> [u8; 32] {
        self.kdf
            .derive::<32>(&tagged_derive(CHACHA20_POLY1305, ChildId(0)))
    }

    pub fn to_chacha20_poly1305_key(&self) -> aead::UnboundKey {
        aead::UnboundKey::new(
            &aead::CHACHA20_POLY1305,
            &self.to_chacha20_poly1305_key_raw(),
        )
        .expect("created key")
    }

    /// Generate a pseudo-random byte array from the derivable secret.
    pub fn to_random_bytes<const LEN: usize>(&self) -> [u8; LEN] {
        self.kdf.derive(&tagged_derive(RAW_BYTES, ChildId(0)))
    }
}

fn tagged_derive(tag: &[u8; 8], derivation: ChildId) -> [u8; 16] {
    let mut derivation_info = [0u8; 16];
    derivation_info[0..8].copy_from_slice(&tag[..]);
    // The endianness isn't important here because we just need some bytes, but
    // let's use the default for this project (big endian)
    derivation_info[8..16].copy_from_slice(&derivation.0.to_be_bytes()[..]);
    derivation_info
}

impl std::fmt::Debug for DerivableSecret {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "DerivableSecret#")?;
        bitcoin_hashes::hex::format_hex(
            &self
                .kdf
                .derive::<8>(b"just a debug fingerprint derivation salt"),
            f,
        )
    }
}