Skip to main content

coven_protocol/membership/
entry.rs

1use super::*;
2
3#[cfg(any(test, feature = "test-utils"))]
4pub fn test_wrapped_key_ref(
5    owner_pubkey: &str,
6    recipient_pubkey: &str,
7    generation: u64,
8    label: &[u8],
9) -> WrappedStoreKeyRef {
10    let wrap_hash = ObjectHash::digest(
11        &[
12            label,
13            owner_pubkey.as_bytes(),
14            recipient_pubkey.as_bytes(),
15            &generation.to_le_bytes(),
16        ]
17        .concat(),
18    );
19    let logical_key =
20        format!("keys/{owner_pubkey}/{recipient_pubkey}/{generation}/{wrap_hash}.json");
21    WrappedStoreKeyRef {
22        owner_pubkey: owner_pubkey.to_string(),
23        recipient_pubkey: recipient_pubkey.to_string(),
24        generation,
25        wrap_hash,
26        object: ExactObjectRef::new(
27            crate::objects::ObjectSlot::logical(logical_key)
28                .expect("test wrapped-key slot is valid"),
29            label.len() as u64,
30            ObjectHash::digest(label),
31        ),
32    }
33}
34
35pub fn derive_founder_stream_id(store_id: &str, owner_pubkey: &str) -> AuthorStreamId {
36    AuthorStreamId::from_digest(ObjectHash::digest(
37        format!("coven.membership-founder-stream.v1\0{store_id}\0{owner_pubkey}").as_bytes(),
38    ))
39}
40
41pub(super) fn store_membership_anchor_stream(
42    owner_pubkey: &str,
43    owner_grant: &MembershipGrantId,
44    anchor: &GrantStreamAnchor,
45) -> Option<AuthorStreamId> {
46    let GrantStreamAnchor::StoreMembership { first_slot } = anchor else {
47        return None;
48    };
49    let prefix = format!(
50        "{}{owner_pubkey}/{owner_grant}/",
51        crate::store_commit::STORE_MEMBERSHIP_HEAD_PREFIX,
52    );
53    first_slot
54        .logical_key()
55        .strip_prefix(&prefix)?
56        .strip_suffix("/1.json")?
57        .parse()
58        .ok()
59}
60
61impl MembershipStreamKey {
62    /// Resolve the Store membership stream named by this exact grant anchor.
63    pub fn from_anchor(
64        author_pubkey: &str,
65        author_owner_grant: &MembershipGrantId,
66        anchor: &GrantStreamAnchor,
67    ) -> Option<Self> {
68        Some(Self {
69            author_pubkey: author_pubkey.to_string(),
70            author_owner_grant: author_owner_grant.clone(),
71            stream_id: store_membership_anchor_stream(author_pubkey, author_owner_grant, anchor)?,
72        })
73    }
74}
75
76pub fn derive_grant_id(
77    store_id: &str,
78    author_pubkey: &str,
79    author_grant: &MembershipGrantId,
80    stream_id: AuthorStreamId,
81    seq: u64,
82    user_pubkey: &str,
83) -> MembershipGrantId {
84    MembershipGrantId(ObjectHash::digest(
85        format!(
86            "coven.membership-grant.v1\0{store_id}\0{author_pubkey}\0{author_grant}\0{stream_id}\0{seq}\0{user_pubkey}"
87        )
88        .as_bytes(),
89    ))
90}
91
92pub fn founder_entry_for_creation(
93    store_id: &str,
94    creation_id: StoreCreationId,
95    owner: &UserKeypair,
96    owner_grant_id: MembershipGrantId,
97    created_at: &str,
98    membership: GrantStreamAnchor,
99    provider_admin: crate::provider::FounderProviderAdminGrant,
100) -> MembershipEntry {
101    let owner_pubkey = keys::public_key_hex(owner);
102    let stream_id = derive_founder_stream_id(store_id, &owner_pubkey);
103    Signed::sign(
104        MembershipEntryBody {
105            store_id: store_id.to_string(),
106            author_pubkey: owner_pubkey.clone(),
107            author_owner_grant: owner_grant_id.clone(),
108            stream_id,
109            seq: 1,
110            previous_hash: None,
111            dependencies: Vec::new(),
112            created_at: created_at.to_string(),
113            change: StoreAuthorityChange::Founder {
114                creation_id,
115                owner_pubkey,
116                owner_grant_id,
117                membership,
118                provider_admin,
119            },
120            provider_admin: None,
121        },
122        owner,
123    )
124}
125
126#[cfg(any(test, feature = "test-utils"))]
127pub fn founder_entry(
128    store_id: &str,
129    owner: &UserKeypair,
130    owner_grant_id: MembershipGrantId,
131    created_at: &str,
132    membership: GrantStreamAnchor,
133    provider_admin: crate::provider::FounderProviderAdminGrant,
134) -> MembershipEntry {
135    founder_entry_for_creation(
136        store_id,
137        StoreCreationId::from_nonce(store_id),
138        owner,
139        owner_grant_id,
140        created_at,
141        membership,
142        provider_admin,
143    )
144}
145
146pub fn verify_membership_entry(entry: &MembershipEntry) -> bool {
147    entry.verify_by(&entry.author_pubkey).is_ok()
148}