Skip to main content

river_core/room_state/
upgrade.rs

1use crate::room_state::member::MemberId;
2use crate::room_state::ChatRoomParametersV1;
3use crate::util::{sign_struct, truncated_base64, verify_struct};
4use crate::ChatRoomStateV1;
5use blake3::Hash;
6use ed25519_dalek::{Signature, SigningKey, VerifyingKey};
7use freenet_scaffold::ComposableState;
8use serde::{Deserialize, Serialize};
9use std::fmt;
10
11#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, Default)]
12pub struct OptionalUpgradeV1(pub Option<AuthorizedUpgradeV1>);
13
14#[derive(Serialize, Deserialize, Clone, PartialEq)]
15pub struct AuthorizedUpgradeV1 {
16    pub upgrade: UpgradeV1,
17    pub signature: Signature,
18}
19
20impl ComposableState for OptionalUpgradeV1 {
21    type ParentState = ChatRoomStateV1;
22    type Summary = Option<u8>;
23    type Delta = AuthorizedUpgradeV1;
24    type Parameters = ChatRoomParametersV1;
25
26    fn verify(
27        &self,
28        _parent_state: &Self::ParentState,
29        parameters: &Self::Parameters,
30    ) -> Result<(), String> {
31        if let Some(upgrade) = &self.0 {
32            upgrade
33                .validate(&parameters.owner)
34                .map_err(|e| format!("Invalid signature: {}", e))
35        } else {
36            Ok(())
37        }
38    }
39
40    fn summarize(
41        &self,
42        _parent_state: &Self::ParentState,
43        _parameters: &Self::Parameters,
44    ) -> Self::Summary {
45        self.0.as_ref().map(|u| u.upgrade.version)
46    }
47
48    fn delta(
49        &self,
50        _parent_state: &Self::ParentState,
51        _parameters: &Self::Parameters,
52        old_state_summary: &Self::Summary,
53    ) -> Option<Self::Delta> {
54        match &self.0 {
55            Some(upgrade) => {
56                // If the upgrade has a higher version than the old room_state summary or of the old summary is None
57                // then return the upgrade as a delta
58                if old_state_summary.is_none_or(|old_version| upgrade.upgrade.version > old_version)
59                {
60                    Some(upgrade.clone())
61                } else {
62                    None
63                }
64            }
65            None => None,
66        }
67    }
68
69    fn apply_delta(
70        &mut self,
71        _parent_state: &Self::ParentState,
72        parameters: &Self::Parameters,
73        delta: &Option<Self::Delta>,
74    ) -> Result<(), String> {
75        if let Some(delta) = delta {
76            // Verify the delta before applying it
77            delta
78                .validate(&parameters.owner)
79                .map_err(|e| format!("Invalid upgrade signature: {}", e))?;
80
81            *self = OptionalUpgradeV1(Some(delta.clone()));
82        }
83        Ok(())
84    }
85}
86
87impl AuthorizedUpgradeV1 {
88    pub fn new(upgrade: UpgradeV1, signing_key: &SigningKey) -> Self {
89        Self {
90            upgrade: upgrade.clone(),
91            signature: sign_struct(&upgrade, signing_key),
92        }
93    }
94
95    /// Create an AuthorizedUpgradeV1 with a pre-computed signature.
96    /// Use this when signing is done externally (e.g., via delegate).
97    pub fn with_signature(upgrade: UpgradeV1, signature: Signature) -> Self {
98        Self { upgrade, signature }
99    }
100
101    pub fn validate(
102        &self,
103        verifying_key: &VerifyingKey,
104    ) -> Result<(), ed25519_dalek::SignatureError> {
105        verify_struct(&self.upgrade, &self.signature, verifying_key)
106    }
107}
108
109impl fmt::Debug for AuthorizedUpgradeV1 {
110    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111        f.debug_struct("AuthorizedUpgrade")
112            .field("upgrade", &self.upgrade)
113            .field(
114                "signature",
115                &format_args!("{}", truncated_base64(self.signature.to_bytes())),
116            )
117            .finish()
118    }
119}
120
121#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
122pub struct UpgradeV1 {
123    pub owner_member_id: MemberId,
124    pub version: u8,
125    pub new_chatroom_address: Hash,
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131    use crate::room_state::member::MemberId;
132    use ed25519_dalek::SigningKey;
133    use freenet_scaffold::util::FastHash;
134    use rand::rngs::OsRng;
135
136    fn create_test_upgrade(owner_id: MemberId) -> UpgradeV1 {
137        UpgradeV1 {
138            owner_member_id: owner_id,
139            version: 1,
140            new_chatroom_address: Hash::from([0; 32]),
141        }
142    }
143
144    #[test]
145    fn test_authorized_upgrade_new_and_validate() {
146        let signing_key = SigningKey::generate(&mut OsRng);
147        let verifying_key = signing_key.verifying_key();
148        let owner_id = MemberId(FastHash(0));
149
150        let upgrade = create_test_upgrade(owner_id);
151        let authorized_upgrade = AuthorizedUpgradeV1::new(upgrade.clone(), &signing_key);
152
153        assert_eq!(authorized_upgrade.upgrade, upgrade);
154        assert!(authorized_upgrade.validate(&verifying_key).is_ok());
155
156        // Test with wrong key
157        let wrong_key = SigningKey::generate(&mut OsRng).verifying_key();
158        assert!(authorized_upgrade.validate(&wrong_key).is_err());
159    }
160
161    #[test]
162    fn test_optional_upgrade_verify() {
163        let owner_signing_key = SigningKey::generate(&mut OsRng);
164        let owner_verifying_key = owner_signing_key.verifying_key();
165        let owner_id = MemberId::from(&owner_verifying_key);
166
167        let upgrade = create_test_upgrade(owner_id);
168        let authorized_upgrade = AuthorizedUpgradeV1::new(upgrade, &owner_signing_key);
169
170        let optional_upgrade = OptionalUpgradeV1(Some(authorized_upgrade));
171
172        let parent_state = ChatRoomStateV1::default();
173        let parameters = ChatRoomParametersV1 {
174            owner: owner_verifying_key,
175        };
176
177        // Verify that a valid upgrade passes verification
178        assert!(
179            optional_upgrade.verify(&parent_state, &parameters).is_ok(),
180            "Valid upgrade should pass verification"
181        );
182
183        // Test with invalid signature
184        let mut invalid_upgrade = optional_upgrade.clone();
185        if let Some(ref mut au) = invalid_upgrade.0 {
186            au.signature = Signature::from_bytes(&[0; 64]); // Replace with an invalid signature
187        }
188        assert!(
189            invalid_upgrade.verify(&parent_state, &parameters).is_err(),
190            "Upgrade with invalid signature should fail verification"
191        );
192
193        // Test with None
194        let none_upgrade = OptionalUpgradeV1(None);
195        assert!(
196            none_upgrade.verify(&parent_state, &parameters).is_ok(),
197            "None upgrade should pass verification"
198        );
199    }
200
201    #[test]
202    fn test_optional_upgrade_summarize() {
203        let signing_key = SigningKey::generate(&mut OsRng);
204        let owner_id = MemberId(FastHash(0));
205
206        let upgrade = create_test_upgrade(owner_id);
207        let authorized_upgrade = AuthorizedUpgradeV1::new(upgrade, &signing_key);
208
209        let optional_upgrade = OptionalUpgradeV1(Some(authorized_upgrade));
210
211        let parent_state = ChatRoomStateV1::default();
212        let parameters = ChatRoomParametersV1 {
213            owner: signing_key.verifying_key(),
214        };
215
216        let summary = optional_upgrade.summarize(&parent_state, &parameters);
217        assert_eq!(summary, Some(1));
218
219        let none_upgrade = OptionalUpgradeV1(None);
220        let none_summary = none_upgrade.summarize(&parent_state, &parameters);
221        assert_eq!(none_summary, None);
222    }
223
224    #[test]
225    fn test_optional_upgrade_delta() {
226        let signing_key = SigningKey::generate(&mut OsRng);
227        let owner_id = MemberId(FastHash(0));
228
229        let upgrade = create_test_upgrade(owner_id);
230        let authorized_upgrade = AuthorizedUpgradeV1::new(upgrade, &signing_key);
231
232        let optional_upgrade = OptionalUpgradeV1(Some(authorized_upgrade.clone()));
233
234        let parent_state = ChatRoomStateV1::default();
235        let parameters = ChatRoomParametersV1 {
236            owner: signing_key.verifying_key(),
237        };
238
239        let old_summary = None;
240        let delta = optional_upgrade.delta(&parent_state, &parameters, &old_summary);
241
242        assert_eq!(delta, Some(authorized_upgrade));
243
244        let none_upgrade = OptionalUpgradeV1(None);
245        let none_delta = none_upgrade.delta(&parent_state, &parameters, &old_summary);
246        assert_eq!(none_delta, None);
247    }
248
249    #[test]
250    fn test_optional_upgrade_apply_delta() {
251        let signing_key = SigningKey::generate(&mut OsRng);
252        let owner_id = MemberId(FastHash(0));
253
254        let upgrade = create_test_upgrade(owner_id);
255        let authorized_upgrade = AuthorizedUpgradeV1::new(upgrade, &signing_key);
256
257        let mut optional_upgrade = OptionalUpgradeV1(None);
258
259        let parent_state = ChatRoomStateV1::default();
260        let parameters = ChatRoomParametersV1 {
261            owner: signing_key.verifying_key(),
262        };
263
264        let delta = authorized_upgrade.clone();
265        assert!(optional_upgrade
266            .apply_delta(&parent_state, &parameters, &Some(delta.clone()))
267            .is_ok());
268        assert_eq!(optional_upgrade, OptionalUpgradeV1(Some(delta)));
269    }
270}