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}