umbral_pre/
capsule.rs

1#[cfg(feature = "serde")]
2use alloc::string::String;
3
4use alloc::boxed::Box;
5use alloc::vec::Vec;
6use core::fmt;
7
8use generic_array::GenericArray;
9use rand_core::{CryptoRng, RngCore};
10
11#[cfg(feature = "serde")]
12use serde::{Deserialize, Serialize};
13
14use crate::capsule_frag::CapsuleFrag;
15use crate::curve::{CompressedPointSize, CurvePoint, CurveScalar, NonZeroCurveScalar};
16use crate::hashing_ds::{hash_capsule_points, hash_to_polynomial_arg, hash_to_shared_secret};
17use crate::keys::{PublicKey, SecretKey};
18use crate::params::Parameters;
19use crate::secret_box::SecretBox;
20use crate::traits::fmt_public;
21
22#[cfg(feature = "default-serialization")]
23use crate::{DefaultDeserialize, DefaultSerialize};
24
25/// Errors that can happen when opening a `Capsule` using reencrypted `CapsuleFrag` objects.
26#[derive(Debug, PartialEq, Eq)]
27pub enum OpenReencryptedError {
28    /// An empty capsule fragment list is given.
29    NoCapsuleFrags,
30    /// Capsule fragments are mismatched (originated from [`KeyFrag`](crate::KeyFrag) objects
31    /// generated by different [`generate_kfrags`](crate::generate_kfrags) calls).
32    MismatchedCapsuleFrags,
33    /// Some of the given capsule fragments are repeated.
34    RepeatingCapsuleFrags,
35    /// Internal validation of the result has failed.
36    /// Can be caused by an incorrect (possibly modified) capsule
37    /// or some of the capsule fragments.
38    ValidationFailed,
39}
40
41impl fmt::Display for OpenReencryptedError {
42    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43        match self {
44            Self::NoCapsuleFrags => write!(f, "Empty CapsuleFrag sequence"),
45            Self::MismatchedCapsuleFrags => write!(f, "CapsuleFrags are not pairwise consistent"),
46            Self::RepeatingCapsuleFrags => write!(f, "Some of the CapsuleFrags are repeated"),
47            Self::ValidationFailed => write!(f, "Internal validation failed"),
48        }
49    }
50}
51
52/// A helper struct:
53/// - allows us not to serialize `params`
54/// - allows us to verify the capsule on deserialization.
55#[cfg(feature = "serde")]
56#[derive(Serialize, Deserialize)]
57struct SerializedCapsule {
58    point_e: CurvePoint,
59    point_v: CurvePoint,
60    signature: CurveScalar,
61}
62
63/// Encapsulated symmetric key used to encrypt the plaintext.
64#[derive(Clone, Debug, PartialEq)]
65#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
66#[cfg_attr(feature = "serde", serde(try_from = "SerializedCapsule"))]
67#[cfg_attr(feature = "serde", serde(into = "SerializedCapsule"))]
68pub struct Capsule {
69    pub(crate) params: Parameters,
70    pub(crate) point_e: CurvePoint,
71    pub(crate) point_v: CurvePoint,
72    pub(crate) signature: CurveScalar,
73}
74
75#[cfg(feature = "serde")]
76impl TryFrom<SerializedCapsule> for Capsule {
77    type Error = String;
78
79    fn try_from(source: SerializedCapsule) -> Result<Self, Self::Error> {
80        Self::new_verified(source.point_e, source.point_v, source.signature)
81            .ok_or_else(|| "Capsule self-verification failed".into())
82    }
83}
84
85#[cfg(feature = "serde")]
86impl From<Capsule> for SerializedCapsule {
87    fn from(source: Capsule) -> Self {
88        Self {
89            point_e: source.point_e,
90            point_v: source.point_v,
91            signature: source.signature,
92        }
93    }
94}
95
96#[cfg(feature = "default-serialization")]
97impl DefaultSerialize for Capsule {}
98
99#[cfg(feature = "default-serialization")]
100impl<'de> DefaultDeserialize<'de> for Capsule {}
101
102impl fmt::Display for Capsule {
103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104        fmt_public("Capsule", &self.signature.to_array(), f)
105    }
106}
107
108pub(crate) type KeySeed = GenericArray<u8, CompressedPointSize>;
109
110impl Capsule {
111    fn new(point_e: CurvePoint, point_v: CurvePoint, signature: CurveScalar) -> Self {
112        let params = Parameters::new();
113        Self {
114            params,
115            point_e,
116            point_v,
117            signature,
118        }
119    }
120
121    #[cfg(feature = "serde")]
122    pub(crate) fn new_verified(
123        point_e: CurvePoint,
124        point_v: CurvePoint,
125        signature: CurveScalar,
126    ) -> Option<Self> {
127        let capsule = Self::new(point_e, point_v, signature);
128        match capsule.verify() {
129            false => None,
130            true => Some(capsule),
131        }
132    }
133
134    /// Serializes the capsule by concatenating the byte represenation of its constituents:
135    /// - `e` (compressed curve point, 33 bytes),
136    /// - `v` (compressed curve point, 33 bytes),
137    /// - `signature` (big-endian scalar, 32 bytes).
138    pub fn to_bytes_simple(&self) -> Box<[u8]> {
139        let e = self.point_e.to_compressed_array();
140        let v = self.point_v.to_compressed_array();
141        let s = self.signature.to_array();
142        let v: &[&[u8]] = &[&e, &v, &s];
143        v.concat().into()
144    }
145
146    /// Verifies the integrity of the capsule.
147    #[cfg(feature = "serde")]
148    fn verify(&self) -> bool {
149        let g = CurvePoint::generator();
150        let h = hash_capsule_points(&self.point_e, &self.point_v);
151        &g * &self.signature == &self.point_v + &(&self.point_e * &h)
152    }
153
154    /// Generates a symmetric key and its associated KEM ciphertext, using the given RNG.
155    pub(crate) fn from_public_key(
156        rng: &mut (impl CryptoRng + RngCore),
157        delegating_pk: &PublicKey,
158    ) -> (Capsule, SecretBox<KeySeed>) {
159        let g = CurvePoint::generator();
160
161        let priv_r = SecretBox::new(NonZeroCurveScalar::random(rng));
162        let pub_r = &g * priv_r.as_secret();
163
164        let priv_u = SecretBox::new(NonZeroCurveScalar::random(rng));
165        let pub_u = &g * priv_u.as_secret();
166
167        let h = hash_capsule_points(&pub_r, &pub_u);
168
169        let s = priv_u.as_secret() + &(priv_r.as_secret() * &h);
170
171        let shared_key =
172            SecretBox::new(&delegating_pk.to_point() * &(priv_r.as_secret() + priv_u.as_secret()));
173
174        let capsule = Self::new(pub_r, pub_u, s);
175
176        (
177            capsule,
178            SecretBox::new(shared_key.as_secret().to_compressed_array()),
179        )
180    }
181
182    /// Derive the same symmetric key
183    pub(crate) fn open_original(&self, delegating_sk: &SecretKey) -> SecretBox<KeySeed> {
184        let shared_key = SecretBox::new(
185            &(&self.point_e + &self.point_v) * delegating_sk.to_secret_scalar().as_secret(),
186        );
187        SecretBox::new(shared_key.as_secret().to_compressed_array())
188    }
189
190    #[allow(clippy::many_single_char_names)]
191    pub(crate) fn open_reencrypted(
192        &self,
193        receiving_sk: &SecretKey,
194        delegating_pk: &PublicKey,
195        cfrags: &[CapsuleFrag],
196    ) -> Result<SecretBox<KeySeed>, OpenReencryptedError> {
197        if cfrags.is_empty() {
198            return Err(OpenReencryptedError::NoCapsuleFrags);
199        }
200
201        let precursor = cfrags[0].precursor;
202
203        if !cfrags.iter().all(|cfrag| cfrag.precursor == precursor) {
204            return Err(OpenReencryptedError::MismatchedCapsuleFrags);
205        }
206
207        let pub_key = receiving_sk.public_key().to_point();
208        let dh_point = &precursor * receiving_sk.to_secret_scalar().as_secret();
209
210        // Combination of CFrags via Shamir's Secret Sharing reconstruction
211        let mut lc = Vec::<NonZeroCurveScalar>::with_capacity(cfrags.len());
212        for cfrag in cfrags {
213            let coeff = hash_to_polynomial_arg(&precursor, &pub_key, &dh_point, &cfrag.kfrag_id);
214            lc.push(coeff);
215        }
216
217        let mut e_prime = CurvePoint::identity();
218        let mut v_prime = CurvePoint::identity();
219        for (i, cfrag) in cfrags.iter().enumerate() {
220            // There is a minuscule probability that coefficients for two different frags are equal,
221            // in which case we'd rather fail gracefully.
222            let lambda_i =
223                lambda_coeff(&lc, i).ok_or(OpenReencryptedError::RepeatingCapsuleFrags)?;
224            e_prime = &e_prime + &(&cfrag.point_e1 * &lambda_i);
225            v_prime = &v_prime + &(&cfrag.point_v1 * &lambda_i);
226        }
227
228        // Secret value 'd' allows to make Umbral non-interactive
229        let d = hash_to_shared_secret(&precursor, &pub_key, &dh_point);
230
231        let s = self.signature;
232        let h = hash_capsule_points(&self.point_e, &self.point_v);
233
234        let orig_pub_key = delegating_pk.to_point();
235
236        let inv_d = d.invert();
237
238        if &orig_pub_key * &(&s * &inv_d) != &(&e_prime * &h) + &v_prime {
239            return Err(OpenReencryptedError::ValidationFailed);
240        }
241
242        let shared_key = SecretBox::new(&(&e_prime + &v_prime) * &d);
243        Ok(SecretBox::new(shared_key.as_secret().to_compressed_array()))
244    }
245}
246
247fn lambda_coeff(xs: &[NonZeroCurveScalar], i: usize) -> Option<CurveScalar> {
248    let mut res = CurveScalar::one();
249    for j in 0..xs.len() {
250        if j != i {
251            let inv_diff_opt: Option<CurveScalar> = (&xs[j] - &xs[i]).invert().into();
252            let inv_diff = inv_diff_opt?;
253            res = &(&res * &xs[j]) * &inv_diff;
254        }
255    }
256    Some(res)
257}
258
259#[cfg(test)]
260mod tests {
261
262    use alloc::vec::Vec;
263
264    use rand_core::OsRng;
265
266    use super::{Capsule, OpenReencryptedError};
267
268    use crate::{generate_kfrags, reencrypt, SecretKey, Signer};
269
270    #[cfg(feature = "serde")]
271    use crate::serde_bytes::tests::check_serialization_roundtrip;
272
273    #[test]
274    fn test_open_reencrypted() {
275        let delegating_sk = SecretKey::random();
276        let delegating_pk = delegating_sk.public_key();
277
278        let signer = Signer::new(SecretKey::random());
279
280        let receiving_sk = SecretKey::random();
281        let receiving_pk = receiving_sk.public_key();
282
283        let (capsule, key_seed) = Capsule::from_public_key(&mut OsRng, &delegating_pk);
284
285        let kfrags = generate_kfrags(&delegating_sk, &receiving_pk, &signer, 2, 3, true, true);
286
287        let vcfrags: Vec<_> = kfrags
288            .iter()
289            .map(|kfrag| reencrypt(&capsule, kfrag.clone()))
290            .collect();
291
292        let cfrags = [vcfrags[0].clone().unverify(), vcfrags[1].clone().unverify()];
293
294        let key_seed_reenc = capsule
295            .open_reencrypted(&receiving_sk, &delegating_pk, &cfrags)
296            .unwrap();
297        assert_eq!(key_seed.as_secret(), key_seed_reenc.as_secret());
298
299        // Empty cfrag vector
300        let result = capsule.open_reencrypted(&receiving_sk, &delegating_pk, &[]);
301        assert_eq!(
302            result.map(|x| *x.as_secret()),
303            Err(OpenReencryptedError::NoCapsuleFrags)
304        );
305
306        // Mismatched cfrags - each `generate_kfrags()` uses new randoms.
307        let kfrags2 = generate_kfrags(&delegating_sk, &receiving_pk, &signer, 2, 3, true, true);
308
309        let vcfrags2: Vec<_> = kfrags2
310            .iter()
311            .map(|kfrag| reencrypt(&capsule, kfrag.clone()))
312            .collect();
313
314        let mismatched_cfrags = [
315            vcfrags[0].clone().unverify(),
316            vcfrags2[1].clone().unverify(),
317        ];
318
319        let result = capsule.open_reencrypted(&receiving_sk, &delegating_pk, &mismatched_cfrags);
320        assert_eq!(
321            result.map(|x| *x.as_secret()),
322            Err(OpenReencryptedError::MismatchedCapsuleFrags)
323        );
324
325        // Mismatched capsule
326        let (capsule2, _key_seed) = Capsule::from_public_key(&mut OsRng, &delegating_pk);
327        let result = capsule2.open_reencrypted(&receiving_sk, &delegating_pk, &cfrags);
328        assert_eq!(
329            result.map(|x| *x.as_secret()),
330            Err(OpenReencryptedError::ValidationFailed)
331        );
332    }
333
334    #[cfg(feature = "serde")]
335    #[test]
336    fn test_serde_serialization() {
337        let delegating_sk = SecretKey::random();
338        let delegating_pk = delegating_sk.public_key();
339        let (capsule, _key_seed) = Capsule::from_public_key(&mut OsRng, &delegating_pk);
340
341        check_serialization_roundtrip(&capsule);
342    }
343}