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 && position_is_valid
115 && self
116 .dependencies
117 .windows(2)
118 .all(|pair| pair[0].stream_key() < pair[1].stream_key())
119 && self.verify_by(&self.author_pubkey).is_ok()
120 }
121}
122
123#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
125#[serde(deny_unknown_fields)]
126pub struct CircleMetadataHeadBody {
127 pub store_root_hash: ObjectHash,
128 pub circle_id: CircleId,
129 pub author_pubkey: String,
130 pub device_id: String,
131 pub stream_id: AuthorStreamId,
132 pub author_owner_grant: MembershipGrantId,
133 pub seq: u64,
134 pub tip_hash: ObjectHash,
135 pub tip: ExactObjectRef,
136 pub successor: SuccessorLink,
137}
138
139impl SignedBody for CircleMetadataHeadBody {
140 const DOMAIN: &'static [u8] = METADATA_HEAD_DOMAIN;
141}
142
143pub type CircleMetadataHead = Signed<CircleMetadataHeadBody>;
144
145impl CircleMetadataHead {
146 pub fn signed(
147 metadata: &CircleMetadata,
148 tip: ExactObjectRef,
149 successor: SuccessorLink,
150 signer: &UserKeypair,
151 ) -> Self {
152 Signed::sign(
153 CircleMetadataHeadBody {
154 store_root_hash: metadata.store_root_hash,
155 circle_id: metadata.circle_id,
156 author_pubkey: metadata.author_pubkey.clone(),
157 device_id: metadata.device_id.clone(),
158 stream_id: metadata.stream_id,
159 author_owner_grant: metadata.author_owner_grant.clone(),
160 seq: metadata.seq,
161 tip_hash: metadata.metadata_hash(),
162 tip,
163 successor,
164 },
165 signer,
166 )
167 }
168
169 pub fn head_hash(&self) -> ObjectHash {
170 self.hash()
171 }
172
173 pub fn coord(&self) -> CircleMetadataCoord {
174 CircleMetadataCoord {
175 author_pubkey: self.author_pubkey.clone(),
176 device_id: self.device_id.clone(),
177 stream_id: self.stream_id,
178 author_owner_grant: self.author_owner_grant.clone(),
179 seq: self.seq,
180 metadata_hash: self.tip_hash,
181 }
182 }
183
184 pub fn verify_for_registration(&self, registration: &StoreDeviceRegistration) -> bool {
185 self.seq > 0
186 && !self.device_id.is_empty()
187 && self.device_id == registration.device_id.to_string()
188 && self.verify_by(®istration.device_signing_pubkey).is_ok()
189 }
190}
191
192#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
193#[serde(deny_unknown_fields)]
194pub struct CircleMetadataHeadRef {
195 pub coord: CircleMetadataCoord,
196 pub head_hash: ObjectHash,
197 pub object: ExactObjectRef,
198}
199
200impl CircleMetadataHeadRef {
201 pub fn from_stored_head(head: &CircleMetadataHead, object: ExactObjectRef) -> Self {
202 Self {
203 coord: head.coord(),
204 head_hash: head.head_hash(),
205 object,
206 }
207 }
208}
209
210#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
211#[serde(deny_unknown_fields)]
212pub struct MergeCircleMetadataStateRef {
213 pub heads: Vec<CircleMetadataHeadRef>,
214 pub selected: CircleMetadataCoord,
215 pub state_hash: ObjectHash,
216}
217
218pub type CircleMetadataStateRef = MergeCircleMetadataStateRef;