coven_protocol/membership/
entry.rs1use 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 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}