confium_crypto_zk/
threshold_abs.rs1use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
11pub enum Attribute {
12 Region(String),
13 Role(String),
14 Clearance(u32),
15 Department(String),
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct SignPolicy {
21 pub required: Vec<Attribute>,
23 pub threshold: u32,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct AttributeSigner {
30 pub id: String,
31 pub attributes: Vec<Attribute>,
32}
33
34pub fn satisfies_policy(signer: &AttributeSigner, policy: &SignPolicy) -> bool {
36 for req in &policy.required {
37 if !signer.attributes.contains(req) {
38 return false;
39 }
40 }
41 true
42}
43
44#[derive(Debug)]
46pub struct AbsSession {
47 pub policy: SignPolicy,
48 pub signers: Vec<AttributeSigner>,
49 pub authorized: Vec<String>,
50}
51
52impl AbsSession {
53 pub fn new(policy: SignPolicy) -> Self {
54 Self {
55 policy,
56 signers: Vec::new(),
57 authorized: Vec::new(),
58 }
59 }
60
61 pub fn add_signer(&mut self, signer: AttributeSigner) -> bool {
63 let authorized = satisfies_policy(&signer, &self.policy);
64 if authorized {
65 self.authorized.push(signer.id.clone());
66 }
67 self.signers.push(signer);
68 authorized
69 }
70
71 pub fn can_sign(&self) -> bool {
73 self.authorized.len() >= self.policy.threshold as usize
74 }
75
76 pub fn authorized_count(&self) -> usize {
78 self.authorized.len()
79 }
80
81 pub fn authorized_ids(&self) -> &[String] {
83 &self.authorized
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 fn make_policy() -> SignPolicy {
92 SignPolicy {
93 required: vec![
94 Attribute::Role("director".into()),
95 Attribute::Region("eu".into()),
96 ],
97 threshold: 2,
98 }
99 }
100
101 #[test]
102 fn satisfies_policy_all_attributes() {
103 let signer = AttributeSigner {
104 id: "alice".into(),
105 attributes: vec![
106 Attribute::Role("director".into()),
107 Attribute::Region("eu".into()),
108 ],
109 };
110 assert!(satisfies_policy(&signer, &make_policy()));
111 }
112
113 #[test]
114 fn missing_attribute_fails() {
115 let signer = AttributeSigner {
116 id: "bob".into(),
117 attributes: vec![Attribute::Role("director".into())],
118 };
119 assert!(!satisfies_policy(&signer, &make_policy()));
120 }
121
122 #[test]
123 fn session_threshold_met() {
124 let mut session = AbsSession::new(make_policy());
125 session.add_signer(AttributeSigner {
126 id: "alice".into(),
127 attributes: vec![
128 Attribute::Role("director".into()),
129 Attribute::Region("eu".into()),
130 ],
131 });
132 session.add_signer(AttributeSigner {
133 id: "bob".into(),
134 attributes: vec![
135 Attribute::Role("director".into()),
136 Attribute::Region("eu".into()),
137 ],
138 });
139 assert!(session.can_sign());
140 assert_eq!(session.authorized_count(), 2);
141 }
142
143 #[test]
144 fn session_threshold_not_met() {
145 let mut session = AbsSession::new(make_policy());
146 session.add_signer(AttributeSigner {
147 id: "alice".into(),
148 attributes: vec![
149 Attribute::Role("director".into()),
150 Attribute::Region("eu".into()),
151 ],
152 });
153 assert!(!session.can_sign());
154 }
155
156 #[test]
157 fn unauthorized_signers_not_counted() {
158 let mut session = AbsSession::new(make_policy());
159 session.add_signer(AttributeSigner {
160 id: "alice".into(),
161 attributes: vec![
162 Attribute::Role("director".into()),
163 Attribute::Region("eu".into()),
164 ],
165 });
166 session.add_signer(AttributeSigner {
167 id: "bob".into(),
168 attributes: vec![Attribute::Role("analyst".into())], });
170 assert!(!session.can_sign());
171 assert_eq!(session.authorized_count(), 1);
172 }
173
174 #[test]
175 fn clearance_attribute() {
176 let policy = SignPolicy {
177 required: vec![Attribute::Clearance(5)],
178 threshold: 1,
179 };
180 let signer = AttributeSigner {
181 id: "alice".into(),
182 attributes: vec![Attribute::Clearance(5)],
183 };
184 assert!(satisfies_policy(&signer, &policy));
185 }
186
187 #[test]
188 fn different_clearance_fails() {
189 let policy = SignPolicy {
190 required: vec![Attribute::Clearance(5)],
191 threshold: 1,
192 };
193 let signer = AttributeSigner {
194 id: "alice".into(),
195 attributes: vec![Attribute::Clearance(3)],
196 };
197 assert!(!satisfies_policy(&signer, &policy));
198 }
199
200 #[test]
201 fn authorized_ids_listed() {
202 let mut session = AbsSession::new(make_policy());
203 session.add_signer(AttributeSigner {
204 id: "alice".into(),
205 attributes: vec![
206 Attribute::Role("director".into()),
207 Attribute::Region("eu".into()),
208 ],
209 });
210 assert_eq!(session.authorized_ids(), &["alice"]);
211 }
212}