nucypher_core/
treasure_map.rs

1use alloc::boxed::Box;
2use alloc::collections::BTreeMap;
3use alloc::format;
4use alloc::string::String;
5use alloc::vec::Vec;
6
7use serde::{Deserialize, Serialize};
8use umbral_pre::{
9    decrypt_original, encrypt, serde_bytes, Capsule, EncryptionError, PublicKey, SecretKey,
10    Signature, Signer, VerifiedKeyFrag,
11};
12
13use crate::address::Address;
14use crate::hrac::HRAC;
15use crate::key_frag::{DecryptionError, EncryptedKeyFrag};
16use crate::versioning::{
17    messagepack_deserialize, messagepack_serialize, ProtocolObject, ProtocolObjectInner,
18};
19use crate::RevocationOrder;
20
21/// A structure containing `KeyFrag` objects encrypted for Ursulas chosen for this policy.
22#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
23pub struct TreasureMap {
24    /// Threshold for successful re-encryption.
25    pub threshold: u8,
26    /// Policy HRAC.
27    pub hrac: HRAC,
28    /// Encrypted key frags assigned to target Ursulas.
29    pub destinations: BTreeMap<Address, EncryptedKeyFrag>,
30    /// A key to create encrypted messages under this policy.
31    pub policy_encrypting_key: PublicKey,
32    /// Publisher's verifying key.
33    pub publisher_verifying_key: PublicKey,
34}
35
36impl TreasureMap {
37    /// Create a new treasure map for a collection of ursulas and kfrags.
38    ///
39    /// Panics if `threshold` is set to 0,
40    /// the number of assigned keyfrags is less than `threshold`,
41    /// or if the addresses in `assigned_kfrags` repeat.
42    pub fn new(
43        signer: &Signer,
44        hrac: &HRAC,
45        policy_encrypting_key: &PublicKey,
46        assigned_kfrags: impl IntoIterator<Item = (Address, (PublicKey, VerifiedKeyFrag))>,
47        threshold: u8,
48    ) -> Self {
49        // Panic here since violation of this condition indicates a bug on the caller's side.
50        assert!(threshold != 0, "threshold must be non-zero");
51
52        // Encrypt each kfrag for an Ursula.
53        let mut destinations = BTreeMap::new();
54        for (ursula_address, (ursula_encrypting_key, verified_kfrag)) in assigned_kfrags.into_iter()
55        {
56            let encrypted_kfrag =
57                EncryptedKeyFrag::new(signer, &ursula_encrypting_key, hrac, verified_kfrag);
58            if destinations
59                .insert(ursula_address, encrypted_kfrag)
60                .is_some()
61            {
62                // This means there are repeating addresses in the mapping.
63                // Panic here since violation of this condition indicates a bug on the caller's side.
64                panic!(
65                    "{}",
66                    format!("Repeating address in assigned_kfrags: {:?}", ursula_address)
67                )
68            };
69        }
70
71        // Panic here since violation of this condition indicates a bug on the caller's side.
72        assert!(
73            destinations.len() >= threshold as usize,
74            "threshold cannot be larger than the total number of shares"
75        );
76
77        Self {
78            threshold,
79            hrac: *hrac,
80            destinations,
81            policy_encrypting_key: *policy_encrypting_key,
82            publisher_verifying_key: signer.verifying_key(),
83        }
84    }
85
86    /// Encrypts the treasure map for Bob.
87    pub fn encrypt(&self, signer: &Signer, recipient_key: &PublicKey) -> EncryptedTreasureMap {
88        EncryptedTreasureMap::new(signer, recipient_key, self)
89    }
90
91    /// Makes revocation orders for all destinations in the treasure map.
92    pub fn make_revocation_orders(&self, signer: &Signer) -> Vec<RevocationOrder> {
93        self.destinations
94            .iter()
95            .map(|(address, ekfrag)| RevocationOrder::new(signer, address, ekfrag))
96            .collect()
97    }
98}
99
100impl<'a> ProtocolObjectInner<'a> for TreasureMap {
101    fn brand() -> [u8; 4] {
102        *b"TMap"
103    }
104
105    fn version() -> (u16, u16) {
106        (3, 0)
107    }
108
109    fn unversioned_to_bytes(&self) -> Box<[u8]> {
110        messagepack_serialize(&self)
111    }
112
113    fn unversioned_from_bytes(minor_version: u16, bytes: &[u8]) -> Option<Result<Self, String>> {
114        if minor_version == 0 {
115            Some(messagepack_deserialize(bytes))
116        } else {
117            None
118        }
119    }
120}
121
122impl<'a> ProtocolObject<'a> for TreasureMap {}
123
124#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
125struct AuthorizedTreasureMap {
126    signature: Signature,
127    treasure_map: TreasureMap,
128}
129
130impl AuthorizedTreasureMap {
131    fn message_to_sign(recipient_key: &PublicKey, treasure_map: &TreasureMap) -> Vec<u8> {
132        let mut message = recipient_key.to_compressed_bytes().to_vec();
133        message.extend(treasure_map.to_bytes().iter());
134        message
135    }
136
137    fn new(signer: &Signer, recipient_key: &PublicKey, treasure_map: &TreasureMap) -> Self {
138        let message = Self::message_to_sign(recipient_key, treasure_map);
139        let signature = signer.sign(&message);
140
141        Self {
142            signature,
143            treasure_map: treasure_map.clone(),
144        }
145    }
146
147    fn verify(
148        self,
149        recipient_key: &PublicKey,
150        publisher_verifying_key: &PublicKey,
151    ) -> Option<TreasureMap> {
152        let message = Self::message_to_sign(recipient_key, &self.treasure_map);
153        if !self.signature.verify(publisher_verifying_key, &message) {
154            return None;
155        }
156        Some(self.treasure_map)
157    }
158}
159
160impl<'a> ProtocolObjectInner<'a> for AuthorizedTreasureMap {
161    fn brand() -> [u8; 4] {
162        *b"AMap"
163    }
164
165    fn version() -> (u16, u16) {
166        (3, 0)
167    }
168
169    fn unversioned_to_bytes(&self) -> Box<[u8]> {
170        messagepack_serialize(&self)
171    }
172
173    fn unversioned_from_bytes(minor_version: u16, bytes: &[u8]) -> Option<Result<Self, String>> {
174        if minor_version == 0 {
175            Some(messagepack_deserialize(bytes))
176        } else {
177            None
178        }
179    }
180}
181
182impl<'a> ProtocolObject<'a> for AuthorizedTreasureMap {}
183
184/// A treasure map encrypted for Bob.
185#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
186pub struct EncryptedTreasureMap {
187    capsule: Capsule,
188    #[serde(with = "serde_bytes::as_base64")]
189    ciphertext: Box<[u8]>,
190}
191
192impl EncryptedTreasureMap {
193    fn new(signer: &Signer, recipient_key: &PublicKey, treasure_map: &TreasureMap) -> Self {
194        // TODO: using Umbral for encryption to avoid introducing more crypto primitives.
195        // Most probably it is an overkill, unless it can be used somehow
196        // for Ursula-to-Ursula "baton passing".
197
198        // TODO: `publisher` here can be different from the one in TreasureMap, it seems.
199        // Do we ever cross-check them? Do we want to enforce them to be the same?
200
201        let authorized_tmap = AuthorizedTreasureMap::new(signer, recipient_key, treasure_map);
202        let (capsule, ciphertext) = match encrypt(recipient_key, &authorized_tmap.to_bytes()) {
203            Ok(result) => result,
204            Err(err) => match err {
205                // For now this is the only error that can happen during encryption,
206                // and there's really no point in propagating it.
207                EncryptionError::PlaintextTooLarge => panic!("encryption failed - out of memory?"),
208            },
209        };
210        Self {
211            capsule,
212            ciphertext,
213        }
214    }
215
216    /// Decrypts and verifies the treasure map.
217    pub fn decrypt(
218        &self,
219        sk: &SecretKey,
220        publisher_verifying_key: &PublicKey,
221    ) -> Result<TreasureMap, DecryptionError> {
222        let auth_tmap_bytes = decrypt_original(sk, &self.capsule, &self.ciphertext)
223            .map_err(DecryptionError::DecryptionFailed)?;
224        let auth_tmap = AuthorizedTreasureMap::from_bytes(&auth_tmap_bytes)
225            .map_err(DecryptionError::DeserializationFailed)?;
226        auth_tmap
227            .verify(&sk.public_key(), publisher_verifying_key)
228            .ok_or(DecryptionError::VerificationFailed)
229    }
230}
231
232impl<'a> ProtocolObjectInner<'a> for EncryptedTreasureMap {
233    fn brand() -> [u8; 4] {
234        *b"EMap"
235    }
236
237    fn version() -> (u16, u16) {
238        (3, 0)
239    }
240
241    fn unversioned_to_bytes(&self) -> Box<[u8]> {
242        messagepack_serialize(&self)
243    }
244
245    fn unversioned_from_bytes(minor_version: u16, bytes: &[u8]) -> Option<Result<Self, String>> {
246        if minor_version == 0 {
247            Some(messagepack_deserialize(bytes))
248        } else {
249            None
250        }
251    }
252}
253
254impl<'a> ProtocolObject<'a> for EncryptedTreasureMap {}