Skip to main content

coven_protocol/circle_control/
metadata.rs

1use 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/// The wire body of one Circle metadata entry. Every field here is signed.
26#[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/// The wire body of one Circle metadata head. Every field here is signed.
124#[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(&registration.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;