1use crate::room_state::member::MemberId;
2use crate::room_state::privacy::{RoomCipherSpec, SecretVersion};
3use crate::room_state::ChatRoomParametersV1;
4use crate::util::{sign_struct, verify_struct};
5use crate::ChatRoomStateV1;
6use ed25519_dalek::{Signature, SigningKey, VerifyingKey};
7use freenet_scaffold::ComposableState;
8use serde::{Deserialize, Serialize};
9use std::collections::{BTreeSet, HashMap, HashSet};
10use std::time::SystemTime;
11
12#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
14pub struct RoomSecretsV1 {
15 pub current_version: SecretVersion,
16 pub versions: Vec<AuthorizedSecretVersionRecord>,
17 pub encrypted_secrets: Vec<AuthorizedEncryptedSecretForMember>,
18}
19
20impl ComposableState for RoomSecretsV1 {
21 type ParentState = ChatRoomStateV1;
22 type Summary = SecretsSummary;
23 type Delta = SecretsDelta;
24 type Parameters = ChatRoomParametersV1;
25
26 fn verify(
27 &self,
28 _parent_state: &Self::ParentState,
29 parameters: &Self::Parameters,
30 ) -> Result<(), String> {
31 for version_record in &self.versions {
33 version_record
34 .verify_signature(¶meters.owner)
35 .map_err(|e| format!("Invalid version record signature: {}", e))?;
36 }
37
38 for encrypted_secret in &self.encrypted_secrets {
40 encrypted_secret
41 .verify_signature(¶meters.owner)
42 .map_err(|e| format!("Invalid encrypted secret signature: {}", e))?;
43 }
44
45 if let Some(max_version) = self.versions.iter().map(|v| v.record.version).max() {
47 if self.current_version != max_version {
48 return Err(format!(
49 "Current version {} does not match maximum version {}",
50 self.current_version, max_version
51 ));
52 }
53 } else if self.current_version != 0 {
54 return Err("Current version is non-zero but no version records exist".to_string());
55 }
56
57 Ok(())
58 }
59
60 fn summarize(
61 &self,
62 _parent_state: &Self::ParentState,
63 _parameters: &Self::Parameters,
64 ) -> Self::Summary {
65 let version_ids: BTreeSet<SecretVersion> =
66 self.versions.iter().map(|v| v.record.version).collect();
67
68 let member_secrets: BTreeSet<(SecretVersion, MemberId)> = self
69 .encrypted_secrets
70 .iter()
71 .map(|s| (s.secret.secret_version, s.secret.member_id))
72 .collect();
73
74 SecretsSummary {
75 current_version: self.current_version,
76 version_ids,
77 member_secrets,
78 }
79 }
80
81 fn delta(
82 &self,
83 _parent_state: &Self::ParentState,
84 _parameters: &Self::Parameters,
85 old_state_summary: &Self::Summary,
86 ) -> Option<Self::Delta> {
87 let new_versions: Vec<AuthorizedSecretVersionRecord> = self
88 .versions
89 .iter()
90 .filter(|v| !old_state_summary.version_ids.contains(&v.record.version))
91 .cloned()
92 .collect();
93
94 let new_encrypted_secrets: Vec<AuthorizedEncryptedSecretForMember> = self
95 .encrypted_secrets
96 .iter()
97 .filter(|s| {
98 !old_state_summary
99 .member_secrets
100 .contains(&(s.secret.secret_version, s.secret.member_id))
101 })
102 .cloned()
103 .collect();
104
105 if new_versions.is_empty()
106 && new_encrypted_secrets.is_empty()
107 && self.current_version == old_state_summary.current_version
108 {
109 None
110 } else {
111 Some(SecretsDelta {
112 current_version: if self.current_version > old_state_summary.current_version {
113 Some(self.current_version)
114 } else {
115 None
116 },
117 new_versions,
118 new_encrypted_secrets,
119 })
120 }
121 }
122
123 fn apply_delta(
124 &mut self,
125 parent_state: &Self::ParentState,
126 parameters: &Self::Parameters,
127 delta: &Option<Self::Delta>,
128 ) -> Result<(), String> {
129 let mut working = self.clone();
141
142 if let Some(delta) = delta {
143 for version_record in &delta.new_versions {
145 version_record
146 .verify_signature(¶meters.owner)
147 .map_err(|e| format!("Invalid version record signature in delta: {}", e))?;
148
149 if working
151 .versions
152 .iter()
153 .any(|v| v.record.version == version_record.record.version)
154 {
155 return Err(format!(
156 "Duplicate secret version: {}",
157 version_record.record.version
158 ));
159 }
160
161 working.versions.push(version_record.clone());
162 }
163
164 let members_by_id = parent_state.members.members_by_member_id();
166 for encrypted_secret in &delta.new_encrypted_secrets {
167 encrypted_secret
168 .verify_signature(¶meters.owner)
169 .map_err(|e| format!("Invalid encrypted secret signature in delta: {}", e))?;
170
171 let member_id = encrypted_secret.secret.member_id;
172
173 if member_id != parameters.owner_id() && !members_by_id.contains_key(&member_id) {
175 continue;
176 }
177
178 if !working
182 .versions
183 .iter()
184 .any(|v| v.record.version == encrypted_secret.secret.secret_version)
185 {
186 return Err(format!(
187 "Encrypted secret references non-existent version: {}",
188 encrypted_secret.secret.secret_version
189 ));
190 }
191
192 if working.encrypted_secrets.iter().any(|s| {
194 s.secret.secret_version == encrypted_secret.secret.secret_version
195 && s.secret.member_id == member_id
196 }) {
197 return Err(format!(
198 "Duplicate encrypted secret for member {:?} version {}",
199 member_id, encrypted_secret.secret.secret_version
200 ));
201 }
202
203 working.encrypted_secrets.push(encrypted_secret.clone());
204 }
205
206 if let Some(new_version) = delta.current_version {
208 if new_version <= working.current_version {
209 return Err(format!(
210 "New current version {} must be greater than existing version {}",
211 new_version, working.current_version
212 ));
213 }
214
215 if !working
217 .versions
218 .iter()
219 .any(|v| v.record.version == new_version)
220 {
221 return Err(format!(
222 "Cannot set current version to non-existent version: {}",
223 new_version
224 ));
225 }
226
227 working.current_version = new_version;
228 }
229
230 let owner_id = parameters.owner_id();
232 working.encrypted_secrets.retain(|s| {
233 s.secret.member_id == owner_id || members_by_id.contains_key(&s.secret.member_id)
234 });
235 }
236
237 working.versions.sort_by_key(|v| v.record.version);
239 working
240 .encrypted_secrets
241 .sort_by_key(|s| (s.secret.secret_version, s.secret.member_id));
242
243 *self = working;
245 Ok(())
246 }
247}
248
249#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
251pub struct SecretsSummary {
252 pub current_version: SecretVersion,
253 pub version_ids: BTreeSet<SecretVersion>,
260 pub member_secrets: BTreeSet<(SecretVersion, MemberId)>,
261}
262
263#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
265pub struct SecretsDelta {
266 pub current_version: Option<SecretVersion>,
267 pub new_versions: Vec<AuthorizedSecretVersionRecord>,
268 pub new_encrypted_secrets: Vec<AuthorizedEncryptedSecretForMember>,
269}
270
271#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
273pub struct SecretVersionRecordV1 {
274 pub version: SecretVersion,
275 pub cipher_spec: RoomCipherSpec,
276 pub created_at: SystemTime,
277}
278
279#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
281pub struct AuthorizedSecretVersionRecord {
282 pub record: SecretVersionRecordV1,
283 pub owner_signature: Signature,
284}
285
286impl AuthorizedSecretVersionRecord {
287 pub fn new(record: SecretVersionRecordV1, owner_signing_key: &SigningKey) -> Self {
288 let signature = sign_struct(&record, owner_signing_key);
289 Self {
290 record,
291 owner_signature: signature,
292 }
293 }
294
295 pub fn with_signature(record: SecretVersionRecordV1, owner_signature: Signature) -> Self {
298 Self {
299 record,
300 owner_signature,
301 }
302 }
303
304 pub fn verify_signature(&self, owner_verifying_key: &VerifyingKey) -> Result<(), String> {
305 verify_struct(&self.record, &self.owner_signature, owner_verifying_key)
306 .map_err(|e| format!("Invalid signature: {}", e))
307 }
308}
309
310#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
312pub struct EncryptedSecretForMemberV1 {
313 pub member_id: MemberId,
314 pub secret_version: SecretVersion,
315 pub ciphertext: Vec<u8>,
316 pub nonce: [u8; 12],
317 pub sender_ephemeral_public_key: [u8; 32],
318 pub provider: MemberId,
319}
320
321#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
323pub struct AuthorizedEncryptedSecretForMember {
324 pub secret: EncryptedSecretForMemberV1,
325 pub owner_signature: Signature,
326}
327
328impl AuthorizedEncryptedSecretForMember {
329 pub fn new(secret: EncryptedSecretForMemberV1, owner_signing_key: &SigningKey) -> Self {
330 let signature = sign_struct(&secret, owner_signing_key);
331 Self {
332 secret,
333 owner_signature: signature,
334 }
335 }
336
337 pub fn with_signature(secret: EncryptedSecretForMemberV1, owner_signature: Signature) -> Self {
340 Self {
341 secret,
342 owner_signature,
343 }
344 }
345
346 pub fn verify_signature(&self, owner_verifying_key: &VerifyingKey) -> Result<(), String> {
347 verify_struct(&self.secret, &self.owner_signature, owner_verifying_key)
348 .map_err(|e| format!("Invalid signature: {}", e))
349 }
350}
351
352#[cfg(feature = "ecies")]
392#[allow(clippy::too_many_arguments)]
393pub fn build_rotation_encrypted_secrets(
394 signing_key: &SigningKey,
395 owner_vk: &VerifyingKey,
396 owner_id: MemberId,
397 new_version: SecretVersion,
398 new_secret: &[u8; 32],
399 current_members_with_vks: &[(MemberId, VerifyingKey)],
400 existing_encrypted_secrets: &[AuthorizedEncryptedSecretForMember],
401) -> Result<Vec<AuthorizedEncryptedSecretForMember>, String> {
402 use crate::ecies::{decrypt_secret_from_member_blob_raw, encrypt_secret_for_member};
403 use std::collections::{BTreeMap, BTreeSet};
404
405 let existing: BTreeSet<(MemberId, SecretVersion)> = existing_encrypted_secrets
407 .iter()
408 .map(|s| (s.secret.member_id, s.secret.secret_version))
409 .collect();
410
411 let mut prior_secrets: BTreeMap<SecretVersion, [u8; 32]> = BTreeMap::new();
415 for blob in existing_encrypted_secrets {
416 if blob.secret.member_id != owner_id {
417 continue;
418 }
419 if blob.secret.secret_version >= new_version {
420 continue;
421 }
422 if prior_secrets.contains_key(&blob.secret.secret_version) {
423 eprintln!(
434 "warn(build_rotation_encrypted_secrets): duplicate owner blob \
435 at version {} (first-wins applied); contract should have \
436 dedup'd (member, version) — investigate",
437 blob.secret.secret_version
438 );
439 continue;
440 }
441 if let Ok(s) = decrypt_secret_from_member_blob_raw(
442 &blob.secret.ciphertext,
443 &blob.secret.nonce,
444 &blob.secret.sender_ephemeral_public_key,
445 signing_key,
446 ) {
447 prior_secrets.insert(blob.secret.secret_version, s);
448 }
449 }
450 prior_secrets.insert(new_version, *new_secret);
452
453 let mut out: Vec<AuthorizedEncryptedSecretForMember> = Vec::new();
454
455 let all_members =
457 std::iter::once((owner_id, *owner_vk)).chain(current_members_with_vks.iter().copied());
458
459 for (member_id, member_vk) in all_members {
468 for (&v, secret_for_version) in &prior_secrets {
469 if existing.contains(&(member_id, v)) {
470 continue;
471 }
472 let (ciphertext, nonce, ephemeral_key) =
473 encrypt_secret_for_member(secret_for_version, &member_vk);
474 let secret_struct = EncryptedSecretForMemberV1 {
475 member_id,
476 secret_version: v,
477 ciphertext,
478 nonce,
479 sender_ephemeral_public_key: ephemeral_key.to_bytes(),
480 provider: owner_id,
481 };
482 out.push(AuthorizedEncryptedSecretForMember::new(
483 secret_struct,
484 signing_key,
485 ));
486 }
487 }
488
489 Ok(out)
490}
491
492impl RoomSecretsV1 {
493 pub fn has_complete_distribution(
495 &self,
496 members: &HashMap<MemberId, &crate::room_state::member::AuthorizedMember>,
497 ) -> bool {
498 if self.current_version == 0 {
499 return true; }
501
502 let member_ids_with_current: HashSet<MemberId> = self
503 .encrypted_secrets
504 .iter()
505 .filter(|s| s.secret.secret_version == self.current_version)
506 .map(|s| s.secret.member_id)
507 .collect();
508
509 members
510 .keys()
511 .all(|id| member_ids_with_current.contains(id))
512 }
513}
514
515#[cfg(test)]
516mod tests {
517 use super::*;
518 use crate::room_state::member::{AuthorizedMember, Member};
519 use ed25519_dalek::SigningKey;
520 use rand::rngs::OsRng;
521
522 fn create_test_state_and_params() -> (ChatRoomStateV1, ChatRoomParametersV1, SigningKey) {
523 let owner_signing_key = SigningKey::generate(&mut OsRng);
524 let owner_verifying_key = owner_signing_key.verifying_key();
525
526 let state = ChatRoomStateV1::default();
527 let params = ChatRoomParametersV1 {
528 owner: owner_verifying_key,
529 };
530
531 (state, params, owner_signing_key)
532 }
533
534 fn create_version_record(
535 version: SecretVersion,
536 owner_sk: &SigningKey,
537 ) -> AuthorizedSecretVersionRecord {
538 let record = SecretVersionRecordV1 {
539 version,
540 cipher_spec: RoomCipherSpec::Aes256Gcm,
541 created_at: SystemTime::now(),
542 };
543 AuthorizedSecretVersionRecord::new(record, owner_sk)
544 }
545
546 fn create_encrypted_secret(
547 member_id: MemberId,
548 version: SecretVersion,
549 owner_sk: &SigningKey,
550 ) -> AuthorizedEncryptedSecretForMember {
551 let secret = EncryptedSecretForMemberV1 {
552 member_id,
553 secret_version: version,
554 ciphertext: vec![1, 2, 3, 4],
555 nonce: [0u8; 12],
556 sender_ephemeral_public_key: [0u8; 32],
557 provider: member_id,
558 };
559 AuthorizedEncryptedSecretForMember::new(secret, owner_sk)
560 }
561
562 #[test]
563 fn test_room_secrets_v1_default() {
564 let secrets = RoomSecretsV1::default();
565 assert_eq!(secrets.current_version, 0);
566 assert!(secrets.versions.is_empty());
567 assert!(secrets.encrypted_secrets.is_empty());
568 }
569
570 #[test]
571 fn test_authorized_secret_version_record() {
572 let owner_signing_key = SigningKey::generate(&mut OsRng);
573 let owner_verifying_key = owner_signing_key.verifying_key();
574
575 let record = SecretVersionRecordV1 {
576 version: 1,
577 cipher_spec: RoomCipherSpec::Aes256Gcm,
578 created_at: SystemTime::now(),
579 };
580
581 let authorized_record =
582 AuthorizedSecretVersionRecord::new(record.clone(), &owner_signing_key);
583
584 assert_eq!(authorized_record.record, record);
585 assert!(authorized_record
586 .verify_signature(&owner_verifying_key)
587 .is_ok());
588
589 let wrong_key = SigningKey::generate(&mut OsRng).verifying_key();
591 assert!(authorized_record.verify_signature(&wrong_key).is_err());
592 }
593
594 #[test]
595 fn test_authorized_encrypted_secret_for_member() {
596 let owner_signing_key = SigningKey::generate(&mut OsRng);
597 let owner_verifying_key = owner_signing_key.verifying_key();
598 let member_id = MemberId::from(&owner_verifying_key);
599
600 let secret = EncryptedSecretForMemberV1 {
601 member_id,
602 secret_version: 1,
603 ciphertext: vec![1, 2, 3, 4],
604 nonce: [0u8; 12],
605 sender_ephemeral_public_key: [0u8; 32],
606 provider: member_id,
607 };
608
609 let authorized_secret =
610 AuthorizedEncryptedSecretForMember::new(secret.clone(), &owner_signing_key);
611
612 assert_eq!(authorized_secret.secret, secret);
613 assert!(authorized_secret
614 .verify_signature(&owner_verifying_key)
615 .is_ok());
616
617 let wrong_key = SigningKey::generate(&mut OsRng).verifying_key();
619 assert!(authorized_secret.verify_signature(&wrong_key).is_err());
620 }
621
622 #[test]
627 fn test_verify_empty_state() {
628 let (state, params, _) = create_test_state_and_params();
629 let secrets = RoomSecretsV1::default();
630
631 assert!(secrets.verify(&state, ¶ms).is_ok());
632 }
633
634 #[test]
635 fn test_verify_valid_state_with_version() {
636 let (state, params, owner_sk) = create_test_state_and_params();
637 let owner_id = params.owner_id();
638
639 let mut secrets = RoomSecretsV1 {
640 current_version: 1,
641 ..Default::default()
642 };
643 secrets.versions.push(create_version_record(1, &owner_sk));
644 secrets
645 .encrypted_secrets
646 .push(create_encrypted_secret(owner_id, 1, &owner_sk));
647
648 assert!(secrets.verify(&state, ¶ms).is_ok());
649 }
650
651 #[test]
652 fn test_verify_fails_with_invalid_version_signature() {
653 let (state, params, _owner_sk) = create_test_state_and_params();
654 let wrong_sk = SigningKey::generate(&mut OsRng);
655
656 let mut secrets = RoomSecretsV1 {
657 current_version: 1,
658 ..Default::default()
659 };
660 secrets.versions.push(create_version_record(1, &wrong_sk)); let result = secrets.verify(&state, ¶ms);
663 assert!(result.is_err());
664 assert!(result
665 .unwrap_err()
666 .contains("Invalid version record signature"));
667 }
668
669 #[test]
670 fn test_verify_fails_with_invalid_secret_signature() {
671 let (state, params, owner_sk) = create_test_state_and_params();
672 let owner_id = params.owner_id();
673 let wrong_sk = SigningKey::generate(&mut OsRng);
674
675 let mut secrets = RoomSecretsV1 {
676 current_version: 1,
677 ..Default::default()
678 };
679 secrets.versions.push(create_version_record(1, &owner_sk));
680 secrets
681 .encrypted_secrets
682 .push(create_encrypted_secret(owner_id, 1, &wrong_sk)); let result = secrets.verify(&state, ¶ms);
685 assert!(result.is_err());
686 assert!(result
687 .unwrap_err()
688 .contains("Invalid encrypted secret signature"));
689 }
690
691 #[test]
692 fn test_verify_fails_with_mismatched_current_version() {
693 let (state, params, owner_sk) = create_test_state_and_params();
694
695 let mut secrets = RoomSecretsV1 {
696 current_version: 2,
697 ..Default::default()
698 }; secrets.versions.push(create_version_record(1, &owner_sk));
700
701 let result = secrets.verify(&state, ¶ms);
702 assert!(result.is_err());
703 assert!(result
704 .unwrap_err()
705 .contains("does not match maximum version"));
706 }
707
708 #[test]
709 fn test_verify_fails_with_nonzero_current_but_no_versions() {
710 let (state, params, _) = create_test_state_and_params();
711
712 let secrets = RoomSecretsV1 {
713 current_version: 1,
714 ..Default::default()
715 };
716 let result = secrets.verify(&state, ¶ms);
719 assert!(result.is_err());
720 assert!(result.unwrap_err().contains("no version records exist"));
721 }
722
723 #[test]
724 fn test_summarize_empty_state() {
725 let (state, params, _) = create_test_state_and_params();
726 let secrets = RoomSecretsV1::default();
727
728 let summary = secrets.summarize(&state, ¶ms);
729 assert_eq!(summary.current_version, 0);
730 assert!(summary.version_ids.is_empty());
731 assert!(summary.member_secrets.is_empty());
732 }
733
734 #[test]
735 fn test_summarize_with_data() {
736 let (state, params, owner_sk) = create_test_state_and_params();
737 let owner_id = params.owner_id();
738
739 let mut secrets = RoomSecretsV1 {
740 current_version: 2,
741 ..Default::default()
742 };
743 secrets.versions.push(create_version_record(1, &owner_sk));
744 secrets.versions.push(create_version_record(2, &owner_sk));
745 secrets
746 .encrypted_secrets
747 .push(create_encrypted_secret(owner_id, 1, &owner_sk));
748 secrets
749 .encrypted_secrets
750 .push(create_encrypted_secret(owner_id, 2, &owner_sk));
751
752 let summary = secrets.summarize(&state, ¶ms);
753 assert_eq!(summary.current_version, 2);
754 assert_eq!(summary.version_ids.len(), 2);
755 assert!(summary.version_ids.contains(&1));
756 assert!(summary.version_ids.contains(&2));
757 assert_eq!(summary.member_secrets.len(), 2);
758 assert!(summary.member_secrets.contains(&(1, owner_id)));
759 assert!(summary.member_secrets.contains(&(2, owner_id)));
760 }
761
762 #[test]
763 fn test_delta_no_changes() {
764 let (state, params, _) = create_test_state_and_params();
765 let secrets = RoomSecretsV1::default();
766 let summary = secrets.summarize(&state, ¶ms);
767
768 let delta = secrets.delta(&state, ¶ms, &summary);
769 assert!(delta.is_none());
770 }
771
772 #[test]
773 fn test_delta_new_version() {
774 let (state, params, owner_sk) = create_test_state_and_params();
775 let owner_id = params.owner_id();
776
777 let mut secrets = RoomSecretsV1 {
778 current_version: 1,
779 ..Default::default()
780 };
781 secrets.versions.push(create_version_record(1, &owner_sk));
782 secrets
783 .encrypted_secrets
784 .push(create_encrypted_secret(owner_id, 1, &owner_sk));
785
786 let old_summary = SecretsSummary {
787 current_version: 0,
788 version_ids: BTreeSet::new(),
789 member_secrets: BTreeSet::new(),
790 };
791
792 let delta = secrets.delta(&state, ¶ms, &old_summary).unwrap();
793 assert_eq!(delta.current_version, Some(1));
794 assert_eq!(delta.new_versions.len(), 1);
795 assert_eq!(delta.new_encrypted_secrets.len(), 1);
796 }
797
798 #[test]
799 fn test_delta_partial_update() {
800 let (state, params, owner_sk) = create_test_state_and_params();
801 let owner_id = params.owner_id();
802
803 let mut secrets = RoomSecretsV1 {
804 current_version: 2,
805 ..Default::default()
806 };
807 secrets.versions.push(create_version_record(1, &owner_sk));
808 secrets.versions.push(create_version_record(2, &owner_sk));
809 secrets
810 .encrypted_secrets
811 .push(create_encrypted_secret(owner_id, 1, &owner_sk));
812 secrets
813 .encrypted_secrets
814 .push(create_encrypted_secret(owner_id, 2, &owner_sk));
815
816 let mut old_summary = SecretsSummary {
817 current_version: 1,
818 version_ids: BTreeSet::new(),
819 member_secrets: BTreeSet::new(),
820 };
821 old_summary.version_ids.insert(1);
822 old_summary.member_secrets.insert((1, owner_id));
823
824 let delta = secrets.delta(&state, ¶ms, &old_summary).unwrap();
825 assert_eq!(delta.current_version, Some(2));
826 assert_eq!(delta.new_versions.len(), 1);
827 assert_eq!(delta.new_versions[0].record.version, 2);
828 assert_eq!(delta.new_encrypted_secrets.len(), 1);
829 assert_eq!(delta.new_encrypted_secrets[0].secret.secret_version, 2);
830 }
831
832 #[test]
833 fn test_apply_delta_add_first_version() {
834 let (state, params, owner_sk) = create_test_state_and_params();
835 let owner_id = params.owner_id();
836
837 let mut secrets = RoomSecretsV1::default();
838
839 let delta = SecretsDelta {
840 current_version: Some(1),
841 new_versions: vec![create_version_record(1, &owner_sk)],
842 new_encrypted_secrets: vec![create_encrypted_secret(owner_id, 1, &owner_sk)],
843 };
844
845 let result = secrets.apply_delta(&state, ¶ms, &Some(delta));
846 assert!(result.is_ok(), "Failed: {:?}", result.err());
847 assert_eq!(secrets.current_version, 1);
848 assert_eq!(secrets.versions.len(), 1);
849 assert_eq!(secrets.encrypted_secrets.len(), 1);
850 }
851
852 #[test]
853 fn test_apply_delta_rejects_duplicate_version() {
854 let (state, params, owner_sk) = create_test_state_and_params();
855
856 let mut secrets = RoomSecretsV1 {
857 current_version: 1,
858 ..Default::default()
859 };
860 secrets.versions.push(create_version_record(1, &owner_sk));
861
862 let delta = SecretsDelta {
863 current_version: None,
864 new_versions: vec![create_version_record(1, &owner_sk)], new_encrypted_secrets: vec![],
866 };
867
868 let result = secrets.apply_delta(&state, ¶ms, &Some(delta));
869 assert!(result.is_err());
870 assert!(result.unwrap_err().contains("Duplicate secret version"));
871 }
872
873 #[test]
874 fn test_apply_delta_skips_secret_for_nonexistent_member() {
875 let (state, params, owner_sk) = create_test_state_and_params();
876 let fake_member_id = MemberId::from(&SigningKey::generate(&mut OsRng).verifying_key());
877
878 let mut secrets = RoomSecretsV1 {
879 current_version: 1,
880 ..Default::default()
881 };
882 secrets.versions.push(create_version_record(1, &owner_sk));
883
884 let delta = SecretsDelta {
885 current_version: None,
886 new_versions: vec![],
887 new_encrypted_secrets: vec![create_encrypted_secret(fake_member_id, 1, &owner_sk)],
888 };
889
890 let result = secrets.apply_delta(&state, ¶ms, &Some(delta));
892 assert!(
893 result.is_ok(),
894 "Should skip non-existent member, got: {:?}",
895 result.err()
896 );
897 assert!(
899 !secrets
900 .encrypted_secrets
901 .iter()
902 .any(|s| s.secret.member_id == fake_member_id),
903 "Secret for non-existent member should not be added"
904 );
905 }
906
907 #[test]
908 fn test_apply_delta_rejects_secret_for_nonexistent_version() {
909 let (state, params, owner_sk) = create_test_state_and_params();
910 let owner_id = params.owner_id();
911
912 let mut secrets = RoomSecretsV1::default();
913
914 let delta = SecretsDelta {
915 current_version: None,
916 new_versions: vec![],
917 new_encrypted_secrets: vec![create_encrypted_secret(owner_id, 99, &owner_sk)], };
919
920 let result = secrets.apply_delta(&state, ¶ms, &Some(delta));
921 assert!(result.is_err());
922 assert!(result.unwrap_err().contains("non-existent version"));
923 }
924
925 #[test]
926 fn test_apply_delta_rejects_duplicate_member_secret() {
927 let (state, params, owner_sk) = create_test_state_and_params();
928 let owner_id = params.owner_id();
929
930 let mut secrets = RoomSecretsV1 {
931 current_version: 1,
932 ..Default::default()
933 };
934 secrets.versions.push(create_version_record(1, &owner_sk));
935 secrets
936 .encrypted_secrets
937 .push(create_encrypted_secret(owner_id, 1, &owner_sk));
938
939 let delta = SecretsDelta {
940 current_version: None,
941 new_versions: vec![],
942 new_encrypted_secrets: vec![create_encrypted_secret(owner_id, 1, &owner_sk)], };
944
945 let result = secrets.apply_delta(&state, ¶ms, &Some(delta));
946 assert!(result.is_err());
947 assert!(result.unwrap_err().contains("Duplicate encrypted secret"));
948 }
949
950 #[test]
951 fn test_apply_delta_rejects_invalid_version_transition() {
952 let (state, params, owner_sk) = create_test_state_and_params();
953
954 let mut secrets = RoomSecretsV1 {
955 current_version: 2,
956 ..Default::default()
957 };
958 secrets.versions.push(create_version_record(1, &owner_sk));
959 secrets.versions.push(create_version_record(2, &owner_sk));
960
961 let delta = SecretsDelta {
962 current_version: Some(1), new_versions: vec![],
964 new_encrypted_secrets: vec![],
965 };
966
967 let result = secrets.apply_delta(&state, ¶ms, &Some(delta));
968 assert!(result.is_err());
969 assert!(result
970 .unwrap_err()
971 .contains("must be greater than existing version"));
972 }
973
974 #[test]
975 fn test_apply_delta_rejects_nonexistent_current_version() {
976 let (state, params, _owner_sk) = create_test_state_and_params();
977
978 let mut secrets = RoomSecretsV1::default();
979
980 let delta = SecretsDelta {
981 current_version: Some(99), new_versions: vec![],
983 new_encrypted_secrets: vec![],
984 };
985
986 let result = secrets.apply_delta(&state, ¶ms, &Some(delta));
987 assert!(result.is_err());
988 assert!(result.unwrap_err().contains("non-existent version"));
989 }
990
991 #[test]
992 fn test_apply_delta_prunes_removed_member_secrets() {
993 let (mut state, params, owner_sk) = create_test_state_and_params();
994 let owner_id = params.owner_id();
995
996 let member_sk = SigningKey::generate(&mut OsRng);
998 let member_vk = member_sk.verifying_key();
999 let member_id = MemberId::from(&member_vk);
1000
1001 let member = Member {
1002 owner_member_id: owner_id,
1003 invited_by: owner_id,
1004 member_vk,
1005 };
1006 let auth_member = AuthorizedMember::new(member, &owner_sk);
1007 state.members.members.push(auth_member);
1008
1009 let mut secrets = RoomSecretsV1 {
1011 current_version: 1,
1012 ..Default::default()
1013 };
1014 secrets.versions.push(create_version_record(1, &owner_sk));
1015 secrets
1016 .encrypted_secrets
1017 .push(create_encrypted_secret(owner_id, 1, &owner_sk));
1018 secrets
1019 .encrypted_secrets
1020 .push(create_encrypted_secret(member_id, 1, &owner_sk));
1021
1022 assert_eq!(secrets.encrypted_secrets.len(), 2);
1023
1024 state.members.members.clear();
1026
1027 let delta = SecretsDelta {
1029 current_version: None,
1030 new_versions: vec![],
1031 new_encrypted_secrets: vec![],
1032 };
1033
1034 let result = secrets.apply_delta(&state, ¶ms, &Some(delta));
1035 assert!(result.is_ok());
1036
1037 assert_eq!(secrets.encrypted_secrets.len(), 1);
1039 assert_eq!(secrets.encrypted_secrets[0].secret.member_id, owner_id);
1040 }
1041
1042 #[test]
1043 fn test_has_complete_distribution_empty() {
1044 let secrets = RoomSecretsV1::default();
1045 let members = HashMap::new();
1046
1047 assert!(secrets.has_complete_distribution(&members));
1048 }
1049
1050 #[test]
1051 fn test_has_complete_distribution_complete() {
1052 let (_state, params, owner_sk) = create_test_state_and_params();
1053 let owner_id = params.owner_id();
1054
1055 let mut secrets = RoomSecretsV1 {
1056 current_version: 1,
1057 ..Default::default()
1058 };
1059 secrets.versions.push(create_version_record(1, &owner_sk));
1060 secrets
1061 .encrypted_secrets
1062 .push(create_encrypted_secret(owner_id, 1, &owner_sk));
1063
1064 let member = Member {
1065 owner_member_id: owner_id,
1066 invited_by: owner_id,
1067 member_vk: params.owner,
1068 };
1069 let auth_member = AuthorizedMember::new(member, &owner_sk);
1070
1071 let mut members = HashMap::new();
1072 members.insert(owner_id, &auth_member);
1073
1074 assert!(secrets.has_complete_distribution(&members));
1075 }
1076
1077 #[test]
1078 fn test_has_complete_distribution_incomplete() {
1079 let (_state, params, owner_sk) = create_test_state_and_params();
1080 let owner_id = params.owner_id();
1081
1082 let member_sk = SigningKey::generate(&mut OsRng);
1083 let member_vk = member_sk.verifying_key();
1084 let member_id = MemberId::from(&member_vk);
1085
1086 let mut secrets = RoomSecretsV1 {
1087 current_version: 1,
1088 ..Default::default()
1089 };
1090 secrets.versions.push(create_version_record(1, &owner_sk));
1091 secrets
1092 .encrypted_secrets
1093 .push(create_encrypted_secret(owner_id, 1, &owner_sk));
1094 let member = Member {
1097 owner_member_id: owner_id,
1098 invited_by: owner_id,
1099 member_vk,
1100 };
1101 let auth_member = AuthorizedMember::new(member, &owner_sk);
1102
1103 let mut members = HashMap::new();
1104 members.insert(member_id, &auth_member);
1105
1106 assert!(!secrets.has_complete_distribution(&members));
1107 }
1108
1109 #[test]
1113 fn test_apply_delta_with_removed_member_secret() {
1114 let (mut state, params, owner_sk) = create_test_state_and_params();
1115 let owner_id = params.owner_id();
1116
1117 let member_sk = SigningKey::generate(&mut OsRng);
1119 let member_vk = member_sk.verifying_key();
1120 let member_id = MemberId::from(&member_vk);
1121
1122 let member = Member {
1123 owner_member_id: owner_id,
1124 invited_by: owner_id,
1125 member_vk,
1126 };
1127 let auth_member = AuthorizedMember::new(member, &owner_sk);
1128 state.members.members.push(auth_member);
1129
1130 let mut secrets = RoomSecretsV1 {
1132 current_version: 1,
1133 ..Default::default()
1134 };
1135 secrets.versions.push(create_version_record(1, &owner_sk));
1136 secrets
1137 .encrypted_secrets
1138 .push(create_encrypted_secret(owner_id, 1, &owner_sk));
1139 secrets
1140 .encrypted_secrets
1141 .push(create_encrypted_secret(member_id, 1, &owner_sk));
1142
1143 state.members.members.clear();
1145
1146 let delta = SecretsDelta {
1148 current_version: Some(2),
1149 new_versions: vec![create_version_record(2, &owner_sk)],
1150 new_encrypted_secrets: vec![
1151 create_encrypted_secret(owner_id, 2, &owner_sk),
1152 create_encrypted_secret(member_id, 2, &owner_sk), ],
1154 };
1155
1156 let result = secrets.apply_delta(&state, ¶ms, &Some(delta));
1158 assert!(
1159 result.is_ok(),
1160 "apply_delta should skip removed member's secret, got: {:?}",
1161 result.err()
1162 );
1163
1164 assert!(
1166 !secrets
1167 .encrypted_secrets
1168 .iter()
1169 .any(|s| s.secret.member_id == member_id),
1170 "Removed member's secrets should be pruned"
1171 );
1172
1173 assert!(
1175 secrets
1176 .encrypted_secrets
1177 .iter()
1178 .any(|s| s.secret.member_id == owner_id && s.secret.secret_version == 2),
1179 "Owner's new secret should be present"
1180 );
1181 }
1182}