coven_protocol/store_commit/
protocol_root.rs1use super::*;
2
3#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
4#[serde(rename_all = "snake_case", tag = "kind", content = "sealed")]
5pub enum StoreKeyConfirmation {
6 NotRequired,
7 Opaque(Vec<u8>),
8}
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(deny_unknown_fields)]
12pub struct StoreCreationDescriptor {
13 pub creation_id: StoreCreationId,
14 pub key_confirmation: StoreKeyConfirmation,
15 pub provider: crate::objects::StoreProviderBinding,
16 pub schema_version: u32,
17 pub sync_routing_hash: ObjectHash,
18 pub founder_pubkey: String,
19 pub founder_grant: MembershipGrantId,
20 pub root_slot: ObjectSlot,
21 pub current_publication_slot: ObjectSlot,
22 pub founder_registration: ObjectSlot,
23 pub founder_provider_admin: crate::provider::FounderProviderAdminGrant,
24 pub founder_membership: GrantStreamAnchor,
25 pub founder_recovery: GrantStreamAnchor,
26}
27
28impl StoreCreationDescriptor {
29 pub fn store_root_id(&self) -> ObjectHash {
30 ObjectHash::digest(&domain_json(b"coven.store-creation-descriptor.v1\0", self))
31 }
32
33 pub fn validate_merge_founder_entry(
34 &self,
35 founder: &MembershipEntry,
36 ) -> Result<(), StoreProtocolError> {
37 let StoreAuthorityChange::Founder {
38 creation_id,
39 owner_pubkey,
40 owner_grant_id,
41 membership,
42 provider_admin,
43 } = &founder.change
44 else {
45 return Err(StoreProtocolError::InvalidFounder);
46 };
47 if founder.store_id != self.store_root_id().to_string()
48 || creation_id != &self.creation_id
49 || founder.author_pubkey != self.founder_pubkey
50 || founder.author_owner_grant != self.founder_grant
51 || owner_pubkey != &self.founder_pubkey
52 || owner_grant_id != &self.founder_grant
53 || membership != &self.founder_membership
54 || provider_admin != &self.founder_provider_admin
55 || founder.seq != 1
56 || founder.previous_hash.is_some()
57 || !founder.dependencies.is_empty()
58 || founder.provider_admin.is_some()
59 || !verify_membership_entry(founder)
60 {
61 return Err(StoreProtocolError::InvalidFounder);
62 }
63 Ok(())
64 }
65}
66
67#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
69#[serde(deny_unknown_fields)]
70pub struct StoreProtocolRootBody {
71 pub descriptor: StoreCreationDescriptor,
72}
73
74impl SignedBody for StoreProtocolRootBody {
75 const DOMAIN: &'static [u8] = STORE_PROTOCOL_ROOT_DOMAIN;
76}
77
78pub type StoreProtocolRoot = Signed<StoreProtocolRootBody>;
79
80impl StoreProtocolRoot {
81 pub fn signed(
82 descriptor: StoreCreationDescriptor,
83 signer: &UserKeypair,
84 ) -> Result<Self, StoreProtocolError> {
85 let body = StoreProtocolRootBody { descriptor };
86 body.validate_descriptor()?;
87 if keys::public_key_hex(signer) != body.descriptor.founder_pubkey {
88 return Err(StoreProtocolError::InvalidSignature);
89 }
90 Ok(Signed::sign(body, signer))
91 }
92
93 pub fn object_hash(&self) -> ObjectHash {
94 self.hash()
95 }
96
97 pub fn parse(bytes: &[u8]) -> Result<Self, StoreProtocolError> {
98 let store_protocol_root: Self = crate::objects::decode_protocol_object(bytes)?;
99 store_protocol_root.body().validate_descriptor()?;
100 let founder_pubkey = store_protocol_root.descriptor.founder_pubkey.clone();
101 store_protocol_root.verify_by(&founder_pubkey)?;
102 Ok(store_protocol_root)
103 }
104
105 pub fn parse_expected(
106 bytes: &[u8],
107 expected: &StoreRootRef,
108 expected_sync_routing_hash: ObjectHash,
109 ) -> Result<Self, StoreProtocolError> {
110 let store_protocol_root = Self::parse_pinned(bytes, expected)?;
111 if store_protocol_root.descriptor.sync_routing_hash != expected_sync_routing_hash {
112 return Err(StoreProtocolError::SyncRoutingMismatch {
113 expected: expected_sync_routing_hash,
114 actual: store_protocol_root.descriptor.sync_routing_hash,
115 });
116 }
117 Ok(store_protocol_root)
118 }
119
120 pub fn parse_pinned(bytes: &[u8], expected: &StoreRootRef) -> Result<Self, StoreProtocolError> {
121 let store_protocol_root = Self::parse(bytes)?;
122 let actual_hash = store_protocol_root.object_hash();
123 crate::objects::verify_store_root(expected.store_root_hash, actual_hash)?;
124 let actual_root_id = store_protocol_root.descriptor.store_root_id();
125 if actual_root_id != expected.store_root_id {
126 return Err(StoreProtocolError::StoreRootIdMismatch {
127 expected: expected.store_root_id,
128 actual: actual_root_id,
129 });
130 }
131 if expected.object.slot() != &store_protocol_root.descriptor.root_slot {
132 return Err(StoreProtocolError::RelocatedSlot {
133 expected: serde_json::to_string(&store_protocol_root.descriptor.root_slot)
134 .expect("Store root slot serialization cannot fail"),
135 actual: serde_json::to_string(expected.object.slot())
136 .expect("Store root slot serialization cannot fail"),
137 });
138 }
139 Ok(store_protocol_root)
140 }
141}
142
143impl StoreProtocolRootBody {
144 fn validate_descriptor(&self) -> Result<(), StoreProtocolError> {
145 let descriptor = &self.descriptor;
146 descriptor.provider.validate()?;
147 descriptor
148 .founder_provider_admin
149 .provider
150 .validate_for(&descriptor.provider)?;
151 descriptor.founder_provider_admin.capability.verify(
152 &descriptor.provider,
153 &descriptor.founder_provider_admin.provider,
154 )?;
155 if !matches!(
156 descriptor.founder_recovery,
157 GrantStreamAnchor::OwnerRecovery { .. }
158 ) {
159 return Err(StoreProtocolError::InvalidFounder);
160 }
161 if descriptor.founder_pubkey.is_empty()
162 || descriptor.root_slot.logical_key() != "store-v1/store-protocol-root.json"
163 || descriptor.current_publication_slot.logical_key()
164 != store_current_publication_logical_key()
165 || !matches!(
166 descriptor.founder_membership,
167 GrantStreamAnchor::StoreMembership { .. }
168 )
169 {
170 return Err(StoreProtocolError::InvalidFounder);
171 }
172 Ok(())
173 }
174}