bc_components/
private_key_base.rs

1use bc_rand::{
2    RandomNumberGenerator, SecureRandomNumberGenerator, rng_random_data,
3};
4use bc_ur::prelude::*;
5use ssh_key::{
6    Algorithm as SSHAlgorithm,
7    private::{
8        DsaKeypair, EcdsaKeypair, Ed25519Keypair, KeypairData,
9        PrivateKey as SSHPrivateKey, RsaKeypair,
10    },
11};
12use zeroize::ZeroizeOnDrop;
13
14use crate::{
15    Decrypter, ECKey, ECPrivateKey, Ed25519PrivateKey, EncapsulationPrivateKey,
16    EncapsulationPublicKey, Error, HKDFRng, PrivateKeyDataProvider,
17    PrivateKeys, PrivateKeysProvider, PublicKeys, PublicKeysProvider, Result,
18    Signature, Signer, SigningOptions, SigningPrivateKey, Verifier,
19    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            _ => {
177                return Err(Error::ssh(format!(
178                    "Unsupported SSH algorithm: {}",
179                    algorithm.as_str()
180                )));
181            }
182        };
183        let private_key = SSHPrivateKey::new(keypair, comment)?;
184        Ok(SigningPrivateKey::new_ssh(private_key))
185    }
186
187    /// Derive a new `X25519PrivateKey` from this `PrivateKeyBase`.
188    ///
189    /// An X25519 key for public key encryption.
190    pub fn x25519_private_key(&self) -> X25519PrivateKey {
191        X25519PrivateKey::derive_from_key_material(&self.0)
192    }
193
194    /// Derive a new `PrivateKeys` from this `PrivateKeyBase`.
195    ///
196    /// - Includes a Schnorr private key for signing.
197    /// - Includes an X25519 private key for encryption.
198    pub fn schnorr_private_keys(&self) -> PrivateKeys {
199        PrivateKeys::with_keys(
200            self.schnorr_signing_private_key(),
201            EncapsulationPrivateKey::X25519(self.x25519_private_key()),
202        )
203    }
204
205    /// Derive a new `PublicKeys` from this `PrivateKeyBase`.
206    ///
207    /// - Includes a Schnorr public key for signing.
208    /// - Includes an X25519 public key encryption.
209    pub fn schnorr_public_keys(&self) -> PublicKeys {
210        PublicKeys::new(
211            self.schnorr_signing_private_key().public_key().unwrap(),
212            EncapsulationPublicKey::X25519(
213                self.x25519_private_key().public_key(),
214            ),
215        )
216    }
217
218    /// Derive a new `PrivateKeys` from this `PrivateKeyBase`.
219    ///
220    /// - Includes an ECDSA private key for signing.
221    /// - Includes an X25519 private key for encryption.
222    pub fn ecdsa_private_keys(&self) -> PrivateKeys {
223        PrivateKeys::with_keys(
224            self.ecdsa_signing_private_key(),
225            EncapsulationPrivateKey::X25519(self.x25519_private_key()),
226        )
227    }
228
229    /// Derive a new `PublicKeys` from this `PrivateKeyBase`.
230    ///
231    /// - Includes an ECDSA public key for signing.
232    /// - Includes an X25519 public key for encryption.
233    pub fn ecdsa_public_keys(&self) -> PublicKeys {
234        PublicKeys::new(
235            self.ecdsa_signing_private_key().public_key().unwrap(),
236            EncapsulationPublicKey::X25519(
237                self.x25519_private_key().public_key(),
238            ),
239        )
240    }
241
242    /// Derive a new `PrivateKeys` from this `PrivateKeyBase`.
243    ///
244    /// - Includes an SSH private key for signing.
245    /// - Includes an X25519 private key for encryption.
246    pub fn ssh_private_keys(
247        &self,
248        algorithm: SSHAlgorithm,
249        comment: impl Into<String>,
250    ) -> Result<PrivateKeys> {
251        let private_key = self.ssh_signing_private_key(algorithm, comment)?;
252        Ok(PrivateKeys::with_keys(
253            private_key,
254            EncapsulationPrivateKey::X25519(self.x25519_private_key()),
255        ))
256    }
257
258    /// Derive a new `PublicKeys` from this `PrivateKeyBase`.
259    ///
260    /// - Includes an SSH public key for signing.
261    /// - Includes an X25519 public key for encryption.
262    pub fn ssh_public_keys(
263        &self,
264        algorithm: SSHAlgorithm,
265        comment: impl Into<String>,
266    ) -> Result<PublicKeys> {
267        let private_key = self.ssh_signing_private_key(algorithm, comment)?;
268        Ok(PublicKeys::new(
269            private_key.public_key().unwrap(),
270            EncapsulationPublicKey::X25519(
271                self.x25519_private_key().public_key(),
272            ),
273        ))
274    }
275
276    /// Get the raw data of this `PrivateKeyBase`.
277    pub fn as_bytes(&self) -> &[u8] { self.as_ref() }
278}
279
280impl PrivateKeysProvider for PrivateKeyBase {
281    fn private_keys(&self) -> PrivateKeys {
282        PrivateKeys::with_keys(
283            self.schnorr_signing_private_key(),
284            EncapsulationPrivateKey::X25519(self.x25519_private_key()),
285        )
286    }
287}
288
289impl PublicKeysProvider for PrivateKeyBase {
290    fn public_keys(&self) -> PublicKeys { self.schnorr_public_keys() }
291}
292
293impl Default for PrivateKeyBase {
294    fn default() -> Self { Self::new() }
295}
296
297impl std::fmt::Debug for PrivateKeyBase {
298    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
299        write!(f, "PrivateKeyBase")
300    }
301}
302
303impl<'a> From<&'a PrivateKeyBase> for &'a [u8] {
304    fn from(value: &'a PrivateKeyBase) -> Self { &value.0 }
305}
306
307impl AsRef<PrivateKeyBase> for PrivateKeyBase {
308    fn as_ref(&self) -> &PrivateKeyBase { self }
309}
310
311impl AsRef<[u8]> for PrivateKeyBase {
312    fn as_ref(&self) -> &[u8] { &self.0 }
313}
314
315impl CBORTagged for PrivateKeyBase {
316    fn cbor_tags() -> Vec<Tag> {
317        tags_for_values(&[tags::TAG_PRIVATE_KEY_BASE])
318    }
319}
320
321impl From<PrivateKeyBase> for CBOR {
322    fn from(value: PrivateKeyBase) -> Self { value.tagged_cbor() }
323}
324
325impl CBORTaggedEncodable for PrivateKeyBase {
326    fn untagged_cbor(&self) -> CBOR { CBOR::to_byte_string(&self.0) }
327}
328
329impl TryFrom<CBOR> for PrivateKeyBase {
330    type Error = dcbor::Error;
331
332    fn try_from(cbor: CBOR) -> std::result::Result<Self, Self::Error> {
333        Self::from_tagged_cbor(cbor)
334    }
335}
336
337impl CBORTaggedDecodable for PrivateKeyBase {
338    fn from_untagged_cbor(untagged_cbor: CBOR) -> dcbor::Result<Self> {
339        let data = CBOR::try_into_byte_string(untagged_cbor)?;
340        let instance = Self::from_data(data);
341        Ok(instance)
342    }
343}
344
345#[cfg(test)]
346mod tests {
347    use bc_ur::{URDecodable, UREncodable};
348    use hex_literal::hex;
349
350    use crate::PrivateKeyBase;
351
352    const SEED: [u8; 16] = hex!("59f2293a5bce7d4de59e71b4207ac5d2");
353
354    #[test]
355    fn test_private_key_base() {
356        crate::register_tags();
357        let private_key_base = PrivateKeyBase::from_data(SEED);
358        assert_eq!(
359            private_key_base
360                .ecdsa_signing_private_key()
361                .to_ecdsa()
362                .unwrap()
363                .data(),
364            &hex!(
365                "9505a44aaf385ce633cf0e2bc49e65cc88794213bdfbf8caf04150b9c4905f5a"
366            )
367        );
368        assert_eq!(
369            private_key_base
370                .schnorr_signing_private_key()
371                .public_key()
372                .unwrap()
373                .to_schnorr()
374                .unwrap()
375                .data(),
376            &hex!(
377                "fd4d22f9e8493da52d730aa402ac9e661deca099ef4db5503f519a73c3493e18"
378            )
379        );
380        assert_eq!(
381            private_key_base.x25519_private_key().data(),
382            &hex!(
383                "77ff838285a0403d3618aa8c30491f99f55221be0b944f50bfb371f43b897485"
384            )
385        );
386        assert_eq!(
387            private_key_base.x25519_private_key().public_key().data(),
388            &hex!(
389                "863cf3facee3ba45dc54e5eedecb21d791d64adfb0a1c63bfb6fea366c1ee62b"
390            )
391        );
392
393        let ur = private_key_base.ur_string();
394        assert_eq!(
395            ur,
396            "ur:crypto-prvkey-base/gdhkwzdtfthptokigtvwnnjsqzcxknsktdsfecsbbk"
397        );
398        assert_eq!(
399            PrivateKeyBase::from_ur_string(&ur).unwrap(),
400            private_key_base
401        );
402    }
403}