Skip to main content

coven_protocol/store_commit/
protocol_root.rs

1use 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/// The wire body of a Store's protocol root. Every field here is signed.
68#[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}