crew_rs/
crew_state.rs

1use std::collections::{HashMap, HashSet};
2
3use endr::ObjectID;
4use litl::impl_debug_as_litl;
5use ridl::{
6    asymm_encr::EncrFromAnon,
7    signing::SignerID,
8    symm_encr::{Encrypted, KeyID, KeySecret},
9};
10use serde_derive::{Deserialize, Serialize};
11
12use crate::{changes::RevealSecretToParent, crew::CrewID, tx::CrewChangeTxEntry};
13use crate::{
14    tx::CrewChangeTx, AddParent, AddRole, CrewChange, CrewRuleset, EntrustInfo, MakeStatement,
15    Member, MemberCredential, RemoveParent, RemoveRole, RevealSecret,
16};
17
18#[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
19pub struct CrewState {
20    pub parents: HashSet<CrewID>,
21    pub roles: HashSet<(Member, String)>,
22    pub shared_secrets: HashMap<String, HashMap<SignerID, EncrFromAnon<KeySecret>>>,
23    pub entrusted_info: HashMap<String, HashMap<String, Encrypted<litl::Val>>>,
24    pub statements: HashSet<(String, litl::Val, SignerID)>,
25    #[serde(with = "serialize_as_vec")]
26    pub parent_secret_map: HashMap<(CrewID, String, KeyID), Encrypted<KeySecret>>,
27}
28impl_debug_as_litl!(CrewState);
29
30pub mod serialize_as_vec;
31
32impl Default for CrewState {
33    fn default() -> Self {
34        CrewState::new()
35    }
36}
37
38impl CrewState {
39    pub fn new() -> Self {
40        CrewState {
41            parents: HashSet::new(),
42            roles: HashSet::new(),
43            shared_secrets: HashMap::new(),
44            entrusted_info: HashMap::new(),
45            statements: HashSet::new(),
46            parent_secret_map: HashMap::new(),
47        }
48    }
49
50    pub fn apply_unchecked(&self, change: &CrewChange, made_by: &SignerID) -> Self {
51        let mut new_state = self.clone();
52
53        match change {
54            CrewChange::AddRole(AddRole { to, role }) => {
55                new_state.roles.insert((to.clone(), role.clone()));
56            }
57            CrewChange::RemoveRole(RemoveRole { from, role }) => {
58                new_state.roles.remove(&(from.clone(), role.clone()));
59            }
60            CrewChange::RevealSecret(RevealSecret {
61                secret_kind,
62                to,
63                encr,
64            }) => {
65                new_state
66                    .shared_secrets
67                    .entry(secret_kind.clone())
68                    .or_default()
69                    .insert(to.clone(), encr.clone());
70            }
71            CrewChange::RevealSecretToParent(RevealSecretToParent {
72                secret_kind,
73                parent_secret_id,
74                parent_id,
75                encr,
76            }) => {
77                new_state.parent_secret_map.insert(
78                    (
79                        parent_id.clone(),
80                        secret_kind.clone(),
81                        parent_secret_id.clone(),
82                    ),
83                    encr.clone(),
84                );
85            }
86            CrewChange::EntrustInfo(EntrustInfo {
87                to_secret_kind,
88                info_id,
89                info,
90            }) => {
91                new_state
92                    .entrusted_info
93                    .entry(to_secret_kind.clone())
94                    .or_default()
95                    .insert(info_id.clone(), info.clone());
96            }
97            CrewChange::AddParent(AddParent { parent }) => {
98                new_state.parents.insert(parent.clone());
99            }
100            CrewChange::RemoveParent(RemoveParent { parent }) => {
101                new_state.parents.remove(parent);
102            }
103            CrewChange::MakeStatement(MakeStatement { path, value }) => {
104                new_state
105                    .statements
106                    .insert((path.clone(), value.clone(), made_by.clone()));
107            }
108        }
109
110        new_state
111    }
112
113    pub fn apply<R: CrewRuleset>(
114        &self,
115        tx: CrewChangeTxEntry,
116        rule_set: R,
117    ) -> Result<CrewState, String> {
118        tx.correctly_signed()?;
119
120        let mut state = self.clone();
121
122        for (change_idx, change) in tx.1.content.attested.changes.iter().enumerate() {
123            rule_set
124                .is_valid_change(&state, &change, &tx.1.by, tx.1.content.attested.made_at)
125                .map_err(|err| format!("Error in change {} in tx: {}", change_idx, err))?;
126            state = state.apply_unchecked(&change, &tx.1.by);
127        }
128
129        Ok(state)
130    }
131
132    pub fn merge_parent_roles(&self, parent_state: &CrewState) -> CrewState {
133        let mut merged = self.clone();
134
135        for (member, role) in &parent_state.roles {
136            if !merged.roles.contains(&(member.clone(), role.clone())) {
137                merged.roles.insert((member.clone(), role.clone()));
138            }
139        }
140
141        merged
142    }
143
144    pub fn roles_of(&self, signer: &SignerID) -> HashSet<String> {
145        self.roles
146            .iter()
147            .filter(|(role_member, _)| &role_member.signer == signer)
148            .map(|(_, role)| role.clone())
149            .collect()
150    }
151
152    pub fn get_shared_secret(
153        &self,
154        secret_kind: &str,
155        credentials: &[MemberCredential],
156    ) -> Result<KeySecret, String> {
157        let (encrypted_secret, matching_credential) = self
158            .shared_secrets
159            .get(secret_kind)
160            .and_then(|secrets| {
161                credentials
162                    .iter()
163                    .find_map(|credential| secrets.get(&credential.pub_id().signer).map(|encr| (encr, credential)))
164            })
165            .ok_or_else(|| "No secret revelation found".to_owned())?;
166
167            matching_credential
168            .decrypt_secret(encrypted_secret)
169            .map_err(|err| err.to_string())
170    }
171
172    pub fn get_entrusted_info(
173        &self,
174        secret_kind: &str,
175        info_id: &str,
176        credentials: &[MemberCredential],
177    ) -> Result<litl::Val, String> {
178        let encrypted_info = self
179            .entrusted_info
180            .get(secret_kind)
181            .and_then(|infos| infos.get(info_id))
182            .ok_or_else(|| "No entrusted info found".to_owned())?;
183
184        let key_secret = self.get_shared_secret(secret_kind, credentials)?;
185        key_secret
186            .decrypt(encrypted_info)
187            .map_err(|err| err.to_string())
188    }
189
190    pub fn merge_with_parent_states(
191        &self,
192        parent_states: HashMap<ObjectID, CrewState>,
193    ) -> Result<CrewState, String> {
194        // ensure that we get exactly one parent state for each current parent
195        // roles get inherited
196        todo!()
197    }
198
199    pub fn statements_with_prefix(&self, prefix: &str) -> HashMap<String, litl::Val> {
200        self.statements
201            .iter()
202            .filter(|(path, _, _)| path.starts_with(prefix))
203            .map(|(path, value, _)| (path.clone(), value.clone()))
204            .collect()
205    }
206}