1use candid::{CandidType, Decode, Deserialize, Encode, Principal};
2use custom_debug::Debug;
3use ic_stable_structures::{storable::Bound, Storable};
4use std::{
5 borrow::Cow,
6 collections::{HashMap, HashSet},
7 hash::Hash,
8};
9
10#[derive(Clone, Debug, CandidType, Deserialize, Eq, PartialEq, Hash)]
11pub struct PrincipalRule {
12 when: Option<u64>,
13 was_read: bool,
14}
15
16impl PrincipalRule {
17 pub fn when(&self) -> Option<u64> {
18 self.when
19 }
20 pub fn was_read(&self) -> bool {
21 self.was_read
22 }
23}
24
25pub const EVERYONE: &str = "everyone";
26pub type NoteId = u128;
27
28#[derive(Clone, Debug, CandidType, Deserialize, Eq, PartialEq, Hash)]
29pub struct HistoryEntry {
30 action: String,
31 user: String,
32 rule: Option<(String, Option<u64>)>,
33 labels: Vec<String>,
34 created_at: u64,
35}
36
37impl HistoryEntry {
38 pub fn action(&self) -> String {
39 self.action.clone()
40 }
41 pub fn user(&self) -> String {
42 self.user.clone()
43 }
44 pub fn rule(&self) -> Option<(String, Option<u64>)> {
45 self.rule.clone()
46 }
47 pub fn created_at(&self) -> u64 {
48 self.created_at
49 }
50 pub fn labels(&self) -> Vec<String> {
51 self.labels.clone()
52 }
53}
54
55#[derive(Clone, Debug, CandidType, Deserialize, Eq, PartialEq)]
56pub struct EncryptedNote {
57 id: NoteId,
58 #[debug(skip)]
59 encrypted_text: String,
60 data: String,
61 owner: String,
62 users: HashMap<String, PrincipalRule>,
65
66 locked: bool,
67 read_by: HashSet<String>,
68 created_at: u64,
69 updated_at: u64,
70 history: Vec<HistoryEntry>,
71}
72
73impl Default for EncryptedNote {
74 fn default() -> Self {
75 EncryptedNote {
76 id: 0,
77 encrypted_text: "".to_string(),
78 data: "".to_string(),
79 owner: "".to_string(),
80 users: HashMap::new(),
81 locked: false,
82 created_at: ic_cdk::api::time(),
83 updated_at: ic_cdk::api::time(),
84 history: vec![],
85 read_by: HashSet::new(),
86 }
87 }
88}
89
90impl EncryptedNote {
91 pub fn create(id: NoteId) -> Self {
92 let user = &caller().to_text();
93 EncryptedNote {
94 id,
95 owner: user.clone(),
96 data: String::new(),
97 users: HashMap::new(),
98 encrypted_text: String::new(),
99 locked: false,
100 read_by: HashSet::new(),
101 created_at: ic_cdk::api::time(),
102 updated_at: ic_cdk::api::time(),
103 history: vec![HistoryEntry {
104 action: "created".to_string(),
105 labels: vec![],
106 user: user.clone(),
107 rule: None,
108 created_at: ic_cdk::api::time(),
109 }],
110 }
111 }
112 pub fn id(&self) -> NoteId {
113 self.id
114 }
115 pub fn data(&self) -> String {
116 self.data.clone()
117 }
118 pub fn read_by(&self) -> HashSet<String> {
119 self.read_by.clone()
120 }
121 pub fn encrypted_text(&self) -> String {
122 self.encrypted_text.clone()
123 }
124 pub fn owner(&self) -> String {
125 self.owner.clone()
126 }
127 pub fn users(&self) -> HashMap<String, PrincipalRule> {
128 self.users.clone()
129 }
130 pub fn locked(&self) -> bool {
131 self.locked
132 }
133 pub fn created_at(&self) -> u64 {
134 self.created_at
135 }
136 pub fn updated_at(&self) -> u64 {
137 self.updated_at
138 }
139 pub fn history(&self) -> Vec<HistoryEntry> {
140 self.history.clone()
141 }
142 pub fn is_authorized(&self) -> bool {
144 let user = &caller().to_text();
145 if user == &self.owner {
146 return true;
147 }
148 if let Some(r) = self.users.get(user) {
151 if r.when.is_none() || r.when.unwrap() <= ic_cdk::api::time() {
152 return true;
153 }
154 } else if let Some(r) = self.users.get(EVERYONE) {
155 if r.when.is_none() || r.when.unwrap() <= ic_cdk::api::time() {
156 return true;
157 }
158 }
159 false
160 }
161 pub fn lock_authorized(&mut self) -> bool {
163 let user = &caller().to_text();
164 if user == &self.owner {
165 let id = self.id;
166 ic_cdk::println!("note not locked with ID {id} as {self:#?} retrieving owner {user}");
167 return true;
168 }
169 if let Some(r) = self.users.get_mut(user) {
172 if r.when.is_none() || r.when.unwrap() <= ic_cdk::api::time() {
173 r.was_read = true;
174 if !self.read_by.contains(user) {
175 self.history.push(HistoryEntry {
176 action: "read".to_string(),
177 user: user.to_string(),
178 labels: if self.locked {
179 vec![]
180 } else {
181 vec!["locked".to_string()]
182 },
183 rule: Some((user.clone(), r.when)),
184 created_at: ic_cdk::api::time(),
185 });
186 }
187 self.locked = true;
188 self.read_by.insert(user.to_string());
189
190 let id = self.id;
191 ic_cdk::println!("locked note with ID {id} as {self:#?} retrieving from {user}");
192
193 return true;
194 }
195 } else if let Some(r) = self.users.get_mut(EVERYONE) {
196 if r.when.is_none() || r.when.unwrap() <= ic_cdk::api::time() {
197 r.was_read = true;
198 if !self.read_by.contains(user) {
199 self.history.push(HistoryEntry {
200 action: "read".to_string(),
201 user: user.to_string(),
202 labels: if self.locked {
203 vec![]
204 } else {
205 vec!["locked".to_string()]
206 },
207 rule: Some((EVERYONE.to_string(), r.when)),
208 created_at: ic_cdk::api::time(),
209 });
210 }
211 self.read_by.insert(user.to_string());
212 self.locked = true;
213
214 let id = self.id;
215 ic_cdk::println!(
216 "locked note with ID {id} as {self:#?} retrieving from {user} as everyone"
217 );
218
219 return true;
220 }
221 }
222 false
223 }
224 pub fn add_reader(&mut self, user: &Option<String>, when: Option<u64>) -> bool {
226 if self.locked && (user.is_none() || self.read_by.contains(&user.clone().unwrap())) {
227 return false;
229 }
230 let user_name = user.clone().unwrap_or_else(|| EVERYONE.to_string());
231 self.history.append(&mut vec![HistoryEntry {
232 action: "share".to_string(),
233 labels: vec![],
234 user: user_name.clone(),
235 rule: Some((user_name.clone(), when)),
236 created_at: ic_cdk::api::time(),
237 }]);
238 self.users.insert(
239 user_name,
240 PrincipalRule {
241 was_read: false,
242 when,
243 },
244 );
245 true
246 }
247 pub fn user_read(&self, user: &String) -> bool {
249 self.read_by.contains(user)
250 }
251 pub fn remove_reader(&mut self, user: &Option<String>) -> bool {
253 if self.locked {
254 if user.iter().any(|u| self.read_by.contains(u)) {
255 return false;
256 } else if let Some(r) = self
257 .users
258 .get(&user.clone().unwrap_or(EVERYONE.to_string()))
259 {
260 if r.was_read {
261 return false;
262 }
263 }
264 }
265 let user_name = user.clone().unwrap_or_else(|| EVERYONE.to_string());
266 if self.users.contains_key(&user_name) {
267 self.users.remove(user_name.as_str());
268 self.history.push(HistoryEntry {
269 action: "unshare".to_string(),
270 labels: vec![],
271 user: user_name.clone(),
272 rule: None,
273 created_at: ic_cdk::api::time(),
274 });
275
276 true
277 } else {
278 false
279 }
280 }
281 pub fn set_data(&mut self, data: String) -> bool {
283 let user = caller().to_text();
284 if self.locked && user != self.owner {
285 return false;
286 }
287 self.data = data;
288 self.updated_at = ic_cdk::api::time();
289 self.history.push(HistoryEntry {
290 action: "updated".to_string(),
291 labels: vec!["data".to_string()],
292 user: user.clone(),
293 rule: None,
294 created_at: ic_cdk::api::time(),
295 });
296 true
297 }
298 pub fn set_encrypted_text(&mut self, encrypted_text: String) -> bool {
299 let user = caller().to_text();
300 if self.locked && user != self.owner {
301 return false;
302 }
303 self.encrypted_text = encrypted_text;
304 self.updated_at = ic_cdk::api::time();
305 self.history.push(HistoryEntry {
306 action: "updated".to_string(),
307 labels: vec!["encrypted_text".to_string()],
308 user: user.clone(),
309 rule: None,
310 created_at: ic_cdk::api::time(),
311 });
312 true
313 }
314 pub fn set_data_and_encrypted_text(&mut self, data: String, encrypted_text: String) -> bool {
315 let user = caller().to_text();
316 if self.locked && user != self.owner {
317 return false;
318 }
319 self.data = data;
320 self.encrypted_text = encrypted_text;
321 self.updated_at = ic_cdk::api::time();
322 self.history.push(HistoryEntry {
323 action: "updated".to_string(),
324 labels: vec!["data".to_string(), "encrypted_text".to_string()],
325 user: user.clone(),
326 rule: None,
327 created_at: ic_cdk::api::time(),
328 });
329 true
330 }
331 pub fn is_shared(&self) -> bool {
333 !self.users.is_empty()
334 }
335 pub fn is_locked(&self) -> bool {
337 self.locked
338 }
339}
340
341impl Storable for EncryptedNote {
342 fn to_bytes(&self) -> Cow<[u8]> {
343 Cow::Owned(Encode!(self).unwrap())
344 }
345 fn from_bytes(bytes: Cow<[u8]>) -> Self {
346 Decode!(bytes.as_ref(), Self).unwrap()
347 }
348 const BOUND: Bound = Bound::Unbounded;
349}
350
351fn caller() -> Principal {
356 let caller = ic_cdk::caller();
357 if caller == Principal::anonymous() {
360 panic!("Anonymous principal not allowed to make calls.")
361 }
362 caller
363}
364
365mod vetkd_types;
366
367pub const VETKD_SYSTEM_API_CANISTER_ID: &str = "nn664-2iaaa-aaaao-a3tqq-cai";
368
369use vetkd_types::{
370 CanisterId, VetKDCurve, VetKDEncryptedKeyReply, VetKDEncryptedKeyRequest, VetKDKeyId,
371 VetKDPublicKeyReply, VetKDPublicKeyRequest,
372};
373
374pub async fn symmetric_key_verification_key_for_note() -> String {
375 let request = VetKDPublicKeyRequest {
376 canister_id: None,
377 derivation_path: vec![b"note_symmetric_key".to_vec()],
378 key_id: bls12_381_test_key_1(),
379 };
380
381 let (response,): (VetKDPublicKeyReply,) = ic_cdk::call(
382 vetkd_system_api_canister_id(),
383 "vetkd_public_key",
384 (request,),
385 )
386 .await
387 .expect("call to vetkd_public_key failed");
388
389 hex::encode(response.public_key)
390}
391
392pub async fn encrypted_symmetric_key_for_note(
396 note_id: NoteId,
397 owner: &String,
398 encryption_public_key: Vec<u8>,
399) -> String {
400 let request = VetKDEncryptedKeyRequest {
401 derivation_id: {
402 let mut buf = vec![];
403 buf.extend_from_slice(¬e_id.to_be_bytes()); buf.extend_from_slice(owner.as_bytes());
405 buf },
407 public_key_derivation_path: vec![b"note_symmetric_key".to_vec()],
408 key_id: bls12_381_test_key_1(),
409 encryption_public_key,
410 };
411
412 let (response,): (VetKDEncryptedKeyReply,) = ic_cdk::call(
413 vetkd_system_api_canister_id(),
414 "vetkd_derive_encrypted_key",
415 (request,),
416 )
417 .await
418 .expect("call to vetkd_derive_encrypted_key failed");
419
420 hex::encode(response.encrypted_key)
421}
422
423fn bls12_381_test_key_1() -> VetKDKeyId {
424 VetKDKeyId {
425 curve: VetKDCurve::Bls12_381,
426 name: "test_key_1".to_string(),
427 }
428}
429
430pub fn vetkd_system_api_canister_id() -> CanisterId {
431 use std::str::FromStr;
432 CanisterId::from_str(VETKD_SYSTEM_API_CANISTER_ID).expect("failed to create canister ID")
433}