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 MembershipChange::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.resolution_dependencies.is_empty()
59 || founder.provider_admin.is_some()
60 || !verify_membership_entry(founder)
61 {
62 return Err(StoreProtocolError::InvalidFounder);
63 }
64 Ok(())
65 }
66}
67
68#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
70#[serde(deny_unknown_fields)]
71pub struct StoreProtocolRootBody {
72 pub descriptor: StoreCreationDescriptor,
73}
74
75impl SignedBody for StoreProtocolRootBody {
76 const DOMAIN: &'static [u8] = STORE_PROTOCOL_ROOT_DOMAIN;
77}
78
79pub type StoreProtocolRoot = Signed<StoreProtocolRootBody>;
80
81impl StoreProtocolRoot {
82 pub fn signed(
83 descriptor: StoreCreationDescriptor,
84 signer: &UserKeypair,
85 ) -> Result<Self, StoreProtocolError> {
86 let body = StoreProtocolRootBody { descriptor };
87 body.validate_descriptor()?;
88 if keys::public_key_hex(signer) != body.descriptor.founder_pubkey {
89 return Err(StoreProtocolError::InvalidSignature);
90 }
91 Ok(Signed::sign(body, signer))
92 }
93
94 pub fn object_hash(&self) -> ObjectHash {
95 self.hash()
96 }
97
98 pub fn parse(bytes: &[u8]) -> Result<Self, StoreProtocolError> {
99 let store_protocol_root: Self = crate::objects::decode_protocol_object(bytes)?;
100 store_protocol_root.body().validate_descriptor()?;
101 let founder_pubkey = store_protocol_root.descriptor.founder_pubkey.clone();
102 store_protocol_root.verify_by(&founder_pubkey)?;
103 Ok(store_protocol_root)
104 }
105
106 pub fn parse_expected(
107 bytes: &[u8],
108 expected: &StoreRootRef,
109 expected_sync_routing_hash: ObjectHash,
110 ) -> Result<Self, StoreProtocolError> {
111 let store_protocol_root = Self::parse_pinned(bytes, expected)?;
112 if store_protocol_root.descriptor.sync_routing_hash != expected_sync_routing_hash {
113 return Err(StoreProtocolError::SyncRoutingMismatch {
114 expected: expected_sync_routing_hash,
115 actual: store_protocol_root.descriptor.sync_routing_hash,
116 });
117 }
118 Ok(store_protocol_root)
119 }
120
121 pub fn parse_pinned(bytes: &[u8], expected: &StoreRootRef) -> Result<Self, StoreProtocolError> {
122 let store_protocol_root = Self::parse(bytes)?;
123 let actual_hash = store_protocol_root.object_hash();
124 crate::objects::verify_store_root(expected.store_root_hash, actual_hash)?;
125 let actual_root_id = store_protocol_root.descriptor.store_root_id();
126 if actual_root_id != expected.store_root_id {
127 return Err(StoreProtocolError::StoreRootIdMismatch {
128 expected: expected.store_root_id,
129 actual: actual_root_id,
130 });
131 }
132 if expected.object.slot() != &store_protocol_root.descriptor.root_slot {
133 return Err(StoreProtocolError::RelocatedSlot {
134 expected: serde_json::to_string(&store_protocol_root.descriptor.root_slot)
135 .expect("Store root slot serialization cannot fail"),
136 actual: serde_json::to_string(expected.object.slot())
137 .expect("Store root slot serialization cannot fail"),
138 });
139 }
140 Ok(store_protocol_root)
141 }
142}
143
144impl StoreProtocolRootBody {
145 fn validate_descriptor(&self) -> Result<(), StoreProtocolError> {
146 let descriptor = &self.descriptor;
147 descriptor.provider.validate()?;
148 descriptor
149 .founder_provider_admin
150 .provider
151 .validate_for(&descriptor.provider)?;
152 descriptor.founder_provider_admin.capability.verify(
153 &descriptor.provider,
154 &descriptor.founder_provider_admin.provider,
155 )?;
156 if !matches!(
157 descriptor.founder_recovery,
158 GrantStreamAnchor::OwnerRecovery { .. }
159 ) {
160 return Err(StoreProtocolError::InvalidFounder);
161 }
162 if descriptor.founder_pubkey.is_empty()
163 || descriptor.root_slot.logical_key() != "store-v1/store-protocol-root.json"
164 || descriptor.current_publication_slot.logical_key()
165 != store_current_publication_logical_key()
166 || !matches!(
167 descriptor.founder_membership,
168 GrantStreamAnchor::StoreMembership { .. }
169 )
170 {
171 return Err(StoreProtocolError::InvalidFounder);
172 }
173 Ok(())
174 }
175}