nucypher_core/
reencryption.rs

1use alloc::boxed::Box;
2use alloc::string::String;
3use alloc::vec::Vec;
4
5use serde::{Deserialize, Serialize};
6use umbral_pre::{Capsule, CapsuleFrag, PublicKey, Signature, Signer, VerifiedCapsuleFrag};
7
8use crate::conditions::{Conditions, Context};
9use crate::hrac::HRAC;
10use crate::key_frag::EncryptedKeyFrag;
11use crate::versioning::{
12    messagepack_deserialize, messagepack_serialize, ProtocolObject, ProtocolObjectInner,
13};
14use crate::VerificationError;
15
16/// A request for an Ursula to reencrypt for several capsules.
17#[derive(PartialEq, Debug, Serialize, Deserialize)]
18pub struct ReencryptionRequest {
19    /// Capsules to re-encrypt.
20    pub capsules: Box<[Capsule]>,
21    /// Policy HRAC.
22    pub hrac: HRAC,
23    /// Key frag encrypted for the Ursula.
24    pub encrypted_kfrag: EncryptedKeyFrag,
25    /// Publisher's verifying key.
26    pub publisher_verifying_key: PublicKey,
27    /// Recipient's (Bob's) verifying key.
28    pub bob_verifying_key: PublicKey,
29    /// A blob of bytes containing decryption conditions for this message.
30    pub conditions: Option<Conditions>,
31    /// A blob of bytes containing context required to evaluate conditions.
32    pub context: Option<Context>,
33}
34
35impl ReencryptionRequest {
36    /// Creates a new reencryption request.
37    pub fn new(
38        capsules: &[Capsule],
39        hrac: &HRAC,
40        encrypted_kfrag: &EncryptedKeyFrag,
41        publisher_verifying_key: &PublicKey,
42        bob_verifying_key: &PublicKey,
43        conditions: Option<&Conditions>,
44        context: Option<&Context>,
45    ) -> Self {
46        Self {
47            capsules: capsules.to_vec().into(),
48            hrac: *hrac,
49            encrypted_kfrag: encrypted_kfrag.clone(),
50            publisher_verifying_key: *publisher_verifying_key,
51            bob_verifying_key: *bob_verifying_key,
52            conditions: conditions.cloned(),
53            context: context.cloned(),
54        }
55    }
56}
57
58impl<'a> ProtocolObjectInner<'a> for ReencryptionRequest {
59    fn brand() -> [u8; 4] {
60        *b"ReRq"
61    }
62
63    fn version() -> (u16, u16) {
64        (3, 0)
65    }
66
67    fn unversioned_to_bytes(&self) -> Box<[u8]> {
68        messagepack_serialize(&self)
69    }
70
71    fn unversioned_from_bytes(minor_version: u16, bytes: &[u8]) -> Option<Result<Self, String>> {
72        if minor_version == 0 {
73            Some(messagepack_deserialize(bytes))
74        } else {
75            None
76        }
77    }
78}
79
80impl<'a> ProtocolObject<'a> for ReencryptionRequest {}
81
82/// A response from Ursula with reencrypted capsule frags.
83#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
84pub struct ReencryptionResponse {
85    cfrags: Box<[CapsuleFrag]>,
86    signature: Signature,
87}
88
89fn message_to_sign(capsules: &[Capsule], cfrags: &[CapsuleFrag]) -> Vec<u8> {
90    let capsule_bytes = capsules.iter().fold(Vec::<u8>::new(), |mut acc, capsule| {
91        acc.extend(messagepack_serialize(capsule).as_ref());
92        acc
93    });
94
95    let cfrag_bytes = cfrags.iter().fold(Vec::<u8>::new(), |mut acc, cfrag| {
96        acc.extend(messagepack_serialize(cfrag).as_ref());
97        acc
98    });
99
100    [capsule_bytes, cfrag_bytes].concat()
101}
102
103impl ReencryptionResponse {
104    /// Creates and signs a new reencryption response.
105    pub fn new<'a>(
106        signer: &Signer,
107        capsules_and_vcfrags: impl IntoIterator<Item = (&'a Capsule, VerifiedCapsuleFrag)>,
108    ) -> Self {
109        let (capsules, vcfrags): (Vec<_>, Vec<_>) = capsules_and_vcfrags.into_iter().unzip();
110
111        // un-verify
112        let cfrags: Vec<_> = vcfrags
113            .into_iter()
114            .map(|vcfrag| vcfrag.unverify())
115            .collect();
116
117        let capsules: Vec<_> = capsules.into_iter().cloned().collect();
118
119        let signature = signer.sign(&message_to_sign(&capsules, &cfrags));
120
121        ReencryptionResponse {
122            cfrags: cfrags.into_boxed_slice(),
123            signature,
124        }
125    }
126
127    /// Verifies the reencryption response and returns the contained kfrags on success.
128    pub fn verify(
129        self,
130        capsules: &[Capsule],
131        alice_verifying_key: &PublicKey,
132        ursula_verifying_key: &PublicKey,
133        policy_encrypting_key: &PublicKey,
134        bob_encrypting_key: &PublicKey,
135    ) -> Result<Box<[VerifiedCapsuleFrag]>, VerificationError> {
136        if capsules.len() != self.cfrags.len() {
137            // Mismatched number of capsules and cfrags
138            return Err(VerificationError);
139        }
140
141        // Validate re-encryption signature
142        if !self.signature.verify(
143            ursula_verifying_key,
144            &message_to_sign(capsules, &self.cfrags),
145        ) {
146            return Err(VerificationError);
147        }
148
149        let vcfrags = self
150            .cfrags
151            .into_vec()
152            .into_iter()
153            .zip(capsules.iter())
154            .map(|(cfrag, capsule)| {
155                cfrag.verify(
156                    capsule,
157                    alice_verifying_key,
158                    policy_encrypting_key,
159                    bob_encrypting_key,
160                )
161            })
162            .collect::<Result<Vec<_>, _>>();
163
164        // From the above statement we get a list of (CapsuleFragVerificationError, CapsuleFrag)
165        // in the error case, but at this point nobody's interested in that.
166        vcfrags
167            .map(|vcfrags| vcfrags.into_boxed_slice())
168            .map_err(|_err| VerificationError)
169    }
170}
171
172impl<'a> ProtocolObjectInner<'a> for ReencryptionResponse {
173    fn brand() -> [u8; 4] {
174        *b"ReRs"
175    }
176
177    fn version() -> (u16, u16) {
178        (3, 0)
179    }
180
181    fn unversioned_to_bytes(&self) -> Box<[u8]> {
182        messagepack_serialize(&self)
183    }
184
185    fn unversioned_from_bytes(minor_version: u16, bytes: &[u8]) -> Option<Result<Self, String>> {
186        if minor_version == 0 {
187            Some(messagepack_deserialize(bytes))
188        } else {
189            None
190        }
191    }
192}
193
194impl<'a> ProtocolObject<'a> for ReencryptionResponse {}
195
196#[cfg(test)]
197mod tests {
198    use umbral_pre::SecretKey;
199    use umbral_pre::{encrypt, generate_kfrags, Signer};
200
201    use crate::{Conditions, Context, EncryptedKeyFrag, HRAC};
202
203    use super::ReencryptionRequest;
204
205    #[test]
206    fn conditions_and_context_are_different() {
207        let some_secret = SecretKey::random();
208        let some_trinket = some_secret.public_key();
209
210        let _another_secret = SecretKey::random();
211        let another_trinket = some_secret.public_key();
212
213        let encryption_result = encrypt(&some_trinket, b"peace at dawn");
214
215        let (capsule, _ciphertext) = encryption_result.unwrap();
216
217        let hrac = HRAC::new(&some_trinket, &another_trinket, &[42]);
218
219        let signer = Signer::new(SecretKey::random());
220
221        let verified_kfrags =
222            generate_kfrags(&some_secret, &another_trinket, &signer, 5, 8, true, true);
223        let verified_kfrags_vector = verified_kfrags.into_vec();
224        let one_verified_krag_in_particular = verified_kfrags_vector[0].clone();
225        let encrypted_kfrag = EncryptedKeyFrag::new(
226            &signer,
227            &another_trinket,
228            &hrac,
229            one_verified_krag_in_particular,
230        );
231
232        let request = ReencryptionRequest::new(
233            &[capsule],
234            &hrac,
235            &encrypted_kfrag,
236            &some_trinket,
237            &another_trinket,
238            Some(&Conditions::new("abcd")),
239            Some(&Context::new("efgh")),
240        );
241        let conditions = request.conditions.unwrap();
242        assert_eq!(conditions.as_ref(), "abcd");
243
244        let context = request.context.unwrap();
245        assert_eq!(context.as_ref(), "efgh");
246    }
247}