bc_components/
private_key_base.rs

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