coven_protocol/circle_control/
access.rs1use super::*;
2
3#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
5#[serde(deny_unknown_fields)]
6pub struct CircleBootstrapRef {
7 pub coverage: CommitFrontier,
8 pub schema_version: u32,
9 pub sync_routing_hash: ObjectHash,
10 pub image: SnapshotImageRef,
11 pub blobs: Vec<crate::blob::RowBlobRef>,
12}
13
14impl CircleBootstrapRef {
15 pub(crate) fn verify_for_access(&self, access: &CircleAccessLeaf) -> bool {
16 if crate::store_commit::validate_commit_frontier(&self.coverage).is_err() {
17 return false;
18 }
19 let blobs_are_canonical = self.blobs.windows(2).all(|pair| {
20 serde_json::to_vec(&pair[0]).expect("row blob reference serialization cannot fail")
21 < serde_json::to_vec(&pair[1])
22 .expect("row blob reference serialization cannot fail")
23 });
24 if !blobs_are_canonical
25 || self.blobs.iter().any(|blob| {
26 !matches!(
27 blob.authority(),
28 crate::blob::RowBlobAuthority::Remote(
29 crate::audience_package::PackageAudience::Circle {
30 circle_id,
31 ..
32 }
33 ) if *circle_id == access.circle_id
34 ) || blob.stored().is_none_or(|stored| {
35 stored.locator().audience()
36 != crate::blob::locator::RemoteAudience::Circle(access.circle_id)
37 })
38 })
39 {
40 return false;
41 }
42 let semantic_prefix = crate::store_commit::circle_bootstrap_image_semantic_prefix(
43 access.circle_id,
44 access.candidate_family,
45 &access.owner_pubkey,
46 access.epoch_id,
47 &access.recipient_slot,
48 self.image.image_hash,
49 );
50 self.image.object.slot().logical_key() == format!("{semantic_prefix}.db")
51 }
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
60#[serde(deny_unknown_fields)]
61pub struct CircleBootstrapCoverageRef {
62 pub circle_id: CircleId,
63 pub control: CircleControlCoord,
64 pub activation_commit: StoreBatchCommitRef,
65 pub bootstrap: CircleBootstrapRef,
66}
67
68#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
69#[serde(rename_all = "snake_case", deny_unknown_fields)]
70pub enum CircleAccessDisposition {
71 Active {
72 keyring: String,
73 key_fingerprint: KeyFingerprint,
74 roster: CircleRosterStateRef,
75 bootstrap: Option<CircleBootstrapRef>,
76 },
77 Inactive,
78}
79
80#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
83#[serde(deny_unknown_fields)]
84pub struct CircleAccessLeafBody {
85 pub store_root_hash: ObjectHash,
86 pub candidate_family: crate::store_commit::CandidateFamilyId,
87 pub circle_id: CircleId,
88 pub epoch_id: CircleEpochId,
89 pub leaf_id: AccessLeafId,
90 pub owner_pubkey: String,
91 pub recipient_pubkey: String,
92 pub recipient_slot: String,
93 pub disposition: CircleAccessDisposition,
94 pub store_membership: StoreMembershipStateRef,
95}
96
97impl SignedBody for CircleAccessLeafBody {
98 const DOMAIN: &'static [u8] = ACCESS_DOMAIN;
99}
100
101pub type CircleAccessLeaf = Signed<CircleAccessLeafBody>;
102
103impl CircleAccessLeaf {
104 pub fn verify_signature(&self) -> bool {
105 self.verify_by(&self.owner_pubkey).is_ok()
106 }
107
108 pub(crate) fn verify_for_control(
109 &self,
110 control: &PreparedCircleControl,
111 candidate_family: crate::store_commit::CandidateFamilyId,
112 ) -> bool {
113 self.verify_signature()
114 && self.store_root_hash == control.value.store_root_hash
115 && self.candidate_family == candidate_family
116 && self.circle_id == control.value.circle_id
117 && self.epoch_id == control.value.epoch_id()
118 && self.store_membership == control.value.store_membership_state_ref()
119 && match &self.disposition {
120 CircleAccessDisposition::Active {
121 keyring,
122 key_fingerprint,
123 roster,
124 bootstrap,
125 } => {
126 roster == &control.value.roster_state_ref()
127 && *key_fingerprint == control.value.key_fingerprint()
128 && MasterKeyring::from_serialized(keyring).is_ok_and(|keyring| {
129 EncryptionService::from(keyring).seal_key_fingerprint()
130 == *key_fingerprint
131 })
132 && bootstrap
133 .as_ref()
134 .is_none_or(|bootstrap| bootstrap.verify_for_access(self))
135 }
136 CircleAccessDisposition::Inactive => true,
137 }
138 && self.owner_pubkey == control.value.author_pubkey
139 }
140}
141
142#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
143#[serde(rename_all = "snake_case", deny_unknown_fields)]
144pub enum MerkleStep {
145 Left(ObjectHash),
146 Right(ObjectHash),
147}
148
149fn merkle_parent(left: ObjectHash, right: ObjectHash) -> ObjectHash {
150 let mut bytes = Vec::with_capacity(1 + 64);
151 bytes.push(1);
152 bytes.extend_from_slice(left.as_bytes());
153 bytes.extend_from_slice(right.as_bytes());
154 ObjectHash::digest(&bytes)
155}
156
157pub fn verify_merkle_proof(mut hash: ObjectHash, proof: &[MerkleStep], root: ObjectHash) -> bool {
158 for step in proof {
159 hash = match step {
160 MerkleStep::Left(left) => merkle_parent(*left, hash),
161 MerkleStep::Right(right) => merkle_parent(hash, *right),
162 };
163 }
164 hash == root
165}
166
167pub fn merkle_root_and_proofs(hashes: &[ObjectHash]) -> (ObjectHash, Vec<Vec<MerkleStep>>) {
168 assert!(
169 !hashes.is_empty(),
170 "a circle control has at least one access leaf"
171 );
172 let mut indexed = hashes
173 .iter()
174 .copied()
175 .enumerate()
176 .collect::<Vec<(usize, ObjectHash)>>();
177 indexed.sort_by_key(|(index, hash)| (*hash, *index));
178 let mut proofs = vec![Vec::new(); hashes.len()];
179 let mut layer = indexed
180 .into_iter()
181 .map(|(index, hash)| (hash, vec![index]))
182 .collect::<Vec<_>>();
183 while layer.len() > 1 {
184 let mut next = Vec::with_capacity(layer.len().div_ceil(2));
185 for pair in layer.chunks(2) {
186 let (left_hash, left_indices) = &pair[0];
187 if let Some((right_hash, right_indices)) = pair.get(1) {
188 for index in left_indices {
189 proofs[*index].push(MerkleStep::Right(*right_hash));
190 }
191 for index in right_indices {
192 proofs[*index].push(MerkleStep::Left(*left_hash));
193 }
194 let mut indices = left_indices.clone();
195 indices.extend(right_indices);
196 next.push((merkle_parent(*left_hash, *right_hash), indices));
197 } else {
198 for index in left_indices {
199 proofs[*index].push(MerkleStep::Right(*left_hash));
200 }
201 next.push((merkle_parent(*left_hash, *left_hash), left_indices.clone()));
202 }
203 }
204 layer = next;
205 }
206 (layer[0].0, proofs)
207}