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::{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: HashSet<SecretVersion> =
66 self.versions.iter().map(|v| v.record.version).collect();
67
68 let member_secrets: HashSet<(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: HashSet<SecretVersion>,
254 pub member_secrets: HashSet<(SecretVersion, MemberId)>,
255}
256
257#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
259pub struct SecretsDelta {
260 pub current_version: Option<SecretVersion>,
261 pub new_versions: Vec<AuthorizedSecretVersionRecord>,
262 pub new_encrypted_secrets: Vec<AuthorizedEncryptedSecretForMember>,
263}
264
265#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
267pub struct SecretVersionRecordV1 {
268 pub version: SecretVersion,
269 pub cipher_spec: RoomCipherSpec,
270 pub created_at: SystemTime,
271}
272
273#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
275pub struct AuthorizedSecretVersionRecord {
276 pub record: SecretVersionRecordV1,
277 pub owner_signature: Signature,
278}
279
280impl AuthorizedSecretVersionRecord {
281 pub fn new(record: SecretVersionRecordV1, owner_signing_key: &SigningKey) -> Self {
282 let signature = sign_struct(&record, owner_signing_key);
283 Self {
284 record,
285 owner_signature: signature,
286 }
287 }
288
289 pub fn with_signature(record: SecretVersionRecordV1, owner_signature: Signature) -> Self {
292 Self {
293 record,
294 owner_signature,
295 }
296 }
297
298 pub fn verify_signature(&self, owner_verifying_key: &VerifyingKey) -> Result<(), String> {
299 verify_struct(&self.record, &self.owner_signature, owner_verifying_key)
300 .map_err(|e| format!("Invalid signature: {}", e))
301 }
302}
303
304#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
306pub struct EncryptedSecretForMemberV1 {
307 pub member_id: MemberId,
308 pub secret_version: SecretVersion,
309 pub ciphertext: Vec<u8>,
310 pub nonce: [u8; 12],
311 pub sender_ephemeral_public_key: [u8; 32],
312 pub provider: MemberId,
313}
314
315#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
317pub struct AuthorizedEncryptedSecretForMember {
318 pub secret: EncryptedSecretForMemberV1,
319 pub owner_signature: Signature,
320}
321
322impl AuthorizedEncryptedSecretForMember {
323 pub fn new(secret: EncryptedSecretForMemberV1, owner_signing_key: &SigningKey) -> Self {
324 let signature = sign_struct(&secret, owner_signing_key);
325 Self {
326 secret,
327 owner_signature: signature,
328 }
329 }
330
331 pub fn with_signature(secret: EncryptedSecretForMemberV1, owner_signature: Signature) -> Self {
334 Self {
335 secret,
336 owner_signature,
337 }
338 }
339
340 pub fn verify_signature(&self, owner_verifying_key: &VerifyingKey) -> Result<(), String> {
341 verify_struct(&self.secret, &self.owner_signature, owner_verifying_key)
342 .map_err(|e| format!("Invalid signature: {}", e))
343 }
344}
345
346#[cfg(feature = "ecies")]
386#[allow(clippy::too_many_arguments)]
387pub fn build_rotation_encrypted_secrets(
388 signing_key: &SigningKey,
389 owner_vk: &VerifyingKey,
390 owner_id: MemberId,
391 new_version: SecretVersion,
392 new_secret: &[u8; 32],
393 current_members_with_vks: &[(MemberId, VerifyingKey)],
394 existing_encrypted_secrets: &[AuthorizedEncryptedSecretForMember],
395) -> Result<Vec<AuthorizedEncryptedSecretForMember>, String> {
396 use crate::ecies::{decrypt_secret_from_member_blob_raw, encrypt_secret_for_member};
397 use std::collections::{BTreeMap, BTreeSet};
398
399 let existing: BTreeSet<(MemberId, SecretVersion)> = existing_encrypted_secrets
401 .iter()
402 .map(|s| (s.secret.member_id, s.secret.secret_version))
403 .collect();
404
405 let mut prior_secrets: BTreeMap<SecretVersion, [u8; 32]> = BTreeMap::new();
409 for blob in existing_encrypted_secrets {
410 if blob.secret.member_id != owner_id {
411 continue;
412 }
413 if blob.secret.secret_version >= new_version {
414 continue;
415 }
416 if prior_secrets.contains_key(&blob.secret.secret_version) {
417 eprintln!(
428 "warn(build_rotation_encrypted_secrets): duplicate owner blob \
429 at version {} (first-wins applied); contract should have \
430 dedup'd (member, version) — investigate",
431 blob.secret.secret_version
432 );
433 continue;
434 }
435 if let Ok(s) = decrypt_secret_from_member_blob_raw(
436 &blob.secret.ciphertext,
437 &blob.secret.nonce,
438 &blob.secret.sender_ephemeral_public_key,
439 signing_key,
440 ) {
441 prior_secrets.insert(blob.secret.secret_version, s);
442 }
443 }
444 prior_secrets.insert(new_version, *new_secret);
446
447 let mut out: Vec<AuthorizedEncryptedSecretForMember> = Vec::new();
448
449 let all_members =
451 std::iter::once((owner_id, *owner_vk)).chain(current_members_with_vks.iter().copied());
452
453 for (member_id, member_vk) in all_members {
462 for (&v, secret_for_version) in &prior_secrets {
463 if existing.contains(&(member_id, v)) {
464 continue;
465 }
466 let (ciphertext, nonce, ephemeral_key) =
467 encrypt_secret_for_member(secret_for_version, &member_vk);
468 let secret_struct = EncryptedSecretForMemberV1 {
469 member_id,
470 secret_version: v,
471 ciphertext,
472 nonce,
473 sender_ephemeral_public_key: ephemeral_key.to_bytes(),
474 provider: owner_id,
475 };
476 out.push(AuthorizedEncryptedSecretForMember::new(
477 secret_struct,
478 signing_key,
479 ));
480 }
481 }
482
483 Ok(out)
484}
485
486impl RoomSecretsV1 {
487 pub fn has_complete_distribution(
489 &self,
490 members: &HashMap<MemberId, &crate::room_state::member::AuthorizedMember>,
491 ) -> bool {
492 if self.current_version == 0 {
493 return true; }
495
496 let member_ids_with_current: HashSet<MemberId> = self
497 .encrypted_secrets
498 .iter()
499 .filter(|s| s.secret.secret_version == self.current_version)
500 .map(|s| s.secret.member_id)
501 .collect();
502
503 members
504 .keys()
505 .all(|id| member_ids_with_current.contains(id))
506 }
507}
508
509#[cfg(test)]
510mod tests {
511 use super::*;
512 use crate::room_state::member::{AuthorizedMember, Member};
513 use ed25519_dalek::SigningKey;
514 use rand::rngs::OsRng;
515
516 fn create_test_state_and_params() -> (ChatRoomStateV1, ChatRoomParametersV1, SigningKey) {
517 let owner_signing_key = SigningKey::generate(&mut OsRng);
518 let owner_verifying_key = owner_signing_key.verifying_key();
519
520 let state = ChatRoomStateV1::default();
521 let params = ChatRoomParametersV1 {
522 owner: owner_verifying_key,
523 };
524
525 (state, params, owner_signing_key)
526 }
527
528 fn create_version_record(
529 version: SecretVersion,
530 owner_sk: &SigningKey,
531 ) -> AuthorizedSecretVersionRecord {
532 let record = SecretVersionRecordV1 {
533 version,
534 cipher_spec: RoomCipherSpec::Aes256Gcm,
535 created_at: SystemTime::now(),
536 };
537 AuthorizedSecretVersionRecord::new(record, owner_sk)
538 }
539
540 fn create_encrypted_secret(
541 member_id: MemberId,
542 version: SecretVersion,
543 owner_sk: &SigningKey,
544 ) -> AuthorizedEncryptedSecretForMember {
545 let secret = EncryptedSecretForMemberV1 {
546 member_id,
547 secret_version: version,
548 ciphertext: vec![1, 2, 3, 4],
549 nonce: [0u8; 12],
550 sender_ephemeral_public_key: [0u8; 32],
551 provider: member_id,
552 };
553 AuthorizedEncryptedSecretForMember::new(secret, owner_sk)
554 }
555
556 #[test]
557 fn test_room_secrets_v1_default() {
558 let secrets = RoomSecretsV1::default();
559 assert_eq!(secrets.current_version, 0);
560 assert!(secrets.versions.is_empty());
561 assert!(secrets.encrypted_secrets.is_empty());
562 }
563
564 #[test]
565 fn test_authorized_secret_version_record() {
566 let owner_signing_key = SigningKey::generate(&mut OsRng);
567 let owner_verifying_key = owner_signing_key.verifying_key();
568
569 let record = SecretVersionRecordV1 {
570 version: 1,
571 cipher_spec: RoomCipherSpec::Aes256Gcm,
572 created_at: SystemTime::now(),
573 };
574
575 let authorized_record =
576 AuthorizedSecretVersionRecord::new(record.clone(), &owner_signing_key);
577
578 assert_eq!(authorized_record.record, record);
579 assert!(authorized_record
580 .verify_signature(&owner_verifying_key)
581 .is_ok());
582
583 let wrong_key = SigningKey::generate(&mut OsRng).verifying_key();
585 assert!(authorized_record.verify_signature(&wrong_key).is_err());
586 }
587
588 #[test]
589 fn test_authorized_encrypted_secret_for_member() {
590 let owner_signing_key = SigningKey::generate(&mut OsRng);
591 let owner_verifying_key = owner_signing_key.verifying_key();
592 let member_id = MemberId::from(&owner_verifying_key);
593
594 let secret = EncryptedSecretForMemberV1 {
595 member_id,
596 secret_version: 1,
597 ciphertext: vec![1, 2, 3, 4],
598 nonce: [0u8; 12],
599 sender_ephemeral_public_key: [0u8; 32],
600 provider: member_id,
601 };
602
603 let authorized_secret =
604 AuthorizedEncryptedSecretForMember::new(secret.clone(), &owner_signing_key);
605
606 assert_eq!(authorized_secret.secret, secret);
607 assert!(authorized_secret
608 .verify_signature(&owner_verifying_key)
609 .is_ok());
610
611 let wrong_key = SigningKey::generate(&mut OsRng).verifying_key();
613 assert!(authorized_secret.verify_signature(&wrong_key).is_err());
614 }
615
616 #[test]
621 fn test_verify_empty_state() {
622 let (state, params, _) = create_test_state_and_params();
623 let secrets = RoomSecretsV1::default();
624
625 assert!(secrets.verify(&state, ¶ms).is_ok());
626 }
627
628 #[test]
629 fn test_verify_valid_state_with_version() {
630 let (state, params, owner_sk) = create_test_state_and_params();
631 let owner_id = params.owner_id();
632
633 let mut secrets = RoomSecretsV1 {
634 current_version: 1,
635 ..Default::default()
636 };
637 secrets.versions.push(create_version_record(1, &owner_sk));
638 secrets
639 .encrypted_secrets
640 .push(create_encrypted_secret(owner_id, 1, &owner_sk));
641
642 assert!(secrets.verify(&state, ¶ms).is_ok());
643 }
644
645 #[test]
646 fn test_verify_fails_with_invalid_version_signature() {
647 let (state, params, _owner_sk) = create_test_state_and_params();
648 let wrong_sk = SigningKey::generate(&mut OsRng);
649
650 let mut secrets = RoomSecretsV1 {
651 current_version: 1,
652 ..Default::default()
653 };
654 secrets.versions.push(create_version_record(1, &wrong_sk)); let result = secrets.verify(&state, ¶ms);
657 assert!(result.is_err());
658 assert!(result
659 .unwrap_err()
660 .contains("Invalid version record signature"));
661 }
662
663 #[test]
664 fn test_verify_fails_with_invalid_secret_signature() {
665 let (state, params, owner_sk) = create_test_state_and_params();
666 let owner_id = params.owner_id();
667 let wrong_sk = SigningKey::generate(&mut OsRng);
668
669 let mut secrets = RoomSecretsV1 {
670 current_version: 1,
671 ..Default::default()
672 };
673 secrets.versions.push(create_version_record(1, &owner_sk));
674 secrets
675 .encrypted_secrets
676 .push(create_encrypted_secret(owner_id, 1, &wrong_sk)); let result = secrets.verify(&state, ¶ms);
679 assert!(result.is_err());
680 assert!(result
681 .unwrap_err()
682 .contains("Invalid encrypted secret signature"));
683 }
684
685 #[test]
686 fn test_verify_fails_with_mismatched_current_version() {
687 let (state, params, owner_sk) = create_test_state_and_params();
688
689 let mut secrets = RoomSecretsV1 {
690 current_version: 2,
691 ..Default::default()
692 }; secrets.versions.push(create_version_record(1, &owner_sk));
694
695 let result = secrets.verify(&state, ¶ms);
696 assert!(result.is_err());
697 assert!(result
698 .unwrap_err()
699 .contains("does not match maximum version"));
700 }
701
702 #[test]
703 fn test_verify_fails_with_nonzero_current_but_no_versions() {
704 let (state, params, _) = create_test_state_and_params();
705
706 let secrets = RoomSecretsV1 {
707 current_version: 1,
708 ..Default::default()
709 };
710 let result = secrets.verify(&state, ¶ms);
713 assert!(result.is_err());
714 assert!(result.unwrap_err().contains("no version records exist"));
715 }
716
717 #[test]
718 fn test_summarize_empty_state() {
719 let (state, params, _) = create_test_state_and_params();
720 let secrets = RoomSecretsV1::default();
721
722 let summary = secrets.summarize(&state, ¶ms);
723 assert_eq!(summary.current_version, 0);
724 assert!(summary.version_ids.is_empty());
725 assert!(summary.member_secrets.is_empty());
726 }
727
728 #[test]
729 fn test_summarize_with_data() {
730 let (state, params, owner_sk) = create_test_state_and_params();
731 let owner_id = params.owner_id();
732
733 let mut secrets = RoomSecretsV1 {
734 current_version: 2,
735 ..Default::default()
736 };
737 secrets.versions.push(create_version_record(1, &owner_sk));
738 secrets.versions.push(create_version_record(2, &owner_sk));
739 secrets
740 .encrypted_secrets
741 .push(create_encrypted_secret(owner_id, 1, &owner_sk));
742 secrets
743 .encrypted_secrets
744 .push(create_encrypted_secret(owner_id, 2, &owner_sk));
745
746 let summary = secrets.summarize(&state, ¶ms);
747 assert_eq!(summary.current_version, 2);
748 assert_eq!(summary.version_ids.len(), 2);
749 assert!(summary.version_ids.contains(&1));
750 assert!(summary.version_ids.contains(&2));
751 assert_eq!(summary.member_secrets.len(), 2);
752 assert!(summary.member_secrets.contains(&(1, owner_id)));
753 assert!(summary.member_secrets.contains(&(2, owner_id)));
754 }
755
756 #[test]
757 fn test_delta_no_changes() {
758 let (state, params, _) = create_test_state_and_params();
759 let secrets = RoomSecretsV1::default();
760 let summary = secrets.summarize(&state, ¶ms);
761
762 let delta = secrets.delta(&state, ¶ms, &summary);
763 assert!(delta.is_none());
764 }
765
766 #[test]
767 fn test_delta_new_version() {
768 let (state, params, owner_sk) = create_test_state_and_params();
769 let owner_id = params.owner_id();
770
771 let mut secrets = RoomSecretsV1 {
772 current_version: 1,
773 ..Default::default()
774 };
775 secrets.versions.push(create_version_record(1, &owner_sk));
776 secrets
777 .encrypted_secrets
778 .push(create_encrypted_secret(owner_id, 1, &owner_sk));
779
780 let old_summary = SecretsSummary {
781 current_version: 0,
782 version_ids: HashSet::new(),
783 member_secrets: HashSet::new(),
784 };
785
786 let delta = secrets.delta(&state, ¶ms, &old_summary).unwrap();
787 assert_eq!(delta.current_version, Some(1));
788 assert_eq!(delta.new_versions.len(), 1);
789 assert_eq!(delta.new_encrypted_secrets.len(), 1);
790 }
791
792 #[test]
793 fn test_delta_partial_update() {
794 let (state, params, owner_sk) = create_test_state_and_params();
795 let owner_id = params.owner_id();
796
797 let mut secrets = RoomSecretsV1 {
798 current_version: 2,
799 ..Default::default()
800 };
801 secrets.versions.push(create_version_record(1, &owner_sk));
802 secrets.versions.push(create_version_record(2, &owner_sk));
803 secrets
804 .encrypted_secrets
805 .push(create_encrypted_secret(owner_id, 1, &owner_sk));
806 secrets
807 .encrypted_secrets
808 .push(create_encrypted_secret(owner_id, 2, &owner_sk));
809
810 let mut old_summary = SecretsSummary {
811 current_version: 1,
812 version_ids: HashSet::new(),
813 member_secrets: HashSet::new(),
814 };
815 old_summary.version_ids.insert(1);
816 old_summary.member_secrets.insert((1, owner_id));
817
818 let delta = secrets.delta(&state, ¶ms, &old_summary).unwrap();
819 assert_eq!(delta.current_version, Some(2));
820 assert_eq!(delta.new_versions.len(), 1);
821 assert_eq!(delta.new_versions[0].record.version, 2);
822 assert_eq!(delta.new_encrypted_secrets.len(), 1);
823 assert_eq!(delta.new_encrypted_secrets[0].secret.secret_version, 2);
824 }
825
826 #[test]
827 fn test_apply_delta_add_first_version() {
828 let (state, params, owner_sk) = create_test_state_and_params();
829 let owner_id = params.owner_id();
830
831 let mut secrets = RoomSecretsV1::default();
832
833 let delta = SecretsDelta {
834 current_version: Some(1),
835 new_versions: vec![create_version_record(1, &owner_sk)],
836 new_encrypted_secrets: vec![create_encrypted_secret(owner_id, 1, &owner_sk)],
837 };
838
839 let result = secrets.apply_delta(&state, ¶ms, &Some(delta));
840 assert!(result.is_ok(), "Failed: {:?}", result.err());
841 assert_eq!(secrets.current_version, 1);
842 assert_eq!(secrets.versions.len(), 1);
843 assert_eq!(secrets.encrypted_secrets.len(), 1);
844 }
845
846 #[test]
847 fn test_apply_delta_rejects_duplicate_version() {
848 let (state, params, owner_sk) = create_test_state_and_params();
849
850 let mut secrets = RoomSecretsV1 {
851 current_version: 1,
852 ..Default::default()
853 };
854 secrets.versions.push(create_version_record(1, &owner_sk));
855
856 let delta = SecretsDelta {
857 current_version: None,
858 new_versions: vec![create_version_record(1, &owner_sk)], new_encrypted_secrets: vec![],
860 };
861
862 let result = secrets.apply_delta(&state, ¶ms, &Some(delta));
863 assert!(result.is_err());
864 assert!(result.unwrap_err().contains("Duplicate secret version"));
865 }
866
867 #[test]
868 fn test_apply_delta_skips_secret_for_nonexistent_member() {
869 let (state, params, owner_sk) = create_test_state_and_params();
870 let fake_member_id = MemberId::from(&SigningKey::generate(&mut OsRng).verifying_key());
871
872 let mut secrets = RoomSecretsV1 {
873 current_version: 1,
874 ..Default::default()
875 };
876 secrets.versions.push(create_version_record(1, &owner_sk));
877
878 let delta = SecretsDelta {
879 current_version: None,
880 new_versions: vec![],
881 new_encrypted_secrets: vec![create_encrypted_secret(fake_member_id, 1, &owner_sk)],
882 };
883
884 let result = secrets.apply_delta(&state, ¶ms, &Some(delta));
886 assert!(
887 result.is_ok(),
888 "Should skip non-existent member, got: {:?}",
889 result.err()
890 );
891 assert!(
893 !secrets
894 .encrypted_secrets
895 .iter()
896 .any(|s| s.secret.member_id == fake_member_id),
897 "Secret for non-existent member should not be added"
898 );
899 }
900
901 #[test]
902 fn test_apply_delta_rejects_secret_for_nonexistent_version() {
903 let (state, params, owner_sk) = create_test_state_and_params();
904 let owner_id = params.owner_id();
905
906 let mut secrets = RoomSecretsV1::default();
907
908 let delta = SecretsDelta {
909 current_version: None,
910 new_versions: vec![],
911 new_encrypted_secrets: vec![create_encrypted_secret(owner_id, 99, &owner_sk)], };
913
914 let result = secrets.apply_delta(&state, ¶ms, &Some(delta));
915 assert!(result.is_err());
916 assert!(result.unwrap_err().contains("non-existent version"));
917 }
918
919 #[test]
920 fn test_apply_delta_rejects_duplicate_member_secret() {
921 let (state, params, owner_sk) = create_test_state_and_params();
922 let owner_id = params.owner_id();
923
924 let mut secrets = RoomSecretsV1 {
925 current_version: 1,
926 ..Default::default()
927 };
928 secrets.versions.push(create_version_record(1, &owner_sk));
929 secrets
930 .encrypted_secrets
931 .push(create_encrypted_secret(owner_id, 1, &owner_sk));
932
933 let delta = SecretsDelta {
934 current_version: None,
935 new_versions: vec![],
936 new_encrypted_secrets: vec![create_encrypted_secret(owner_id, 1, &owner_sk)], };
938
939 let result = secrets.apply_delta(&state, ¶ms, &Some(delta));
940 assert!(result.is_err());
941 assert!(result.unwrap_err().contains("Duplicate encrypted secret"));
942 }
943
944 #[test]
945 fn test_apply_delta_rejects_invalid_version_transition() {
946 let (state, params, owner_sk) = create_test_state_and_params();
947
948 let mut secrets = RoomSecretsV1 {
949 current_version: 2,
950 ..Default::default()
951 };
952 secrets.versions.push(create_version_record(1, &owner_sk));
953 secrets.versions.push(create_version_record(2, &owner_sk));
954
955 let delta = SecretsDelta {
956 current_version: Some(1), new_versions: vec![],
958 new_encrypted_secrets: vec![],
959 };
960
961 let result = secrets.apply_delta(&state, ¶ms, &Some(delta));
962 assert!(result.is_err());
963 assert!(result
964 .unwrap_err()
965 .contains("must be greater than existing version"));
966 }
967
968 #[test]
969 fn test_apply_delta_rejects_nonexistent_current_version() {
970 let (state, params, _owner_sk) = create_test_state_and_params();
971
972 let mut secrets = RoomSecretsV1::default();
973
974 let delta = SecretsDelta {
975 current_version: Some(99), new_versions: vec![],
977 new_encrypted_secrets: vec![],
978 };
979
980 let result = secrets.apply_delta(&state, ¶ms, &Some(delta));
981 assert!(result.is_err());
982 assert!(result.unwrap_err().contains("non-existent version"));
983 }
984
985 #[test]
986 fn test_apply_delta_prunes_removed_member_secrets() {
987 let (mut state, params, owner_sk) = create_test_state_and_params();
988 let owner_id = params.owner_id();
989
990 let member_sk = SigningKey::generate(&mut OsRng);
992 let member_vk = member_sk.verifying_key();
993 let member_id = MemberId::from(&member_vk);
994
995 let member = Member {
996 owner_member_id: owner_id,
997 invited_by: owner_id,
998 member_vk,
999 };
1000 let auth_member = AuthorizedMember::new(member, &owner_sk);
1001 state.members.members.push(auth_member);
1002
1003 let mut secrets = RoomSecretsV1 {
1005 current_version: 1,
1006 ..Default::default()
1007 };
1008 secrets.versions.push(create_version_record(1, &owner_sk));
1009 secrets
1010 .encrypted_secrets
1011 .push(create_encrypted_secret(owner_id, 1, &owner_sk));
1012 secrets
1013 .encrypted_secrets
1014 .push(create_encrypted_secret(member_id, 1, &owner_sk));
1015
1016 assert_eq!(secrets.encrypted_secrets.len(), 2);
1017
1018 state.members.members.clear();
1020
1021 let delta = SecretsDelta {
1023 current_version: None,
1024 new_versions: vec![],
1025 new_encrypted_secrets: vec![],
1026 };
1027
1028 let result = secrets.apply_delta(&state, ¶ms, &Some(delta));
1029 assert!(result.is_ok());
1030
1031 assert_eq!(secrets.encrypted_secrets.len(), 1);
1033 assert_eq!(secrets.encrypted_secrets[0].secret.member_id, owner_id);
1034 }
1035
1036 #[test]
1037 fn test_has_complete_distribution_empty() {
1038 let secrets = RoomSecretsV1::default();
1039 let members = HashMap::new();
1040
1041 assert!(secrets.has_complete_distribution(&members));
1042 }
1043
1044 #[test]
1045 fn test_has_complete_distribution_complete() {
1046 let (_state, params, owner_sk) = create_test_state_and_params();
1047 let owner_id = params.owner_id();
1048
1049 let mut secrets = RoomSecretsV1 {
1050 current_version: 1,
1051 ..Default::default()
1052 };
1053 secrets.versions.push(create_version_record(1, &owner_sk));
1054 secrets
1055 .encrypted_secrets
1056 .push(create_encrypted_secret(owner_id, 1, &owner_sk));
1057
1058 let member = Member {
1059 owner_member_id: owner_id,
1060 invited_by: owner_id,
1061 member_vk: params.owner,
1062 };
1063 let auth_member = AuthorizedMember::new(member, &owner_sk);
1064
1065 let mut members = HashMap::new();
1066 members.insert(owner_id, &auth_member);
1067
1068 assert!(secrets.has_complete_distribution(&members));
1069 }
1070
1071 #[test]
1072 fn test_has_complete_distribution_incomplete() {
1073 let (_state, params, owner_sk) = create_test_state_and_params();
1074 let owner_id = params.owner_id();
1075
1076 let member_sk = SigningKey::generate(&mut OsRng);
1077 let member_vk = member_sk.verifying_key();
1078 let member_id = MemberId::from(&member_vk);
1079
1080 let mut secrets = RoomSecretsV1 {
1081 current_version: 1,
1082 ..Default::default()
1083 };
1084 secrets.versions.push(create_version_record(1, &owner_sk));
1085 secrets
1086 .encrypted_secrets
1087 .push(create_encrypted_secret(owner_id, 1, &owner_sk));
1088 let member = Member {
1091 owner_member_id: owner_id,
1092 invited_by: owner_id,
1093 member_vk,
1094 };
1095 let auth_member = AuthorizedMember::new(member, &owner_sk);
1096
1097 let mut members = HashMap::new();
1098 members.insert(member_id, &auth_member);
1099
1100 assert!(!secrets.has_complete_distribution(&members));
1101 }
1102
1103 #[test]
1107 fn test_apply_delta_with_removed_member_secret() {
1108 let (mut state, params, owner_sk) = create_test_state_and_params();
1109 let owner_id = params.owner_id();
1110
1111 let member_sk = SigningKey::generate(&mut OsRng);
1113 let member_vk = member_sk.verifying_key();
1114 let member_id = MemberId::from(&member_vk);
1115
1116 let member = Member {
1117 owner_member_id: owner_id,
1118 invited_by: owner_id,
1119 member_vk,
1120 };
1121 let auth_member = AuthorizedMember::new(member, &owner_sk);
1122 state.members.members.push(auth_member);
1123
1124 let mut secrets = RoomSecretsV1 {
1126 current_version: 1,
1127 ..Default::default()
1128 };
1129 secrets.versions.push(create_version_record(1, &owner_sk));
1130 secrets
1131 .encrypted_secrets
1132 .push(create_encrypted_secret(owner_id, 1, &owner_sk));
1133 secrets
1134 .encrypted_secrets
1135 .push(create_encrypted_secret(member_id, 1, &owner_sk));
1136
1137 state.members.members.clear();
1139
1140 let delta = SecretsDelta {
1142 current_version: Some(2),
1143 new_versions: vec![create_version_record(2, &owner_sk)],
1144 new_encrypted_secrets: vec![
1145 create_encrypted_secret(owner_id, 2, &owner_sk),
1146 create_encrypted_secret(member_id, 2, &owner_sk), ],
1148 };
1149
1150 let result = secrets.apply_delta(&state, ¶ms, &Some(delta));
1152 assert!(
1153 result.is_ok(),
1154 "apply_delta should skip removed member's secret, got: {:?}",
1155 result.err()
1156 );
1157
1158 assert!(
1160 !secrets
1161 .encrypted_secrets
1162 .iter()
1163 .any(|s| s.secret.member_id == member_id),
1164 "Removed member's secrets should be pruned"
1165 );
1166
1167 assert!(
1169 secrets
1170 .encrypted_secrets
1171 .iter()
1172 .any(|s| s.secret.member_id == owner_id && s.secret.secret_version == 2),
1173 "Owner's new secret should be present"
1174 );
1175 }
1176}