coven_protocol/circle_control/
metadata.rs1use super::*;
2
3#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
4#[serde(deny_unknown_fields)]
5pub struct CircleMetadataCoord {
6 pub author_pubkey: String,
7 pub device_id: String,
8 pub stream_id: AuthorStreamId,
9 pub author_owner_grant: MembershipGrantId,
10 pub seq: u64,
11 pub metadata_hash: ObjectHash,
12}
13
14impl CircleMetadataCoord {
15 pub fn stream_key(&self) -> CircleAuthorStreamKey {
16 CircleAuthorStreamKey {
17 author_pubkey: self.author_pubkey.clone(),
18 device_id: self.device_id.clone(),
19 stream_id: self.stream_id,
20 author_owner_grant: self.author_owner_grant.clone(),
21 }
22 }
23}
24
25#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
27#[serde(deny_unknown_fields)]
28pub struct CircleMetadataBody {
29 pub store_root_hash: ObjectHash,
30 pub circle_id: CircleId,
31 pub epoch_id: CircleEpochId,
32 pub name: String,
33 pub seq: u64,
34 pub previous_hash: Option<ObjectHash>,
35 pub dependencies: Vec<CircleMetadataCoord>,
36 pub metadata_stamp: String,
37 pub author_pubkey: String,
38 pub device_id: String,
39 pub stream_id: AuthorStreamId,
40 pub author_owner_grant: MembershipGrantId,
41 pub author_roster: CircleRosterStateRef,
42 pub key_fingerprint: KeyFingerprint,
43}
44
45impl SignedBody for CircleMetadataBody {
46 const DOMAIN: &'static [u8] = METADATA_DOMAIN;
47}
48
49pub type CircleMetadata = Signed<CircleMetadataBody>;
50
51impl CircleMetadata {
52 pub(super) fn founder(
53 store_root_hash: ObjectHash,
54 circle_id: CircleId,
55 epoch_id: CircleEpochId,
56 name: &str,
57 metadata_stamp: &str,
58 device_id: &str,
59 stream_id: AuthorStreamId,
60 owner_grant: MembershipGrantId,
61 author_roster: CircleRosterStateRef,
62 key_fingerprint: KeyFingerprint,
63 signer: &dyn coven_keys::keys::IdentityKeyAuthority,
64 ) -> Result<Self, CircleTransitionError> {
65 if name.trim().is_empty() {
66 return Err(CircleTransitionError::EmptyName);
67 }
68 Ok(Signed::sign(
69 CircleMetadataBody {
70 store_root_hash,
71 circle_id,
72 epoch_id,
73 name: name.to_string(),
74 seq: 1,
75 previous_hash: None,
76 dependencies: Vec::new(),
77 metadata_stamp: metadata_stamp.to_string(),
78 author_pubkey: keys::public_key_hex(signer),
79 device_id: device_id.to_string(),
80 stream_id,
81 author_owner_grant: owner_grant,
82 author_roster,
83 key_fingerprint,
84 },
85 signer,
86 ))
87 }
88
89 pub fn metadata_hash(&self) -> ObjectHash {
90 self.hash()
91 }
92
93 pub fn coord(&self) -> CircleMetadataCoord {
94 CircleMetadataCoord {
95 author_pubkey: self.author_pubkey.clone(),
96 device_id: self.device_id.clone(),
97 stream_id: self.stream_id,
98 author_owner_grant: self.author_owner_grant.clone(),
99 seq: self.seq,
100 metadata_hash: self.metadata_hash(),
101 }
102 }
103
104 pub fn verify(&self) -> bool {
105 let position_is_valid = crate::causal_grants::author_stream_position_is_valid(
106 self.seq,
107 self.previous_hash,
108 &self.coord().stream_key(),
109 self.dependencies
110 .iter()
111 .map(CircleMetadataCoord::stream_key),
112 );
113 !self.name.trim().is_empty()
114 && crate::hlc::Timestamp::parse(&self.metadata_stamp)
115 .is_some_and(|stamp| stamp.to_string() == self.metadata_stamp)
116 && position_is_valid
117 && self
118 .dependencies
119 .windows(2)
120 .all(|pair| pair[0].stream_key() < pair[1].stream_key())
121 && self.verify_by(&self.author_pubkey).is_ok()
122 }
123}
124
125#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
127#[serde(deny_unknown_fields)]
128pub struct CircleMetadataHeadBody {
129 pub store_root_hash: ObjectHash,
130 pub circle_id: CircleId,
131 pub author_pubkey: String,
132 pub device_id: String,
133 pub stream_id: AuthorStreamId,
134 pub author_owner_grant: MembershipGrantId,
135 pub seq: u64,
136 pub tip_hash: ObjectHash,
137 pub tip: ExactObjectRef,
138 pub successor: SuccessorLink,
139}
140
141impl SignedBody for CircleMetadataHeadBody {
142 const DOMAIN: &'static [u8] = METADATA_HEAD_DOMAIN;
143}
144
145pub type CircleMetadataHead = Signed<CircleMetadataHeadBody>;
146
147impl CircleMetadataHead {
148 pub fn signed(
149 metadata: &CircleMetadata,
150 tip: ExactObjectRef,
151 successor: SuccessorLink,
152 signer: &UserKeypair,
153 ) -> Self {
154 Signed::sign(
155 CircleMetadataHeadBody {
156 store_root_hash: metadata.store_root_hash,
157 circle_id: metadata.circle_id,
158 author_pubkey: metadata.author_pubkey.clone(),
159 device_id: metadata.device_id.clone(),
160 stream_id: metadata.stream_id,
161 author_owner_grant: metadata.author_owner_grant.clone(),
162 seq: metadata.seq,
163 tip_hash: metadata.metadata_hash(),
164 tip,
165 successor,
166 },
167 signer,
168 )
169 }
170
171 pub fn head_hash(&self) -> ObjectHash {
172 self.hash()
173 }
174
175 pub fn coord(&self) -> CircleMetadataCoord {
176 CircleMetadataCoord {
177 author_pubkey: self.author_pubkey.clone(),
178 device_id: self.device_id.clone(),
179 stream_id: self.stream_id,
180 author_owner_grant: self.author_owner_grant.clone(),
181 seq: self.seq,
182 metadata_hash: self.tip_hash,
183 }
184 }
185
186 pub fn verify_for_registration(&self, registration: &StoreDeviceRegistration) -> bool {
187 self.seq > 0
188 && !self.device_id.is_empty()
189 && self.device_id == registration.device_id.to_string()
190 && self.verify_by(®istration.device_signing_pubkey).is_ok()
191 }
192}
193
194#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
195#[serde(deny_unknown_fields)]
196pub struct CircleMetadataHeadRef {
197 pub coord: CircleMetadataCoord,
198 pub head_hash: ObjectHash,
199 pub object: ExactObjectRef,
200}
201
202impl CircleMetadataHeadRef {
203 pub fn from_stored_head(head: &CircleMetadataHead, object: ExactObjectRef) -> Self {
204 Self {
205 coord: head.coord(),
206 head_hash: head.head_hash(),
207 object,
208 }
209 }
210}
211
212#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
213#[serde(deny_unknown_fields)]
214pub struct MergeCircleMetadataStateRef {
215 pub heads: Vec<CircleMetadataHeadRef>,
216 pub selected: CircleMetadataCoord,
217 pub state_hash: ObjectHash,
218}
219
220pub type CircleMetadataStateRef = MergeCircleMetadataStateRef;