1use serde::{Deserialize, Serialize};
8
9use crate::types::{CesrKey, ConfigTrait, Prefix, Said, Threshold};
10
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
17#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
18pub struct KeyState {
19 pub prefix: Prefix,
21
22 pub current_keys: Vec<CesrKey>,
24
25 pub next_commitment: Vec<Said>,
27
28 pub sequence: u128,
30
31 pub last_event_said: Said,
33
34 pub is_abandoned: bool,
36 pub threshold: Threshold,
38 pub next_threshold: Threshold,
40 #[serde(default)]
42 pub backers: Vec<Prefix>,
43 #[serde(default)]
45 pub backer_threshold: Threshold,
46 #[serde(default)]
48 pub config_traits: Vec<ConfigTrait>,
49 #[serde(default)]
51 pub is_non_transferable: bool,
52 #[serde(default)]
54 pub delegator: Option<Prefix>,
55 #[serde(default)]
59 pub last_establishment_sequence: u128,
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
64pub enum AnchorStatus {
65 Anchored,
67 Unverified,
69 NotAnchored,
71}
72
73impl KeyState {
74 #[allow(clippy::too_many_arguments)]
87 pub fn from_inception(
88 prefix: Prefix,
89 keys: Vec<CesrKey>,
90 next: Vec<Said>,
91 threshold: Threshold,
92 next_threshold: Threshold,
93 said: Said,
94 backers: Vec<Prefix>,
95 backer_threshold: Threshold,
96 config_traits: Vec<ConfigTrait>,
97 ) -> Self {
98 let is_non_transferable = next.is_empty();
99 Self {
100 prefix,
101 current_keys: keys,
102 next_commitment: next.clone(),
103 sequence: 0,
104 last_event_said: said,
105 is_abandoned: false,
109 threshold,
110 next_threshold,
111 backers,
112 backer_threshold,
113 config_traits,
114 is_non_transferable,
115 delegator: None,
116 last_establishment_sequence: 0,
117 }
118 }
119
120 #[allow(clippy::too_many_arguments)]
127 pub fn apply_rotation(
128 &mut self,
129 new_keys: Vec<CesrKey>,
130 new_next: Vec<Said>,
131 threshold: Threshold,
132 next_threshold: Threshold,
133 sequence: u128,
134 said: Said,
135 backers_to_remove: &[Prefix],
136 backers_to_add: &[Prefix],
137 backer_threshold: Threshold,
138 config_traits: Vec<ConfigTrait>,
139 ) {
140 self.current_keys = new_keys;
141 self.next_commitment = new_next.clone();
142 self.threshold = threshold;
143 self.next_threshold = next_threshold;
144 self.sequence = sequence;
145 self.last_event_said = said;
146 self.is_abandoned = new_next.is_empty();
147
148 self.backers.retain(|b| !backers_to_remove.contains(b));
150 self.backers.extend(backers_to_add.iter().cloned());
151 self.backer_threshold = backer_threshold;
152
153 if !config_traits.is_empty() {
155 self.config_traits = config_traits;
156 }
157
158 self.last_establishment_sequence = sequence;
159 }
160
161 pub fn apply_interaction(&mut self, sequence: u128, said: Said) {
165 self.sequence = sequence;
166 self.last_event_said = said;
167 }
168
169 pub fn current_key(&self) -> Option<&CesrKey> {
171 self.current_keys.first()
172 }
173
174 pub fn can_rotate(&self) -> bool {
178 !self.is_abandoned && !self.next_commitment.is_empty()
179 }
180
181 pub fn can_emit_ixn(&self) -> bool {
187 !self.is_non_transferable
188 && !self
189 .config_traits
190 .contains(&crate::types::ConfigTrait::EstablishmentOnly)
191 }
192
193 pub fn did(&self) -> String {
195 format!("did:keri:{}", self.prefix.as_str())
196 }
197}
198
199#[cfg(test)]
200#[allow(clippy::unwrap_used)]
201mod tests {
202 use super::*;
203
204 fn make_key(s: &str) -> CesrKey {
205 CesrKey::new_unchecked(s.to_string())
206 }
207
208 fn make_state() -> KeyState {
209 KeyState::from_inception(
210 Prefix::new_unchecked("EPrefix".to_string()),
211 vec![make_key("DKey1")],
212 vec![Said::new_unchecked("ENext1".to_string())],
213 Threshold::Simple(1),
214 Threshold::Simple(1),
215 Said::new_unchecked("ESAID".to_string()),
216 vec![],
217 Threshold::Simple(0),
218 vec![],
219 )
220 }
221
222 #[test]
223 fn key_state_from_inception() {
224 let state = make_state();
225 assert_eq!(state.sequence, 0);
226 assert!(!state.is_abandoned);
227 assert!(state.can_rotate());
228 assert_eq!(state.current_key().map(|k| k.as_str()), Some("DKey1"));
229 assert_eq!(state.did(), "did:keri:EPrefix");
230 }
231
232 #[test]
233 fn non_transferable_inception_is_not_abandoned() {
234 let state = KeyState::from_inception(
237 Prefix::new_unchecked("EPrefix".to_string()),
238 vec![make_key("DKey1")],
239 vec![],
240 Threshold::Simple(1),
241 Threshold::Simple(0),
242 Said::new_unchecked("ESAID".to_string()),
243 vec![],
244 Threshold::Simple(0),
245 vec![],
246 );
247 assert!(state.is_non_transferable);
248 assert!(!state.is_abandoned);
249 assert!(!state.can_rotate());
250 }
251
252 #[test]
253 fn key_state_apply_rotation() {
254 let mut state = make_state();
255
256 state.apply_rotation(
257 vec![make_key("DKey2")],
258 vec![Said::new_unchecked("ENext2".to_string())],
259 Threshold::Simple(1),
260 Threshold::Simple(1),
261 1,
262 Said::new_unchecked("ESAID2".to_string()),
263 &[],
264 &[],
265 Threshold::Simple(0),
266 vec![],
267 );
268
269 assert_eq!(state.sequence, 1);
270 assert_eq!(state.current_keys[0].as_str(), "DKey2");
271 assert_eq!(state.next_commitment[0], "ENext2");
272 assert_eq!(state.last_event_said, "ESAID2");
273 assert!(state.can_rotate());
274 }
275
276 #[test]
277 fn key_state_apply_interaction() {
278 let mut state = make_state();
279 state.apply_interaction(1, Said::new_unchecked("ESAID_IXN".to_string()));
280
281 assert_eq!(state.sequence, 1);
282 assert_eq!(state.current_keys[0].as_str(), "DKey1");
283 assert_eq!(state.last_event_said, "ESAID_IXN");
284 }
285
286 #[test]
287 fn abandoned_identity_cannot_rotate() {
288 let mut state = make_state();
292 assert!(!state.is_abandoned);
293
294 state.apply_rotation(
295 vec![make_key("DKey2")],
296 vec![],
297 Threshold::Simple(1),
298 Threshold::Simple(0),
299 1,
300 Said::new_unchecked("ESAID_ROT".to_string()),
301 &[],
302 &[],
303 Threshold::Simple(0),
304 vec![],
305 );
306 assert!(state.is_abandoned);
307 assert!(!state.can_rotate());
308 }
309
310 #[test]
311 fn key_state_serializes() {
312 let state = make_state();
313 let json = serde_json::to_string(&state).unwrap();
314 let parsed: KeyState = serde_json::from_str(&json).unwrap();
315 assert_eq!(state, parsed);
316 }
317
318 #[test]
319 fn rotation_applies_backer_deltas() {
320 let mut state = KeyState::from_inception(
321 Prefix::new_unchecked("EPrefix".to_string()),
322 vec![make_key("DKey1")],
323 vec![Said::new_unchecked("ENext1".to_string())],
324 Threshold::Simple(1),
325 Threshold::Simple(1),
326 Said::new_unchecked("ESAID".to_string()),
327 vec![
328 Prefix::new_unchecked("DWit1".to_string()),
329 Prefix::new_unchecked("DWit2".to_string()),
330 ],
331 Threshold::Simple(2),
332 vec![],
333 );
334
335 state.apply_rotation(
336 vec![make_key("DKey2")],
337 vec![Said::new_unchecked("ENext2".to_string())],
338 Threshold::Simple(1),
339 Threshold::Simple(1),
340 1,
341 Said::new_unchecked("ESAID2".to_string()),
342 &[Prefix::new_unchecked("DWit1".to_string())],
343 &[Prefix::new_unchecked("DWit3".to_string())],
344 Threshold::Simple(2),
345 vec![],
346 );
347
348 assert_eq!(state.backers.len(), 2);
349 assert_eq!(state.backers[0].as_str(), "DWit2");
350 assert_eq!(state.backers[1].as_str(), "DWit3");
351 }
352
353 #[test]
354 fn transferable_identity_can_emit_ixn() {
355 let state = make_state();
356 assert!(state.can_emit_ixn());
357 }
358
359 #[test]
360 fn non_transferable_identity_cannot_emit_ixn() {
361 let state = KeyState::from_inception(
362 Prefix::new_unchecked("EPrefix".to_string()),
363 vec![make_key("DKey1")],
364 vec![],
365 Threshold::Simple(1),
366 Threshold::Simple(0),
367 Said::new_unchecked("ESAID".to_string()),
368 vec![],
369 Threshold::Simple(0),
370 vec![],
371 );
372 assert!(state.is_non_transferable);
373 assert!(!state.can_emit_ixn());
374 }
375
376 #[test]
377 fn establishment_only_identity_cannot_emit_ixn() {
378 let state = KeyState::from_inception(
379 Prefix::new_unchecked("EPrefix".to_string()),
380 vec![make_key("DKey1")],
381 vec![Said::new_unchecked("ENext1".to_string())],
382 Threshold::Simple(1),
383 Threshold::Simple(1),
384 Said::new_unchecked("ESAID".to_string()),
385 vec![],
386 Threshold::Simple(0),
387 vec![ConfigTrait::EstablishmentOnly],
388 );
389 assert!(!state.can_emit_ixn());
390 }
391}