bc_components/
private_key_base.rs

1use anyhow::{Result, bail};
2use bc_rand::{
3    RandomNumberGenerator, SecureRandomNumberGenerator, rng_random_data,
4};
5use bc_ur::prelude::*;
6use ssh_key::{
7    Algorithm as SSHAlgorithm,
8    private::{
9        DsaKeypair, EcdsaKeypair, Ed25519Keypair, KeypairData,
10        PrivateKey as SSHPrivateKey, RsaKeypair,
11    },
12};
13use zeroize::ZeroizeOnDrop;
14
15use crate::{
16    Decrypter, ECKey, ECPrivateKey, Ed25519PrivateKey, EncapsulationPrivateKey,
17    EncapsulationPublicKey, HKDFRng, PrivateKeyDataProvider, PrivateKeys,
18    PrivateKeysProvider, PublicKeys, PublicKeysProvider, Signature, Signer,
19    SigningOptions, SigningPrivateKey, Verifier, X25519PrivateKey, tags,
20};
21
22/// A secure foundation for deriving multiple cryptographic keys.
23///
24/// `PrivateKeyBase` serves as a root of cryptographic material from which
25/// various types of keys can be deterministically derived. It securely manages
26/// the underlying key material and provides methods to derive specific
27/// cryptographic keys for different purposes.
28///
29/// It supports:
30/// - Deterministic derivation of signing keys (Schnorr, ECDSA, Ed25519)
31/// - Deterministic derivation of encryption keys (X25519)
32/// - SSH key generation for various algorithms (Ed25519, ECDSA, DSA, RSA)
33/// - Key pair generation for both signing and encryption
34///
35/// This design allows a single master seed to generate multiple secure keys
36/// for different cryptographic operations, similar to the concept of an
37/// HD wallet in cryptocurrency systems.
38///
39/// # Security
40///
41/// `PrivateKeyBase` implements `ZeroizeOnDrop` to securely erase the sensitive
42/// key material from memory when the object is dropped, reducing the risk of
43/// key extraction via memory attacks.
44///
45/// # Examples
46///
47/// Creating and using a PrivateKeyBase:
48///
49/// ```
50/// use bc_components::{
51///     PrivateKeyBase, PrivateKeysProvider, PublicKeysProvider, Signer,
52/// };
53///
54/// // Create a new random PrivateKeyBase
55/// let key_base = PrivateKeyBase::new();
56///
57/// // Sign a message using the derived Schnorr key
58/// let message = b"Hello, world!";
59/// let signature = key_base.sign(message).unwrap();
60///
61/// // Generate a key pair for public/private key operations
62/// let (private_keys, public_keys) =
63///     (key_base.private_keys(), key_base.public_keys());
64/// ```
65#[derive(Clone, Eq, PartialEq, ZeroizeOnDrop)]
66pub struct PrivateKeyBase(Vec<u8>);
67
68impl Signer for PrivateKeyBase {
69    fn sign_with_options(
70        &self,
71        message: &dyn AsRef<[u8]>,
72        options: Option<SigningOptions>,
73    ) -> Result<Signature> {
74        let schnorr_key = self.schnorr_signing_private_key();
75        schnorr_key.sign_with_options(message, options)
76    }
77}
78
79impl Verifier for PrivateKeyBase {
80    fn verify(&self, signature: &Signature, message: &dyn AsRef<[u8]>) -> bool {
81        let schnorr_key = self
82            .schnorr_signing_private_key()
83            .to_schnorr()
84            .unwrap()
85            .public_key();
86        match signature.to_schnorr() {
87            Some(schnorr_signature) => {
88                schnorr_key.verify(schnorr_signature, message)
89            }
90            None => false,
91        }
92    }
93}
94
95impl Decrypter for PrivateKeyBase {
96    fn encapsulation_private_key(&self) -> EncapsulationPrivateKey {
97        EncapsulationPrivateKey::X25519(self.x25519_private_key())
98    }
99}
100
101impl PrivateKeyBase {
102    /// Generate a new random `PrivateKeyBase`.
103    pub fn new() -> Self {
104        let mut rng = SecureRandomNumberGenerator;
105        Self::new_using(&mut rng)
106    }
107
108    /// Restores a `PrivateKeyBase` from bytes.
109    pub fn from_data(data: impl AsRef<[u8]>) -> Self {
110        Self(data.as_ref().to_vec())
111    }
112
113    /// Restores a `PrivateKeyBase` from an optional reference to an array of
114    /// bytes.
115    ///
116    /// If the data is `None`, a new random `PrivateKeyBase` is generated.
117    pub fn from_optional_data(data: Option<impl AsRef<[u8]>>) -> Self {
118        match data {
119            Some(data) => Self::from_data(data),
120            None => Self::new(),
121        }
122    }
123
124    /// Generate a new random `PrivateKeyBase` using the given random number
125    /// generator.
126    pub fn new_using(rng: &mut impl RandomNumberGenerator) -> Self {
127        Self::from_data(rng_random_data(rng, 32))
128    }
129
130    /// Create a new `PrivateKeyBase` from the given private keys data provider.
131    pub fn new_with_provider(provider: impl PrivateKeyDataProvider) -> Self {
132        Self::from_data(provider.private_key_data())
133    }
134
135    /// Derive a new ECDSA `SigningPrivateKey` from this `PrivateKeyBase`.
136    pub fn ecdsa_signing_private_key(&self) -> SigningPrivateKey {
137        SigningPrivateKey::new_ecdsa(ECPrivateKey::derive_from_key_material(
138            &self.0,
139        ))
140    }
141
142    /// Derive a new Schnorr `SigningPrivateKey` from this `PrivateKeyBase`.
143    pub fn schnorr_signing_private_key(&self) -> SigningPrivateKey {
144        SigningPrivateKey::new_schnorr(ECPrivateKey::derive_from_key_material(
145            &self.0,
146        ))
147    }
148
149    /// Derive a new Ed25519 `SigningPrivateKey` from this `PrivateKeyBase`.
150    pub fn ed25519_signing_private_key(&self) -> SigningPrivateKey {
151        SigningPrivateKey::new_ed25519(
152            Ed25519PrivateKey::derive_from_key_material(&self.0),
153        )
154    }
155
156    /// Derive a new SSH `SigningPrivateKey` from this `PrivateKeyBase`.
157    pub fn ssh_signing_private_key(
158        &self,
159        algorithm: SSHAlgorithm,
160        comment: impl Into<String>,
161    ) -> Result<SigningPrivateKey> {
162        let mut rng = HKDFRng::new(&self.0, algorithm.as_str());
163        let keypair = match algorithm {
164            SSHAlgorithm::Dsa => {
165                KeypairData::Dsa(DsaKeypair::random(&mut rng)?)
166            }
167            SSHAlgorithm::Ecdsa { curve } => {
168                KeypairData::Ecdsa(EcdsaKeypair::random(&mut rng, curve)?)
169            }
170            SSHAlgorithm::Ed25519 => {
171                KeypairData::Ed25519(Ed25519Keypair::random(&mut rng))
172            }
173            SSHAlgorithm::Rsa { hash: _ } => {
174                KeypairData::Rsa(RsaKeypair::random(&mut rng, 2048)?)
175            }
176            _ => bail!("Unsupported SSH algorithm: {:?}", algorithm.as_str()),
177        };
178        let private_key = SSHPrivateKey::new(keypair, comment)?;
179        Ok(SigningPrivateKey::new_ssh(private_key))
180    }
181
182    /// Derive a new `X25519PrivateKey` from this `PrivateKeyBase`.
183    ///
184    /// An X25519 key for public key encryption.
185    pub fn x25519_private_key(&self) -> X25519PrivateKey {
186        X25519PrivateKey::derive_from_key_material(&self.0)
187    }
188
189    /// Derive a new `PrivateKeys` from this `PrivateKeyBase`.
190    ///
191    /// - Includes a Schnorr private key for signing.
192    /// - Includes an X25519 private key for encryption.
193    pub fn schnorr_private_keys(&self) -> PrivateKeys {
194        PrivateKeys::with_keys(
195            self.schnorr_signing_private_key(),
196            EncapsulationPrivateKey::X25519(self.x25519_private_key()),
197        )
198    }
199
200    /// Derive a new `PublicKeys` from this `PrivateKeyBase`.
201    ///
202    /// - Includes a Schnorr public key for signing.
203    /// - Includes an X25519 public key encryption.
204    pub fn schnorr_public_keys(&self) -> PublicKeys {
205        PublicKeys::new(
206            self.schnorr_signing_private_key().public_key().unwrap(),
207            EncapsulationPublicKey::X25519(
208                self.x25519_private_key().public_key(),
209            ),
210        )
211    }
212
213    /// Derive a new `PrivateKeys` from this `PrivateKeyBase`.
214    ///
215    /// - Includes an ECDSA private key for signing.
216    /// - Includes an X25519 private key for encryption.
217    pub fn ecdsa_private_keys(&self) -> PrivateKeys {
218        PrivateKeys::with_keys(
219            self.ecdsa_signing_private_key(),
220            EncapsulationPrivateKey::X25519(self.x25519_private_key()),
221        )
222    }
223
224    /// Derive a new `PublicKeys` from this `PrivateKeyBase`.
225    ///
226    /// - Includes an ECDSA public key for signing.
227    /// - Includes an X25519 public key for encryption.
228    pub fn ecdsa_public_keys(&self) -> PublicKeys {
229        PublicKeys::new(
230            self.ecdsa_signing_private_key().public_key().unwrap(),
231            EncapsulationPublicKey::X25519(
232                self.x25519_private_key().public_key(),
233            ),
234        )
235    }
236
237    /// Derive a new `PrivateKeys` from this `PrivateKeyBase`.
238    ///
239    /// - Includes an SSH private key for signing.
240    /// - Includes an X25519 private key for encryption.
241    pub fn ssh_private_keys(
242        &self,
243        algorithm: SSHAlgorithm,
244        comment: impl Into<String>,
245    ) -> Result<PrivateKeys> {
246        let private_key = self.ssh_signing_private_key(algorithm, comment)?;
247        Ok(PrivateKeys::with_keys(
248            private_key,
249            EncapsulationPrivateKey::X25519(self.x25519_private_key()),
250        ))
251    }
252
253    /// Derive a new `PublicKeys` from this `PrivateKeyBase`.
254    ///
255    /// - Includes an SSH public key for signing.
256    /// - Includes an X25519 public key for encryption.
257    pub fn ssh_public_keys(
258        &self,
259        algorithm: SSHAlgorithm,
260        comment: impl Into<String>,
261    ) -> Result<PublicKeys> {
262        let private_key = self.ssh_signing_private_key(algorithm, comment)?;
263        Ok(PublicKeys::new(
264            private_key.public_key().unwrap(),
265            EncapsulationPublicKey::X25519(
266                self.x25519_private_key().public_key(),
267            ),
268        ))
269    }
270
271    /// Get the raw data of this `PrivateKeyBase`.
272    pub fn as_bytes(&self) -> &[u8] { self.as_ref() }
273}
274
275impl PrivateKeysProvider for PrivateKeyBase {
276    fn private_keys(&self) -> PrivateKeys {
277        PrivateKeys::with_keys(
278            self.schnorr_signing_private_key(),
279            EncapsulationPrivateKey::X25519(self.x25519_private_key()),
280        )
281    }
282}
283
284impl PublicKeysProvider for PrivateKeyBase {
285    fn public_keys(&self) -> PublicKeys { self.schnorr_public_keys() }
286}
287
288impl Default for PrivateKeyBase {
289    fn default() -> Self { Self::new() }
290}
291
292impl std::fmt::Debug for PrivateKeyBase {
293    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
294        write!(f, "PrivateKeyBase")
295    }
296}
297
298impl<'a> From<&'a PrivateKeyBase> for &'a [u8] {
299    fn from(value: &'a PrivateKeyBase) -> Self { &value.0 }
300}
301
302impl AsRef<PrivateKeyBase> for PrivateKeyBase {
303    fn as_ref(&self) -> &PrivateKeyBase { self }
304}
305
306impl AsRef<[u8]> for PrivateKeyBase {
307    fn as_ref(&self) -> &[u8] { &self.0 }
308}
309
310impl CBORTagged for PrivateKeyBase {
311    fn cbor_tags() -> Vec<Tag> {
312        tags_for_values(&[tags::TAG_PRIVATE_KEY_BASE])
313    }
314}
315
316impl From<PrivateKeyBase> for CBOR {
317    fn from(value: PrivateKeyBase) -> Self { value.tagged_cbor() }
318}
319
320impl CBORTaggedEncodable for PrivateKeyBase {
321    fn untagged_cbor(&self) -> CBOR { CBOR::to_byte_string(&self.0) }
322}
323
324impl TryFrom<CBOR> for PrivateKeyBase {
325    type Error = dcbor::Error;
326
327    fn try_from(cbor: CBOR) -> Result<Self, Self::Error> {
328        Self::from_tagged_cbor(cbor)
329    }
330}
331
332impl CBORTaggedDecodable for PrivateKeyBase {
333    fn from_untagged_cbor(untagged_cbor: CBOR) -> dcbor::Result<Self> {
334        let data = CBOR::try_into_byte_string(untagged_cbor)?;
335        let instance = Self::from_data(data);
336        Ok(instance)
337    }
338}
339
340#[cfg(test)]
341mod tests {
342    use bc_ur::{URDecodable, UREncodable};
343    use hex_literal::hex;
344
345    use crate::PrivateKeyBase;
346
347    const SEED: [u8; 16] = hex!("59f2293a5bce7d4de59e71b4207ac5d2");
348
349    #[test]
350    fn test_private_key_base() {
351        crate::register_tags();
352        let private_key_base = PrivateKeyBase::from_data(SEED);
353        assert_eq!(
354            private_key_base
355                .ecdsa_signing_private_key()
356                .to_ecdsa()
357                .unwrap()
358                .data(),
359            &hex!(
360                "9505a44aaf385ce633cf0e2bc49e65cc88794213bdfbf8caf04150b9c4905f5a"
361            )
362        );
363        assert_eq!(
364            private_key_base
365                .schnorr_signing_private_key()
366                .public_key()
367                .unwrap()
368                .to_schnorr()
369                .unwrap()
370                .data(),
371            &hex!(
372                "fd4d22f9e8493da52d730aa402ac9e661deca099ef4db5503f519a73c3493e18"
373            )
374        );
375        assert_eq!(
376            private_key_base.x25519_private_key().data(),
377            &hex!(
378                "77ff838285a0403d3618aa8c30491f99f55221be0b944f50bfb371f43b897485"
379            )
380        );
381        assert_eq!(
382            private_key_base.x25519_private_key().public_key().data(),
383            &hex!(
384                "863cf3facee3ba45dc54e5eedecb21d791d64adfb0a1c63bfb6fea366c1ee62b"
385            )
386        );
387
388        let ur = private_key_base.ur_string();
389        assert_eq!(
390            ur,
391            "ur:crypto-prvkey-base/gdhkwzdtfthptokigtvwnnjsqzcxknsktdsfecsbbk"
392        );
393        assert_eq!(
394            PrivateKeyBase::from_ur_string(&ur).unwrap(),
395            private_key_base
396        );
397    }
398}