Skip to main content

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    #[allow(clippy::result_large_err)]
129    pub fn verify(
130        self,
131        capsules: &[Capsule],
132        alice_verifying_key: &PublicKey,
133        ursula_verifying_key: &PublicKey,
134        policy_encrypting_key: &PublicKey,
135        bob_encrypting_key: &PublicKey,
136    ) -> Result<Box<[VerifiedCapsuleFrag]>, VerificationError> {
137        if capsules.len() != self.cfrags.len() {
138            // Mismatched number of capsules and cfrags
139            return Err(VerificationError);
140        }
141
142        // Validate re-encryption signature
143        if !self.signature.verify(
144            ursula_verifying_key,
145            &message_to_sign(capsules, &self.cfrags),
146        ) {
147            return Err(VerificationError);
148        }
149
150        let vcfrags = self
151            .cfrags
152            .into_vec()
153            .into_iter()
154            .zip(capsules.iter())
155            .map(|(cfrag, capsule)| {
156                cfrag.verify(
157                    capsule,
158                    alice_verifying_key,
159                    policy_encrypting_key,
160                    bob_encrypting_key,
161                )
162            })
163            .collect::<Result<Vec<_>, _>>();
164
165        // From the above statement we get a list of (CapsuleFragVerificationError, CapsuleFrag)
166        // in the error case, but at this point nobody's interested in that.
167        vcfrags
168            .map(|vcfrags| vcfrags.into_boxed_slice())
169            .map_err(|_err| VerificationError)
170    }
171}
172
173impl<'a> ProtocolObjectInner<'a> for ReencryptionResponse {
174    fn brand() -> [u8; 4] {
175        *b"ReRs"
176    }
177
178    fn version() -> (u16, u16) {
179        (3, 0)
180    }
181
182    fn unversioned_to_bytes(&self) -> Box<[u8]> {
183        messagepack_serialize(&self)
184    }
185
186    fn unversioned_from_bytes(minor_version: u16, bytes: &[u8]) -> Option<Result<Self, String>> {
187        if minor_version == 0 {
188            Some(messagepack_deserialize(bytes))
189        } else {
190            None
191        }
192    }
193}
194
195impl<'a> ProtocolObject<'a> for ReencryptionResponse {}
196
197#[cfg(test)]
198mod tests {
199    use umbral_pre::SecretKey;
200    use umbral_pre::{encrypt, generate_kfrags, Signer};
201
202    use crate::{Conditions, Context, EncryptedKeyFrag, HRAC};
203
204    use super::ReencryptionRequest;
205
206    #[test]
207    fn conditions_and_context_are_different() {
208        let some_secret = SecretKey::random();
209        let some_trinket = some_secret.public_key();
210
211        let _another_secret = SecretKey::random();
212        let another_trinket = some_secret.public_key();
213
214        let encryption_result = encrypt(&some_trinket, b"peace at dawn");
215
216        let (capsule, _ciphertext) = encryption_result.unwrap();
217
218        let hrac = HRAC::new(&some_trinket, &another_trinket, &[42]);
219
220        let signer = Signer::new(SecretKey::random());
221
222        let verified_kfrags =
223            generate_kfrags(&some_secret, &another_trinket, &signer, 5, 8, true, true);
224        let verified_kfrags_vector = verified_kfrags.into_vec();
225        let one_verified_krag_in_particular = verified_kfrags_vector[0].clone();
226        let encrypted_kfrag = EncryptedKeyFrag::new(
227            &signer,
228            &another_trinket,
229            &hrac,
230            one_verified_krag_in_particular,
231        );
232
233        let request = ReencryptionRequest::new(
234            &[capsule],
235            &hrac,
236            &encrypted_kfrag,
237            &some_trinket,
238            &another_trinket,
239            Some(&Conditions::new("abcd")),
240            Some(&Context::new("efgh")),
241        );
242        let conditions = request.conditions.unwrap();
243        assert_eq!(conditions.as_ref(), "abcd");
244
245        let context = request.context.unwrap();
246        assert_eq!(context.as_ref(), "efgh");
247    }
248}