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