Skip to main content

quantum_shield/
api.rs

1//! The [`HybridCrypto`] convenience facade.
2
3use crate::error::Result;
4use crate::keys::{KeyPair, PublicKeyBundle};
5use crate::multi::MultiRecipientEnvelope;
6use crate::types::{Envelope, HybridSignature};
7use alloc::vec::Vec;
8use zeroize::Zeroizing;
9
10/// A hybrid keypair with convenience methods for the common workflows.
11///
12/// This is a thin wrapper around [`KeyPair`] plus the free functions
13/// [`seal`](crate::seal) and [`verify`](crate::verify).
14///
15/// # Example
16///
17/// ```no_run
18/// use quantum_shield::HybridCrypto;
19///
20/// # fn main() -> quantum_shield::Result<()> {
21/// let alice = HybridCrypto::generate()?;
22/// let bob = HybridCrypto::generate()?;
23///
24/// // Alice encrypts for Bob.
25/// let envelope = alice.seal_for(b"hello", bob.public_keys())?;
26/// let plaintext = bob.open(&envelope)?;
27/// assert_eq!(plaintext, b"hello");
28///
29/// // Alice signs; anyone verifies.
30/// let sig = alice.sign(b"release-v2.tar.gz", b"code-signing")?;
31/// quantum_shield::verify(b"release-v2.tar.gz", b"code-signing", &sig, alice.public_keys())?;
32/// # Ok(())
33/// # }
34/// ```
35#[derive(Debug)]
36pub struct HybridCrypto {
37    keypair: KeyPair,
38}
39
40impl HybridCrypto {
41    /// Generate a fresh hybrid keypair from OS randomness.
42    ///
43    /// # Errors
44    ///
45    /// Returns [`Error::RandomnessUnavailable`](crate::Error::RandomnessUnavailable)
46    /// if the OS RNG fails.
47    pub fn generate() -> Result<Self> {
48        Ok(Self {
49            keypair: KeyPair::generate()?,
50        })
51    }
52
53    /// Restore a keypair from a [`HybridCrypto::to_secret_bytes`] export.
54    ///
55    /// # Errors
56    ///
57    /// Returns [`Error::InvalidKey`](crate::Error::InvalidKey) on malformed input.
58    pub fn from_secret_bytes(bytes: &[u8]) -> Result<Self> {
59        Ok(Self {
60            keypair: KeyPair::from_secret_bytes(bytes)?,
61        })
62    }
63
64    /// Export the private seeds. Handle with care; the buffer zeroizes on drop.
65    pub fn to_secret_bytes(&self) -> Zeroizing<Vec<u8>> {
66        self.keypair.to_secret_bytes()
67    }
68
69    /// The public half of this keypair, for sharing.
70    pub fn public_keys(&self) -> &PublicKeyBundle {
71        self.keypair.public_keys()
72    }
73
74    /// Encrypt `plaintext` for `recipient`. Equivalent to [`crate::seal`].
75    ///
76    /// # Errors
77    ///
78    /// See [`crate::seal`].
79    pub fn seal_for(&self, plaintext: &[u8], recipient: &PublicKeyBundle) -> Result<Envelope> {
80        crate::seal(plaintext, recipient)
81    }
82
83    /// Decrypt an [`Envelope`] addressed to this keypair.
84    ///
85    /// # Errors
86    ///
87    /// Returns [`Error::DecryptionFailed`](crate::Error::DecryptionFailed) for
88    /// any cryptographic failure, with no further detail by design.
89    pub fn open(&self, envelope: &Envelope) -> Result<Vec<u8>> {
90        crate::seal::open(&self.keypair, envelope)
91    }
92
93    /// Decrypt a [`MultiRecipientEnvelope`] if this keypair is a recipient.
94    ///
95    /// Equivalent to [`crate::open_multi`].
96    ///
97    /// # Errors
98    ///
99    /// Returns [`Error::DecryptionFailed`](crate::Error::DecryptionFailed) if
100    /// this keypair is not a recipient or the envelope was tampered with.
101    pub fn open_multi(&self, envelope: &MultiRecipientEnvelope) -> Result<Vec<u8>> {
102        crate::multi::open_multi(&self.keypair, envelope)
103    }
104
105    /// Begin decrypting a streaming envelope from its header bytes.
106    ///
107    /// # Errors
108    ///
109    /// Returns [`Error::InvalidEnvelope`](crate::Error::InvalidEnvelope) if the
110    /// header is malformed.
111    pub fn stream_opener(&self, header: &[u8]) -> Result<crate::StreamOpener> {
112        crate::StreamOpener::new(&self.keypair, header)
113    }
114
115    /// Sign `new_public` as this keypair's authorized successor at `epoch`,
116    /// producing a [`RotationAttestation`](crate::RotationAttestation) that
117    /// anyone trusting this keypair can verify with [`crate::verify_rotation`].
118    ///
119    /// Use a strictly increasing `epoch` across successive rotations so that
120    /// verifiers can reject rolled-back attestations.
121    ///
122    /// # Errors
123    ///
124    /// Propagates signing errors (none expected for a valid keypair).
125    pub fn attest_rotation(
126        &self,
127        new_public: &PublicKeyBundle,
128        epoch: u64,
129    ) -> Result<crate::RotationAttestation> {
130        crate::rotate::attest_rotation(&self.keypair, new_public, epoch)
131    }
132
133    /// Sign `message` under an application `context` (0–255 bytes) with both
134    /// Ed25519 and ML-DSA-87.
135    ///
136    /// The context separates uses of the same key (e.g. `b"code-signing"` vs
137    /// `b"api-auth"`); pass `b""` if you don't need one, and pass the same
138    /// value to [`crate::verify`].
139    ///
140    /// # Errors
141    ///
142    /// Returns [`Error::ContextTooLong`](crate::Error::ContextTooLong) if
143    /// `context` exceeds 255 bytes.
144    pub fn sign(&self, message: &[u8], context: &[u8]) -> Result<HybridSignature> {
145        crate::sign::sign(&self.keypair, message, context)
146    }
147}
148
149impl From<KeyPair> for HybridCrypto {
150    fn from(keypair: KeyPair) -> Self {
151        Self { keypair }
152    }
153}