bc_components/
private_key_base.rs

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