1use super::*;
2
3#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
4#[serde(deny_unknown_fields)]
5pub struct MergeCircleControlOrder {
6 pub device_id: String,
7 pub stream_id: AuthorStreamId,
8 pub author_owner_grant: MembershipGrantId,
9 pub seq: u64,
10 pub previous_control_hash: Option<ObjectHash>,
11 pub dependencies: Vec<CircleControlCoord>,
12}
13
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
19#[serde(deny_unknown_fields)]
20pub struct DeletedCircle {
21 pub frozen_epoch: MergeActiveCircleEpoch,
22}
23
24#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
25#[serde(rename_all = "snake_case", deny_unknown_fields)]
26pub enum CircleControlState {
27 ActiveEpoch(MergeActiveCircleEpoch),
28 EpochClose(CircleEpochClose),
29 Deleted(DeletedCircle),
30}
31
32impl CircleControlState {
33 pub fn access_epoch(&self) -> &MergeActiveCircleEpoch {
34 match self {
35 Self::ActiveEpoch(active) => active,
36 Self::EpochClose(close) => &close.frozen_epoch,
37 Self::Deleted(deleted) => &deleted.frozen_epoch,
38 }
39 }
40
41 pub fn access_epoch_mut(&mut self) -> &mut MergeActiveCircleEpoch {
42 match self {
43 Self::ActiveEpoch(active) => active,
44 Self::EpochClose(close) => &mut close.frozen_epoch,
45 Self::Deleted(deleted) => &mut deleted.frozen_epoch,
46 }
47 }
48
49 pub fn active_epoch(&self) -> Option<&MergeActiveCircleEpoch> {
50 match self {
51 Self::ActiveEpoch(active) => Some(active),
52 Self::EpochClose(_) | Self::Deleted(_) => None,
53 }
54 }
55
56 pub fn active_epoch_mut(&mut self) -> Option<&mut MergeActiveCircleEpoch> {
57 match self {
58 Self::ActiveEpoch(active) => Some(active),
59 Self::EpochClose(_) | Self::Deleted(_) => None,
60 }
61 }
62
63 pub fn is_deleted(&self) -> bool {
64 matches!(self, Self::Deleted(_))
65 }
66}
67
68#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
69#[serde(deny_unknown_fields)]
70pub struct MergeCircleControlHeadRef {
71 pub coord: CircleControlCoord,
72 pub head_hash: ObjectHash,
73 pub object: ExactObjectRef,
74}
75
76#[derive(Debug, Clone)]
84pub struct ResolvedConflictBranch {
85 pub control_head: MergeCircleControlHeadRef,
86 pub metadata_heads: Vec<CircleMetadataHeadRef>,
87 pub roster_heads: Vec<CircleRosterHeadRef>,
88 pub selected_metadata: CircleMetadata,
89}
90
91pub fn merge_frontier_head<H>(
97 frontier: &mut Vec<H>,
98 head: H,
99 stream_key: impl Fn(&H) -> CircleAuthorStreamKey,
100 seq: impl Fn(&H) -> u64,
101) {
102 let key = stream_key(&head);
103 match frontier
104 .iter_mut()
105 .find(|existing| stream_key(existing) == key)
106 {
107 Some(existing) if seq(&head) > seq(existing) => *existing = head,
108 Some(_) => {}
109 None => frontier.push(head),
110 }
111}
112
113#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
114#[serde(rename_all = "snake_case", deny_unknown_fields)]
115pub enum MergeCircleOwnerAuthorityRef {
116 Roster {
117 roster: MergeCircleRosterStateRef,
118 grant_id: MembershipGrantId,
119 created_at: crate::circle_roster::CircleRosterCoord,
120 },
121 ConflictResolution {
122 conflict_hash: ObjectHash,
123 resolution_hash: ObjectHash,
124 },
125}
126
127impl MergeCircleOwnerAuthorityRef {
128 pub(crate) fn grant_id(&self, author_pubkey: &str) -> MembershipGrantId {
129 match self {
130 Self::Roster { grant_id, .. } => grant_id.clone(),
131 Self::ConflictResolution { conflict_hash, .. } => {
132 crate::circle_roster::derive_circle_resolution_grant(conflict_hash, author_pubkey)
133 }
134 }
135 }
136}
137
138#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
139#[serde(deny_unknown_fields)]
140pub struct CircleControlValue {
141 pub order: MergeCircleControlOrder,
142 pub state: CircleControlState,
143 pub author_authority: MergeCircleOwnerAuthorityRef,
144 pub membership_authority: MembershipGrantCreationAuthority,
145}
146
147#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
149#[serde(deny_unknown_fields)]
150pub struct CircleControlBody {
151 pub store_root_hash: ObjectHash,
152 pub circle_id: CircleId,
153 pub value: CircleControlValue,
154 pub author_pubkey: String,
155}
156
157impl SignedBody for CircleControlBody {
158 const DOMAIN: &'static [u8] = CONTROL_DOMAIN;
159}
160
161pub type CircleControl = Signed<CircleControlBody>;
162
163impl CircleControlBody {
164 pub fn state(&self) -> &CircleControlState {
165 &self.value.state
166 }
167
168 pub fn active_epoch(&self) -> Option<&MergeActiveCircleEpoch> {
169 self.value.state.active_epoch()
170 }
171
172 pub fn access_epoch(&self) -> &MergeActiveCircleEpoch {
173 self.value.state.access_epoch()
174 }
175
176 pub fn active_common(&self) -> &ActiveCircleEpochCore {
177 &self.access_epoch().common
178 }
179
180 pub fn epoch_id(&self) -> CircleEpochId {
181 self.active_common().epoch_id
182 }
183
184 pub fn key_fingerprint(&self) -> KeyFingerprint {
185 self.active_common().key_fingerprint
186 }
187
188 pub fn owners(&self) -> &[String] {
189 &self.active_common().owners
190 }
191
192 pub(crate) fn access_root(&self) -> ObjectHash {
193 self.active_common().access_root
194 }
195
196 pub fn roster_state_ref(&self) -> CircleRosterStateRef {
197 self.access_epoch().roster.clone()
198 }
199
200 pub fn metadata_state_ref(&self) -> CircleMetadataStateRef {
201 self.access_epoch().metadata.clone()
202 }
203
204 pub fn store_membership_state_ref(&self) -> StoreMembershipStateRef {
205 self.access_epoch().store_membership.clone()
206 }
207
208 pub fn previous_control_hash(&self) -> Option<ObjectHash> {
209 self.value.order.previous_control_hash
210 }
211
212 pub fn is_founder(&self) -> bool {
213 self.value.order.seq == 1
214 && self.value.order.previous_control_hash.is_none()
215 && self.value.order.dependencies.is_empty()
216 }
217
218 pub(crate) fn ordinal(&self) -> u64 {
219 self.value.order.seq
220 }
221
222 pub fn author_grant_id(&self) -> MembershipGrantId {
223 self.value.author_authority.grant_id(&self.author_pubkey)
224 }
225
226 #[cfg(any(test, feature = "test-utils"))]
227 pub fn membership_authority(&self) -> &MembershipGrantCreationAuthority {
228 &self.value.membership_authority
229 }
230}
231
232impl CircleControl {
233 pub fn control_hash(&self) -> ObjectHash {
234 self.hash()
235 }
236
237 pub fn causally_covers(&self, prior: &Self) -> bool {
238 if self.store_root_hash != prior.store_root_hash || self.circle_id != prior.circle_id {
239 return false;
240 }
241 self.value.order.previous_control_hash == Some(prior.control_hash())
242 || self
243 .value
244 .order
245 .dependencies
246 .binary_search(&prior.coord())
247 .is_ok()
248 }
249
250 pub fn verify(&self) -> bool {
251 let order = &self.value.order;
252 let access_epoch = self.access_epoch();
253 let author_authority = &self.value.author_authority;
254 let grant_id = author_authority.grant_id(&self.author_pubkey);
255 let stream_key = CircleAuthorStreamKey {
256 author_pubkey: self.author_pubkey.clone(),
257 device_id: order.device_id.clone(),
258 stream_id: order.stream_id,
259 author_owner_grant: order.author_owner_grant.clone(),
260 };
261 let covered_are_canonical = access_epoch
262 .covered_control_heads
263 .windows(2)
264 .all(|pair| pair[0].coord.stream_key() < pair[1].coord.stream_key());
265 let own_predecessor = access_epoch
266 .covered_control_heads
267 .iter()
268 .find(|head| head.coord.stream_key() == stream_key);
269 let expected_dependencies = access_epoch
270 .covered_control_heads
271 .iter()
272 .filter(|head| head.coord.stream_key() != stream_key)
273 .map(|head| head.coord.clone())
274 .collect::<Vec<_>>();
275 let order_is_valid = !order.device_id.is_empty()
276 && order.seq > 0
277 && order.author_owner_grant == grant_id
278 && covered_are_canonical
279 && order.dependencies == expected_dependencies;
280 let authority_is_founder_roster = matches!(
281 author_authority,
282 MergeCircleOwnerAuthorityRef::Roster { roster, .. }
283 if roster == &access_epoch.roster
284 );
285 let founder = order.seq == 1 && access_epoch.covered_control_heads.is_empty();
286 let continuity_is_valid = match (order.seq, own_predecessor) {
287 (1, None) => order.previous_control_hash.is_none(),
288 (seq, Some(predecessor)) if seq > 1 => {
289 predecessor.coord.seq.checked_add(1) == Some(seq)
290 && order.previous_control_hash == Some(predecessor.coord.control_hash)
291 }
292 _ => false,
293 };
294 let founder_identity_is_valid = !founder
295 || (authority_is_founder_roster
296 && self.circle_id
297 == CircleId::founder(self.store_root_hash, &self.author_pubkey, &grant_id));
298 let common = &access_epoch.common;
299 let owners_are_canonical =
300 !common.owners.is_empty() && common.owners.windows(2).all(|pair| pair[0] < pair[1]);
301 let origin_is_valid = match &common.origin {
302 CircleEpochOrigin::Founder => true,
303 CircleEpochOrigin::Closed { cutoff, .. } => {
304 crate::store_commit::validate_commit_frontier(cutoff).is_ok()
305 }
306 };
307 let state_is_valid = match &self.value.state {
308 CircleControlState::ActiveEpoch(_) => true,
309 CircleControlState::EpochClose(close) => !founder && close.verify_shape(self.circle_id),
310 CircleControlState::Deleted(_) => !founder,
314 };
315 owners_are_canonical
316 && origin_is_valid
317 && state_is_valid
318 && order_is_valid
319 && continuity_is_valid
320 && founder_identity_is_valid
321 && self.verify_by(&self.author_pubkey).is_ok()
322 }
323
324 pub fn coord(&self) -> CircleControlCoord {
325 let order = &self.value.order;
326 CircleControlCoord {
327 device_id: order.device_id.clone(),
328 stream_id: order.stream_id,
329 author_pubkey: self.author_pubkey.clone(),
330 author_owner_grant: order.author_owner_grant.clone(),
331 seq: order.seq,
332 control_hash: self.control_hash(),
333 }
334 }
335}
336
337#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
339#[serde(deny_unknown_fields)]
340pub struct CircleControlHeadBody {
341 pub store_root_hash: ObjectHash,
342 pub circle_id: CircleId,
343 pub control: CircleControlCoord,
344 pub entry: ExactObjectRef,
345 pub successor: SuccessorLink,
346}
347
348impl SignedBody for CircleControlHeadBody {
349 const DOMAIN: &'static [u8] = CONTROL_HEAD_DOMAIN;
350}
351
352pub type CircleControlHead = Signed<CircleControlHeadBody>;
353
354impl CircleControlHead {
355 pub fn signed(
356 control: &CircleControl,
357 entry: ExactObjectRef,
358 successor: SuccessorLink,
359 signer: &UserKeypair,
360 ) -> Self {
361 Signed::sign(
362 CircleControlHeadBody {
363 store_root_hash: control.store_root_hash,
364 circle_id: control.circle_id,
365 control: control.coord(),
366 entry,
367 successor,
368 },
369 signer,
370 )
371 }
372
373 pub fn head_hash(&self) -> ObjectHash {
374 self.hash()
375 }
376
377 pub fn verify(&self, registration: &StoreDeviceRegistration) -> bool {
378 self.control.validate().is_ok()
379 && self.control.device_id == registration.device_id.to_string()
380 && self.verify_by(®istration.device_signing_pubkey).is_ok()
381 }
382}
383
384#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
387#[serde(deny_unknown_fields)]
388pub struct AccessEnvelopeBody {
389 pub store_root_hash: ObjectHash,
390 pub candidate_family: crate::store_commit::CandidateFamilyId,
391 pub circle_id: CircleId,
392 pub owner_pubkey: String,
393 pub recipient_slot: String,
394 pub control_hash: ObjectHash,
395 pub leaf_id: AccessLeafId,
396 pub leaf_hash: ObjectHash,
397 pub value_hash: ObjectHash,
398 pub proof: Vec<MerkleStep>,
399}
400
401impl SignedBody for AccessEnvelopeBody {
402 const DOMAIN: &'static [u8] = ENVELOPE_DOMAIN;
403}
404
405pub type AccessEnvelope = Signed<AccessEnvelopeBody>;
406
407impl AccessEnvelope {
408 pub fn verify(
409 &self,
410 control: &PreparedCircleControl,
411 candidate_family: crate::store_commit::CandidateFamilyId,
412 ) -> bool {
413 self.store_root_hash == control.value.store_root_hash
414 && self.candidate_family == candidate_family
415 && self.circle_id == control.value.circle_id
416 && self.owner_pubkey == control.value.author_pubkey
417 && self.control_hash == control.coord.control_hash()
418 && self.verify_by(&self.owner_pubkey).is_ok()
419 && verify_merkle_proof(self.leaf_hash, &self.proof, control.value.access_root())
420 }
421}