@snaha/swarm-id 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/README.md +431 -0
  2. package/dist/chunk/bmt.d.ts +17 -0
  3. package/dist/chunk/bmt.d.ts.map +1 -0
  4. package/dist/chunk/cac.d.ts +18 -0
  5. package/dist/chunk/cac.d.ts.map +1 -0
  6. package/dist/chunk/constants.d.ts +10 -0
  7. package/dist/chunk/constants.d.ts.map +1 -0
  8. package/dist/chunk/encrypted-cac.d.ts +48 -0
  9. package/dist/chunk/encrypted-cac.d.ts.map +1 -0
  10. package/dist/chunk/encryption.d.ts +86 -0
  11. package/dist/chunk/encryption.d.ts.map +1 -0
  12. package/dist/chunk/index.d.ts +6 -0
  13. package/dist/chunk/index.d.ts.map +1 -0
  14. package/dist/index.d.ts +46 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/proxy/act/act.d.ts +78 -0
  17. package/dist/proxy/act/act.d.ts.map +1 -0
  18. package/dist/proxy/act/crypto.d.ts +44 -0
  19. package/dist/proxy/act/crypto.d.ts.map +1 -0
  20. package/dist/proxy/act/grantee-list.d.ts +82 -0
  21. package/dist/proxy/act/grantee-list.d.ts.map +1 -0
  22. package/dist/proxy/act/history.d.ts +183 -0
  23. package/dist/proxy/act/history.d.ts.map +1 -0
  24. package/dist/proxy/act/index.d.ts +104 -0
  25. package/dist/proxy/act/index.d.ts.map +1 -0
  26. package/dist/proxy/chunking-encrypted.d.ts +14 -0
  27. package/dist/proxy/chunking-encrypted.d.ts.map +1 -0
  28. package/dist/proxy/chunking.d.ts +15 -0
  29. package/dist/proxy/chunking.d.ts.map +1 -0
  30. package/dist/proxy/download-data.d.ts +16 -0
  31. package/dist/proxy/download-data.d.ts.map +1 -0
  32. package/dist/proxy/feed-manifest.d.ts +62 -0
  33. package/dist/proxy/feed-manifest.d.ts.map +1 -0
  34. package/dist/proxy/feeds/epochs/async-finder.d.ts +77 -0
  35. package/dist/proxy/feeds/epochs/async-finder.d.ts.map +1 -0
  36. package/dist/proxy/feeds/epochs/epoch.d.ts +88 -0
  37. package/dist/proxy/feeds/epochs/epoch.d.ts.map +1 -0
  38. package/dist/proxy/feeds/epochs/finder.d.ts +67 -0
  39. package/dist/proxy/feeds/epochs/finder.d.ts.map +1 -0
  40. package/dist/proxy/feeds/epochs/index.d.ts +35 -0
  41. package/dist/proxy/feeds/epochs/index.d.ts.map +1 -0
  42. package/dist/proxy/feeds/epochs/test-utils.d.ts +93 -0
  43. package/dist/proxy/feeds/epochs/test-utils.d.ts.map +1 -0
  44. package/dist/proxy/feeds/epochs/types.d.ts +109 -0
  45. package/dist/proxy/feeds/epochs/types.d.ts.map +1 -0
  46. package/dist/proxy/feeds/epochs/updater.d.ts +68 -0
  47. package/dist/proxy/feeds/epochs/updater.d.ts.map +1 -0
  48. package/dist/proxy/feeds/epochs/utils.d.ts +22 -0
  49. package/dist/proxy/feeds/epochs/utils.d.ts.map +1 -0
  50. package/dist/proxy/feeds/index.d.ts +5 -0
  51. package/dist/proxy/feeds/index.d.ts.map +1 -0
  52. package/dist/proxy/feeds/sequence/async-finder.d.ts +14 -0
  53. package/dist/proxy/feeds/sequence/async-finder.d.ts.map +1 -0
  54. package/dist/proxy/feeds/sequence/finder.d.ts +17 -0
  55. package/dist/proxy/feeds/sequence/finder.d.ts.map +1 -0
  56. package/dist/proxy/feeds/sequence/index.d.ts +23 -0
  57. package/dist/proxy/feeds/sequence/index.d.ts.map +1 -0
  58. package/dist/proxy/feeds/sequence/types.d.ts +80 -0
  59. package/dist/proxy/feeds/sequence/types.d.ts.map +1 -0
  60. package/dist/proxy/feeds/sequence/updater.d.ts +26 -0
  61. package/dist/proxy/feeds/sequence/updater.d.ts.map +1 -0
  62. package/dist/proxy/index.d.ts +6 -0
  63. package/dist/proxy/index.d.ts.map +1 -0
  64. package/dist/proxy/manifest-builder.d.ts +183 -0
  65. package/dist/proxy/manifest-builder.d.ts.map +1 -0
  66. package/dist/proxy/mantaray-encrypted.d.ts +27 -0
  67. package/dist/proxy/mantaray-encrypted.d.ts.map +1 -0
  68. package/dist/proxy/mantaray.d.ts +26 -0
  69. package/dist/proxy/mantaray.d.ts.map +1 -0
  70. package/dist/proxy/types.d.ts +29 -0
  71. package/dist/proxy/types.d.ts.map +1 -0
  72. package/dist/proxy/upload-data.d.ts +17 -0
  73. package/dist/proxy/upload-data.d.ts.map +1 -0
  74. package/dist/proxy/upload-encrypted-data.d.ts +103 -0
  75. package/dist/proxy/upload-encrypted-data.d.ts.map +1 -0
  76. package/dist/schemas.d.ts +240 -0
  77. package/dist/schemas.d.ts.map +1 -0
  78. package/dist/storage/debounced-uploader.d.ts +62 -0
  79. package/dist/storage/debounced-uploader.d.ts.map +1 -0
  80. package/dist/storage/utilization-store.d.ts +108 -0
  81. package/dist/storage/utilization-store.d.ts.map +1 -0
  82. package/dist/swarm-id-auth.d.ts +74 -0
  83. package/dist/swarm-id-auth.d.ts.map +1 -0
  84. package/dist/swarm-id-auth.js +2 -0
  85. package/dist/swarm-id-auth.js.map +1 -0
  86. package/dist/swarm-id-client.d.ts +878 -0
  87. package/dist/swarm-id-client.d.ts.map +1 -0
  88. package/dist/swarm-id-client.js +2 -0
  89. package/dist/swarm-id-client.js.map +1 -0
  90. package/dist/swarm-id-proxy.d.ts +236 -0
  91. package/dist/swarm-id-proxy.d.ts.map +1 -0
  92. package/dist/swarm-id-proxy.js +2 -0
  93. package/dist/swarm-id-proxy.js.map +1 -0
  94. package/dist/swarm-id.esm.js +2 -0
  95. package/dist/swarm-id.esm.js.map +1 -0
  96. package/dist/swarm-id.umd.js +2 -0
  97. package/dist/swarm-id.umd.js.map +1 -0
  98. package/dist/sync/index.d.ts +9 -0
  99. package/dist/sync/index.d.ts.map +1 -0
  100. package/dist/sync/key-derivation.d.ts +25 -0
  101. package/dist/sync/key-derivation.d.ts.map +1 -0
  102. package/dist/sync/restore-account.d.ts +28 -0
  103. package/dist/sync/restore-account.d.ts.map +1 -0
  104. package/dist/sync/serialization.d.ts +16 -0
  105. package/dist/sync/serialization.d.ts.map +1 -0
  106. package/dist/sync/store-interfaces.d.ts +53 -0
  107. package/dist/sync/store-interfaces.d.ts.map +1 -0
  108. package/dist/sync/sync-account.d.ts +44 -0
  109. package/dist/sync/sync-account.d.ts.map +1 -0
  110. package/dist/sync/types.d.ts +13 -0
  111. package/dist/sync/types.d.ts.map +1 -0
  112. package/dist/test-fixtures.d.ts +17 -0
  113. package/dist/test-fixtures.d.ts.map +1 -0
  114. package/dist/types-BD_VkNn0.js +2 -0
  115. package/dist/types-BD_VkNn0.js.map +1 -0
  116. package/dist/types-lJCaT-50.js +2 -0
  117. package/dist/types-lJCaT-50.js.map +1 -0
  118. package/dist/types.d.ts +2157 -0
  119. package/dist/types.d.ts.map +1 -0
  120. package/dist/utils/account-payload.d.ts +94 -0
  121. package/dist/utils/account-payload.d.ts.map +1 -0
  122. package/dist/utils/account-state-snapshot.d.ts +38 -0
  123. package/dist/utils/account-state-snapshot.d.ts.map +1 -0
  124. package/dist/utils/backup-encryption.d.ts +127 -0
  125. package/dist/utils/backup-encryption.d.ts.map +1 -0
  126. package/dist/utils/batch-utilization.d.ts +432 -0
  127. package/dist/utils/batch-utilization.d.ts.map +1 -0
  128. package/dist/utils/constants.d.ts +11 -0
  129. package/dist/utils/constants.d.ts.map +1 -0
  130. package/dist/utils/hex.d.ts +17 -0
  131. package/dist/utils/hex.d.ts.map +1 -0
  132. package/dist/utils/key-derivation.d.ts +92 -0
  133. package/dist/utils/key-derivation.d.ts.map +1 -0
  134. package/dist/utils/storage-managers.d.ts +65 -0
  135. package/dist/utils/storage-managers.d.ts.map +1 -0
  136. package/dist/utils/swarm-id-export.d.ts +24 -0
  137. package/dist/utils/swarm-id-export.d.ts.map +1 -0
  138. package/dist/utils/ttl.d.ts +49 -0
  139. package/dist/utils/ttl.d.ts.map +1 -0
  140. package/dist/utils/url.d.ts +41 -0
  141. package/dist/utils/url.d.ts.map +1 -0
  142. package/dist/utils/versioned-storage.d.ts +131 -0
  143. package/dist/utils/versioned-storage.d.ts.map +1 -0
  144. package/package.json +78 -0
  145. package/src/chunk/bmt.test.ts +217 -0
  146. package/src/chunk/bmt.ts +57 -0
  147. package/src/chunk/cac.test.ts +214 -0
  148. package/src/chunk/cac.ts +65 -0
  149. package/src/chunk/constants.ts +18 -0
  150. package/src/chunk/encrypted-cac.test.ts +385 -0
  151. package/src/chunk/encrypted-cac.ts +131 -0
  152. package/src/chunk/encryption.test.ts +352 -0
  153. package/src/chunk/encryption.ts +300 -0
  154. package/src/chunk/index.ts +47 -0
  155. package/src/index.ts +430 -0
  156. package/src/proxy/act/act.test.ts +278 -0
  157. package/src/proxy/act/act.ts +158 -0
  158. package/src/proxy/act/bee-compat.test.ts +948 -0
  159. package/src/proxy/act/crypto.test.ts +436 -0
  160. package/src/proxy/act/crypto.ts +376 -0
  161. package/src/proxy/act/grantee-list.test.ts +393 -0
  162. package/src/proxy/act/grantee-list.ts +239 -0
  163. package/src/proxy/act/history.test.ts +360 -0
  164. package/src/proxy/act/history.ts +413 -0
  165. package/src/proxy/act/index.test.ts +748 -0
  166. package/src/proxy/act/index.ts +853 -0
  167. package/src/proxy/chunking-encrypted.ts +95 -0
  168. package/src/proxy/chunking.ts +65 -0
  169. package/src/proxy/download-data.ts +448 -0
  170. package/src/proxy/feed-manifest.ts +174 -0
  171. package/src/proxy/feeds/epochs/async-finder.ts +372 -0
  172. package/src/proxy/feeds/epochs/epoch.test.ts +249 -0
  173. package/src/proxy/feeds/epochs/epoch.ts +181 -0
  174. package/src/proxy/feeds/epochs/finder.ts +282 -0
  175. package/src/proxy/feeds/epochs/index.ts +73 -0
  176. package/src/proxy/feeds/epochs/integration.test.ts +1336 -0
  177. package/src/proxy/feeds/epochs/test-utils.ts +274 -0
  178. package/src/proxy/feeds/epochs/types.ts +128 -0
  179. package/src/proxy/feeds/epochs/updater.ts +192 -0
  180. package/src/proxy/feeds/epochs/utils.ts +62 -0
  181. package/src/proxy/feeds/index.ts +5 -0
  182. package/src/proxy/feeds/sequence/async-finder.ts +31 -0
  183. package/src/proxy/feeds/sequence/finder.ts +73 -0
  184. package/src/proxy/feeds/sequence/index.ts +54 -0
  185. package/src/proxy/feeds/sequence/integration.test.ts +966 -0
  186. package/src/proxy/feeds/sequence/types.ts +103 -0
  187. package/src/proxy/feeds/sequence/updater.ts +71 -0
  188. package/src/proxy/index.ts +5 -0
  189. package/src/proxy/manifest-builder.test.ts +427 -0
  190. package/src/proxy/manifest-builder.ts +679 -0
  191. package/src/proxy/mantaray-encrypted.ts +78 -0
  192. package/src/proxy/mantaray.ts +104 -0
  193. package/src/proxy/types.ts +32 -0
  194. package/src/proxy/upload-data.ts +189 -0
  195. package/src/proxy/upload-encrypted-data.ts +658 -0
  196. package/src/schemas.ts +299 -0
  197. package/src/storage/debounced-uploader.ts +192 -0
  198. package/src/storage/utilization-store.ts +397 -0
  199. package/src/swarm-id-client.test.ts +99 -0
  200. package/src/swarm-id-client.ts +3095 -0
  201. package/src/swarm-id-proxy.ts +3891 -0
  202. package/src/sync/index.ts +28 -0
  203. package/src/sync/restore-account.ts +90 -0
  204. package/src/sync/serialization.ts +39 -0
  205. package/src/sync/store-interfaces.ts +62 -0
  206. package/src/sync/sync-account.test.ts +302 -0
  207. package/src/sync/sync-account.ts +396 -0
  208. package/src/sync/types.ts +11 -0
  209. package/src/test-fixtures.ts +109 -0
  210. package/src/types.ts +1651 -0
  211. package/src/utils/account-state-snapshot.test.ts +595 -0
  212. package/src/utils/account-state-snapshot.ts +94 -0
  213. package/src/utils/backup-encryption.test.ts +442 -0
  214. package/src/utils/backup-encryption.ts +352 -0
  215. package/src/utils/batch-utilization.ts +1309 -0
  216. package/src/utils/constants.ts +20 -0
  217. package/src/utils/hex.ts +27 -0
  218. package/src/utils/key-derivation.ts +197 -0
  219. package/src/utils/storage-managers.ts +365 -0
  220. package/src/utils/ttl.ts +129 -0
  221. package/src/utils/url.test.ts +136 -0
  222. package/src/utils/url.ts +71 -0
  223. package/src/utils/versioned-storage.ts +323 -0
@@ -0,0 +1,853 @@
1
+ /**
2
+ * ACT (Access Control Tries) - Bee-Compatible Implementation
3
+ *
4
+ * This module provides client-side ACT operations with Bee-compatible
5
+ * Simple Manifest (JSON) format:
6
+ * - ACT manifest (JSON with lookup key -> encrypted access key mappings)
7
+ * - Encrypted grantee list (stored separately)
8
+ * - History manifest (tracks ACT versions over time)
9
+ */
10
+
11
+ import type { Bee, BeeRequestOptions, UploadOptions } from "@ethersphere/bee-js"
12
+ import type { UploadContext, UploadProgress } from "../types"
13
+ import { uploadEncryptedDataWithSigning } from "../upload-encrypted-data"
14
+ import { uploadDataWithSigning } from "../upload-data"
15
+ import { downloadDataWithChunkAPI } from "../download-data"
16
+ import { hexToUint8Array, uint8ArrayToHex } from "../../utils/hex"
17
+ import {
18
+ deriveKeys,
19
+ counterModeEncrypt,
20
+ counterModeDecrypt,
21
+ publicKeyFromPrivate,
22
+ generateRandomKey,
23
+ publicKeyFromCompressed,
24
+ compressPublicKey,
25
+ } from "./crypto"
26
+
27
+ type ActUploadOptions = UploadOptions & { beeCompatible?: boolean }
28
+ import {
29
+ serializeAct,
30
+ deserializeAct,
31
+ findEntryByLookupKey,
32
+ publicKeysEqual,
33
+ type ActEntry,
34
+ } from "./act"
35
+ import {
36
+ serializeAndEncryptGranteeList,
37
+ decryptAndDeserializeGranteeList,
38
+ type UncompressedPublicKey,
39
+ } from "./grantee-list"
40
+ import {
41
+ createHistoryManifest,
42
+ addHistoryEntry,
43
+ getLatestEntry,
44
+ getEntryAtTimestamp,
45
+ saveHistoryTreeRecursively,
46
+ deserializeHistory,
47
+ loadHistoryEntries,
48
+ getCurrentTimestamp,
49
+ } from "./history"
50
+
51
+ // Reference size constants
52
+ const REFERENCE_SIZE = 32
53
+
54
+ /**
55
+ * Result of ACT upload operation
56
+ */
57
+ export interface ActUploadResult {
58
+ encryptedReference: string // Encrypted content reference (NOT stored in ACT)
59
+ historyReference: string // History manifest reference (root of ACT versions)
60
+ granteeListReference: string // Encrypted grantee list reference
61
+ publisherPubKey: string // Compressed public key for sharing with grantees
62
+ actReference: string // Latest ACT reference (for convenience)
63
+ tagUid?: number
64
+ }
65
+
66
+ /**
67
+ * Result of ACT grantee modification
68
+ */
69
+ export interface ActGranteeModifyResult {
70
+ historyReference: string
71
+ granteeListReference: string
72
+ actReference: string
73
+ tagUid?: number
74
+ }
75
+
76
+ /**
77
+ * Result of ACT revocation (includes new encrypted reference due to key rotation)
78
+ */
79
+ export interface ActRevocationResult extends ActGranteeModifyResult {
80
+ encryptedReference: string // New encrypted reference after key rotation
81
+ }
82
+
83
+ /**
84
+ * Format decrypted reference - trim to 32 bytes if second half is all zeros
85
+ */
86
+ function formatDecryptedReference(decryptedRef: Uint8Array): string {
87
+ let isShortRef = true
88
+ for (let i = REFERENCE_SIZE; i < decryptedRef.length; i++) {
89
+ if (decryptedRef[i] !== 0) {
90
+ isShortRef = false
91
+ break
92
+ }
93
+ }
94
+
95
+ if (isShortRef) {
96
+ return uint8ArrayToHex(decryptedRef.slice(0, REFERENCE_SIZE))
97
+ }
98
+ return uint8ArrayToHex(decryptedRef)
99
+ }
100
+
101
+ /**
102
+ * Create an ACT-protected upload
103
+ *
104
+ * This creates:
105
+ * 1. ACT manifest (JSON Simple Manifest with lookup key -> encrypted access key mappings)
106
+ * 2. Encrypted grantee list (for publisher management)
107
+ * 3. History manifest (tracks ACT versions over time)
108
+ *
109
+ * @param context - Upload context with bee and stamper
110
+ * @param contentReference - The reference to protect (32 or 64 bytes)
111
+ * @param publisherPrivateKey - Publisher's private key (32 bytes)
112
+ * @param granteePublicKeys - Array of grantee public keys
113
+ * @param options - Upload options
114
+ * @param requestOptions - Bee request options
115
+ * @param onProgress - Progress callback
116
+ * @returns Multiple references for ACT
117
+ */
118
+ export async function createActForContent(
119
+ context: UploadContext,
120
+ contentReference: Uint8Array,
121
+ publisherPrivateKey: Uint8Array,
122
+ granteePublicKeys: Array<{ x: Uint8Array; y: Uint8Array }>,
123
+ options?: ActUploadOptions,
124
+ requestOptions?: BeeRequestOptions,
125
+ onProgress?: (progress: UploadProgress) => void,
126
+ ): Promise<ActUploadResult> {
127
+ // Generate random access key
128
+ const accessKey = generateRandomKey()
129
+
130
+ // Encrypt the content reference with the access key
131
+ // CTR mode preserves input length - 32-byte ref → 32-byte encrypted
132
+ const encryptedRef = counterModeEncrypt(contentReference, accessKey)
133
+
134
+ // Get publisher's public key
135
+ const publisherPubKey = publicKeyFromPrivate(publisherPrivateKey)
136
+
137
+ // Create entries for publisher and all grantees
138
+ const entries: ActEntry[] = []
139
+
140
+ // Entry for publisher (so they can decrypt their own content)
141
+ const publisherKeys = deriveKeys(
142
+ publisherPrivateKey,
143
+ publisherPubKey.x,
144
+ publisherPubKey.y,
145
+ )
146
+ entries.push({
147
+ lookupKey: publisherKeys.lookupKey,
148
+ encryptedAccessKey: counterModeEncrypt(
149
+ accessKey,
150
+ publisherKeys.accessKeyDecryptionKey,
151
+ ),
152
+ })
153
+
154
+ // Entry for each grantee
155
+ for (const granteePubKey of granteePublicKeys) {
156
+ const granteeKeys = deriveKeys(
157
+ publisherPrivateKey,
158
+ granteePubKey.x,
159
+ granteePubKey.y,
160
+ )
161
+ entries.push({
162
+ lookupKey: granteeKeys.lookupKey,
163
+ encryptedAccessKey: counterModeEncrypt(
164
+ accessKey,
165
+ granteeKeys.accessKeyDecryptionKey,
166
+ ),
167
+ })
168
+ }
169
+
170
+ // 1. Serialize and upload ACT manifest (JSON Simple Manifest format)
171
+ const actJson = serializeAct(entries)
172
+
173
+ const beeCompatible = options?.beeCompatible === true
174
+
175
+ const actResult = beeCompatible
176
+ ? await uploadDataWithSigning(
177
+ context,
178
+ actJson,
179
+ options,
180
+ undefined,
181
+ requestOptions,
182
+ )
183
+ : await uploadEncryptedDataWithSigning(
184
+ context,
185
+ actJson,
186
+ undefined,
187
+ options,
188
+ undefined,
189
+ requestOptions,
190
+ )
191
+
192
+ // 2. Serialize and upload encrypted grantee list
193
+ const encryptedGranteeList = serializeAndEncryptGranteeList(
194
+ granteePublicKeys,
195
+ publisherPrivateKey,
196
+ )
197
+
198
+ const granteeListResult = await uploadEncryptedDataWithSigning(
199
+ context,
200
+ encryptedGranteeList,
201
+ undefined,
202
+ options,
203
+ undefined,
204
+ requestOptions,
205
+ )
206
+
207
+ // 3. Create and upload history manifest
208
+ const timestamp = getCurrentTimestamp()
209
+ const historyManifest = createHistoryManifest()
210
+ addHistoryEntry(
211
+ historyManifest,
212
+ timestamp,
213
+ actResult.reference,
214
+ granteeListResult.reference,
215
+ )
216
+
217
+ // Save history tree bottom-up using Bee's actual returned references
218
+ // This ensures parent nodes reference children by their actual storage addresses
219
+ const historyResult = await saveHistoryTreeRecursively(
220
+ historyManifest,
221
+ async (data, isRoot) => {
222
+ const result = beeCompatible
223
+ ? await uploadDataWithSigning(
224
+ context,
225
+ data,
226
+ options,
227
+ isRoot ? onProgress : undefined,
228
+ requestOptions,
229
+ )
230
+ : await uploadEncryptedDataWithSigning(
231
+ context,
232
+ data,
233
+ undefined,
234
+ options,
235
+ isRoot ? onProgress : undefined,
236
+ requestOptions,
237
+ )
238
+ return result
239
+ },
240
+ )
241
+
242
+ const historyReference = historyResult.rootReference
243
+ const historyTagUid = historyResult.tagUid
244
+
245
+ // Compress publisher public key for API response
246
+ const compressedPubKey = compressPublicKey(
247
+ publisherPubKey.x,
248
+ publisherPubKey.y,
249
+ )
250
+
251
+ return {
252
+ encryptedReference: uint8ArrayToHex(encryptedRef),
253
+ historyReference,
254
+ granteeListReference: granteeListResult.reference,
255
+ publisherPubKey: uint8ArrayToHex(compressedPubKey),
256
+ actReference: actResult.reference,
257
+ tagUid: historyTagUid,
258
+ }
259
+ }
260
+
261
+ /**
262
+ * Decrypt an ACT-protected reference
263
+ *
264
+ * @param bee - Bee instance
265
+ * @param encryptedReference - The encrypted reference (hex string)
266
+ * @param historyReference - History manifest reference
267
+ * @param publisherPubKeyHex - Publisher's compressed public key (hex)
268
+ * @param readerPrivateKey - Reader's private key (32 bytes)
269
+ * @param timestamp - Optional timestamp to look up specific ACT version
270
+ * @param requestOptions - Bee request options
271
+ * @returns Decrypted content reference (hex string)
272
+ */
273
+ export async function decryptActReference(
274
+ bee: Bee,
275
+ encryptedReference: string,
276
+ historyReference: string,
277
+ publisherPubKeyHex: string,
278
+ readerPrivateKey: Uint8Array,
279
+ timestamp?: number,
280
+ requestOptions?: BeeRequestOptions,
281
+ ): Promise<string> {
282
+ // Parse publisher public key
283
+ const compressedPubKey = hexToUint8Array(publisherPubKeyHex)
284
+ const publisherPubKey = publicKeyFromCompressed(compressedPubKey)
285
+
286
+ // Download history manifest
287
+ const historyData = await downloadDataWithChunkAPI(
288
+ bee,
289
+ historyReference,
290
+ undefined,
291
+ undefined,
292
+ requestOptions,
293
+ )
294
+ const historyManifest = deserializeHistory(
295
+ historyData,
296
+ hexToUint8Array(historyReference),
297
+ )
298
+
299
+ // Load child nodes to populate entry data
300
+ await loadHistoryEntries(historyManifest, async (ref) => {
301
+ return downloadDataWithChunkAPI(
302
+ bee,
303
+ ref,
304
+ undefined,
305
+ undefined,
306
+ requestOptions,
307
+ )
308
+ })
309
+
310
+ // Get the appropriate entry
311
+ const entry = timestamp
312
+ ? getEntryAtTimestamp(historyManifest, timestamp)
313
+ : getLatestEntry(historyManifest)
314
+
315
+ if (!entry) {
316
+ throw new Error("No ACT entry found in history")
317
+ }
318
+
319
+ const actReference = entry.metadata.actReference
320
+
321
+ // Download ACT manifest (JSON Simple Manifest)
322
+ const actData = await downloadDataWithChunkAPI(
323
+ bee,
324
+ actReference,
325
+ undefined,
326
+ undefined,
327
+ requestOptions,
328
+ )
329
+ const entries = deserializeAct(actData)
330
+
331
+ // Derive keys using reader's private key and publisher's public key
332
+ const derivedKeys = deriveKeys(
333
+ readerPrivateKey,
334
+ publisherPubKey.x,
335
+ publisherPubKey.y,
336
+ )
337
+
338
+ // Find entry matching the lookup key
339
+ let foundEntry = findEntryByLookupKey(entries, derivedKeys.lookupKey)
340
+
341
+ if (!foundEntry) {
342
+ // Also try self-lookup (if reader is publisher)
343
+ const readerPubKey = publicKeyFromPrivate(readerPrivateKey)
344
+ const selfKeys = deriveKeys(
345
+ readerPrivateKey,
346
+ readerPubKey.x,
347
+ readerPubKey.y,
348
+ )
349
+ foundEntry = findEntryByLookupKey(entries, selfKeys.lookupKey)
350
+
351
+ if (!foundEntry) {
352
+ throw new Error("Access denied: no ACT entry found for this key")
353
+ }
354
+
355
+ // Use self keys for decryption
356
+ const accessKey = counterModeDecrypt(
357
+ foundEntry.encryptedAccessKey,
358
+ selfKeys.accessKeyDecryptionKey,
359
+ )
360
+ const encryptedRef = hexToUint8Array(encryptedReference)
361
+ const decryptedRef = counterModeDecrypt(encryptedRef, accessKey)
362
+ return formatDecryptedReference(decryptedRef)
363
+ }
364
+
365
+ // Decrypt access key
366
+ const accessKey = counterModeDecrypt(
367
+ foundEntry.encryptedAccessKey,
368
+ derivedKeys.accessKeyDecryptionKey,
369
+ )
370
+
371
+ // Decrypt the content reference
372
+ const encryptedRef = hexToUint8Array(encryptedReference)
373
+ const decryptedRef = counterModeDecrypt(encryptedRef, accessKey)
374
+
375
+ return formatDecryptedReference(decryptedRef)
376
+ }
377
+
378
+ /**
379
+ * Add grantees to an existing ACT
380
+ */
381
+ export async function addGranteesToAct(
382
+ context: UploadContext,
383
+ historyReference: string,
384
+ publisherPrivateKey: Uint8Array,
385
+ newGranteePublicKeys: Array<{ x: Uint8Array; y: Uint8Array }>,
386
+ options?: ActUploadOptions,
387
+ requestOptions?: BeeRequestOptions,
388
+ onProgress?: (progress: UploadProgress) => void,
389
+ ): Promise<ActGranteeModifyResult> {
390
+ const { bee } = context
391
+ const beeCompatible = historyReference.length === 64
392
+
393
+ // Download history manifest
394
+ const historyData = await downloadDataWithChunkAPI(
395
+ bee,
396
+ historyReference,
397
+ undefined,
398
+ undefined,
399
+ requestOptions,
400
+ )
401
+ const historyManifest = deserializeHistory(
402
+ historyData,
403
+ hexToUint8Array(historyReference),
404
+ )
405
+
406
+ // Load child nodes to populate entry data
407
+ await loadHistoryEntries(historyManifest, async (ref) => {
408
+ return downloadDataWithChunkAPI(
409
+ bee,
410
+ ref,
411
+ undefined,
412
+ undefined,
413
+ requestOptions,
414
+ )
415
+ })
416
+
417
+ // Get latest entry
418
+ const latestEntry = getLatestEntry(historyManifest)
419
+ if (!latestEntry) {
420
+ throw new Error("History manifest is empty")
421
+ }
422
+
423
+ // Download current ACT (JSON Simple Manifest)
424
+ const actData = await downloadDataWithChunkAPI(
425
+ bee,
426
+ latestEntry.metadata.actReference,
427
+ undefined,
428
+ undefined,
429
+ requestOptions,
430
+ )
431
+ const entries = deserializeAct(actData)
432
+
433
+ // Get publisher's public key and recover access key
434
+ const publisherPubKey = publicKeyFromPrivate(publisherPrivateKey)
435
+ const publisherKeys = deriveKeys(
436
+ publisherPrivateKey,
437
+ publisherPubKey.x,
438
+ publisherPubKey.y,
439
+ )
440
+ const publisherEntry = findEntryByLookupKey(entries, publisherKeys.lookupKey)
441
+
442
+ if (!publisherEntry) {
443
+ throw new Error("Cannot find publisher entry in ACT")
444
+ }
445
+
446
+ const accessKey = counterModeDecrypt(
447
+ publisherEntry.encryptedAccessKey,
448
+ publisherKeys.accessKeyDecryptionKey,
449
+ )
450
+
451
+ // Create new ACT manifest with existing entries plus new grantees
452
+ const newEntries: ActEntry[] = [...entries]
453
+ for (const granteePubKey of newGranteePublicKeys) {
454
+ const granteeKeys = deriveKeys(
455
+ publisherPrivateKey,
456
+ granteePubKey.x,
457
+ granteePubKey.y,
458
+ )
459
+ newEntries.push({
460
+ lookupKey: granteeKeys.lookupKey,
461
+ encryptedAccessKey: counterModeEncrypt(
462
+ accessKey,
463
+ granteeKeys.accessKeyDecryptionKey,
464
+ ),
465
+ })
466
+ }
467
+
468
+ // Download and update grantee list
469
+ let existingGrantees: UncompressedPublicKey[] = []
470
+ if (latestEntry.metadata.encryptedGranteeListRef) {
471
+ const encryptedList = await downloadDataWithChunkAPI(
472
+ bee,
473
+ latestEntry.metadata.encryptedGranteeListRef,
474
+ undefined,
475
+ undefined,
476
+ requestOptions,
477
+ )
478
+ existingGrantees = decryptAndDeserializeGranteeList(
479
+ encryptedList,
480
+ publisherPrivateKey,
481
+ )
482
+ }
483
+ const updatedGrantees = [...existingGrantees, ...newGranteePublicKeys]
484
+
485
+ // Upload new ACT manifest (JSON Simple Manifest format)
486
+ const newActJson = serializeAct(newEntries)
487
+ const actResult = beeCompatible
488
+ ? await uploadDataWithSigning(
489
+ context,
490
+ newActJson,
491
+ options,
492
+ undefined,
493
+ requestOptions,
494
+ )
495
+ : await uploadEncryptedDataWithSigning(
496
+ context,
497
+ newActJson,
498
+ undefined,
499
+ options,
500
+ undefined,
501
+ requestOptions,
502
+ )
503
+
504
+ // Upload updated grantee list
505
+ const encryptedGranteeList = serializeAndEncryptGranteeList(
506
+ updatedGrantees,
507
+ publisherPrivateKey,
508
+ )
509
+ const granteeListResult = await uploadEncryptedDataWithSigning(
510
+ context,
511
+ encryptedGranteeList,
512
+ undefined,
513
+ options,
514
+ undefined,
515
+ requestOptions,
516
+ )
517
+
518
+ // Add new history entry
519
+ const timestamp = getCurrentTimestamp()
520
+ addHistoryEntry(
521
+ historyManifest,
522
+ timestamp,
523
+ actResult.reference,
524
+ granteeListResult.reference,
525
+ )
526
+
527
+ // Save history tree bottom-up using Bee's actual returned references
528
+ const historyResult = await saveHistoryTreeRecursively(
529
+ historyManifest,
530
+ async (data, isRoot) => {
531
+ const result = beeCompatible
532
+ ? await uploadDataWithSigning(
533
+ context,
534
+ data,
535
+ options,
536
+ isRoot ? onProgress : undefined,
537
+ requestOptions,
538
+ )
539
+ : await uploadEncryptedDataWithSigning(
540
+ context,
541
+ data,
542
+ undefined,
543
+ options,
544
+ isRoot ? onProgress : undefined,
545
+ requestOptions,
546
+ )
547
+ return result
548
+ },
549
+ )
550
+
551
+ const newHistoryReference = historyResult.rootReference
552
+ const historyTagUid = historyResult.tagUid
553
+
554
+ return {
555
+ historyReference: newHistoryReference,
556
+ granteeListReference: granteeListResult.reference,
557
+ actReference: actResult.reference,
558
+ tagUid: historyTagUid,
559
+ }
560
+ }
561
+
562
+ /**
563
+ * Revoke grantees from an ACT (performs key rotation)
564
+ */
565
+ export async function revokeGranteesFromAct(
566
+ context: UploadContext,
567
+ historyReference: string,
568
+ encryptedReference: string,
569
+ publisherPrivateKey: Uint8Array,
570
+ revokePublicKeys: Array<{ x: Uint8Array; y: Uint8Array }>,
571
+ options?: ActUploadOptions,
572
+ requestOptions?: BeeRequestOptions,
573
+ onProgress?: (progress: UploadProgress) => void,
574
+ ): Promise<ActRevocationResult> {
575
+ const { bee } = context
576
+ const beeCompatible = historyReference.length === 64
577
+
578
+ // Download history manifest
579
+ const historyData = await downloadDataWithChunkAPI(
580
+ bee,
581
+ historyReference,
582
+ undefined,
583
+ undefined,
584
+ requestOptions,
585
+ )
586
+ const historyManifest = deserializeHistory(
587
+ historyData,
588
+ hexToUint8Array(historyReference),
589
+ )
590
+
591
+ // Load child nodes to populate entry data
592
+ await loadHistoryEntries(historyManifest, async (ref) => {
593
+ return downloadDataWithChunkAPI(
594
+ bee,
595
+ ref,
596
+ undefined,
597
+ undefined,
598
+ requestOptions,
599
+ )
600
+ })
601
+
602
+ // Get latest entry
603
+ const latestEntry = getLatestEntry(historyManifest)
604
+ if (!latestEntry) {
605
+ throw new Error("History manifest is empty")
606
+ }
607
+
608
+ // Download current ACT (JSON Simple Manifest)
609
+ const actData = await downloadDataWithChunkAPI(
610
+ bee,
611
+ latestEntry.metadata.actReference,
612
+ undefined,
613
+ undefined,
614
+ requestOptions,
615
+ )
616
+ const entries = deserializeAct(actData)
617
+
618
+ // Get publisher's public key and recover old access key
619
+ const publisherPubKey = publicKeyFromPrivate(publisherPrivateKey)
620
+ const publisherKeys = deriveKeys(
621
+ publisherPrivateKey,
622
+ publisherPubKey.x,
623
+ publisherPubKey.y,
624
+ )
625
+ const publisherEntry = findEntryByLookupKey(entries, publisherKeys.lookupKey)
626
+
627
+ if (!publisherEntry) {
628
+ throw new Error("Cannot find publisher entry in ACT")
629
+ }
630
+
631
+ const oldAccessKey = counterModeDecrypt(
632
+ publisherEntry.encryptedAccessKey,
633
+ publisherKeys.accessKeyDecryptionKey,
634
+ )
635
+
636
+ // Decrypt the content reference
637
+ const encryptedRef = hexToUint8Array(encryptedReference)
638
+ const decryptedRef = counterModeDecrypt(encryptedRef, oldAccessKey)
639
+
640
+ // Generate NEW access key for key rotation
641
+ const newAccessKey = generateRandomKey()
642
+
643
+ // Encrypt content reference with new access key
644
+ const newEncryptedRef = counterModeEncrypt(decryptedRef, newAccessKey)
645
+
646
+ // Get current grantee list and filter out revoked
647
+ let currentGrantees: UncompressedPublicKey[] = []
648
+ if (latestEntry.metadata.encryptedGranteeListRef) {
649
+ const encryptedList = await downloadDataWithChunkAPI(
650
+ bee,
651
+ latestEntry.metadata.encryptedGranteeListRef,
652
+ undefined,
653
+ undefined,
654
+ requestOptions,
655
+ )
656
+ currentGrantees = decryptAndDeserializeGranteeList(
657
+ encryptedList,
658
+ publisherPrivateKey,
659
+ )
660
+ }
661
+
662
+ const remainingGrantees = currentGrantees.filter((grantee) => {
663
+ return !revokePublicKeys.some((revoked) =>
664
+ publicKeysEqual(grantee, revoked),
665
+ )
666
+ })
667
+
668
+ // Rebuild ALL entries with new access key
669
+ const newEntries: ActEntry[] = []
670
+
671
+ // Entry for publisher
672
+ newEntries.push({
673
+ lookupKey: publisherKeys.lookupKey,
674
+ encryptedAccessKey: counterModeEncrypt(
675
+ newAccessKey,
676
+ publisherKeys.accessKeyDecryptionKey,
677
+ ),
678
+ })
679
+
680
+ // Entry for each remaining grantee
681
+ for (const granteePubKey of remainingGrantees) {
682
+ const granteeKeys = deriveKeys(
683
+ publisherPrivateKey,
684
+ granteePubKey.x,
685
+ granteePubKey.y,
686
+ )
687
+ newEntries.push({
688
+ lookupKey: granteeKeys.lookupKey,
689
+ encryptedAccessKey: counterModeEncrypt(
690
+ newAccessKey,
691
+ granteeKeys.accessKeyDecryptionKey,
692
+ ),
693
+ })
694
+ }
695
+
696
+ // Upload new ACT manifest (JSON Simple Manifest format)
697
+ const newActJson = serializeAct(newEntries)
698
+ const actResult = beeCompatible
699
+ ? await uploadDataWithSigning(
700
+ context,
701
+ newActJson,
702
+ options,
703
+ undefined,
704
+ requestOptions,
705
+ )
706
+ : await uploadEncryptedDataWithSigning(
707
+ context,
708
+ newActJson,
709
+ undefined,
710
+ options,
711
+ undefined,
712
+ requestOptions,
713
+ )
714
+
715
+ // Upload updated grantee list
716
+ const encryptedGranteeList = serializeAndEncryptGranteeList(
717
+ remainingGrantees,
718
+ publisherPrivateKey,
719
+ )
720
+ const granteeListResult = await uploadEncryptedDataWithSigning(
721
+ context,
722
+ encryptedGranteeList,
723
+ undefined,
724
+ options,
725
+ undefined,
726
+ requestOptions,
727
+ )
728
+
729
+ // Add new history entry
730
+ const timestamp = getCurrentTimestamp()
731
+ addHistoryEntry(
732
+ historyManifest,
733
+ timestamp,
734
+ actResult.reference,
735
+ granteeListResult.reference,
736
+ )
737
+
738
+ // Save history tree bottom-up using Bee's actual returned references
739
+ const historyResult = await saveHistoryTreeRecursively(
740
+ historyManifest,
741
+ async (data, isRoot) => {
742
+ const result = beeCompatible
743
+ ? await uploadDataWithSigning(
744
+ context,
745
+ data,
746
+ options,
747
+ isRoot ? onProgress : undefined,
748
+ requestOptions,
749
+ )
750
+ : await uploadEncryptedDataWithSigning(
751
+ context,
752
+ data,
753
+ undefined,
754
+ options,
755
+ isRoot ? onProgress : undefined,
756
+ requestOptions,
757
+ )
758
+ return result
759
+ },
760
+ )
761
+
762
+ const newHistoryReference = historyResult.rootReference
763
+ const historyTagUid = historyResult.tagUid
764
+
765
+ return {
766
+ encryptedReference: uint8ArrayToHex(newEncryptedRef),
767
+ historyReference: newHistoryReference,
768
+ granteeListReference: granteeListResult.reference,
769
+ actReference: actResult.reference,
770
+ tagUid: historyTagUid,
771
+ }
772
+ }
773
+
774
+ /**
775
+ * Get grantees from an ACT
776
+ */
777
+ export async function getGranteesFromAct(
778
+ bee: Bee,
779
+ historyReference: string,
780
+ publisherPrivateKey: Uint8Array,
781
+ requestOptions?: BeeRequestOptions,
782
+ ): Promise<string[]> {
783
+ // Download history manifest
784
+ const historyData = await downloadDataWithChunkAPI(
785
+ bee,
786
+ historyReference,
787
+ undefined,
788
+ undefined,
789
+ requestOptions,
790
+ )
791
+ const historyManifest = deserializeHistory(
792
+ historyData,
793
+ hexToUint8Array(historyReference),
794
+ )
795
+
796
+ // Load child nodes to populate entry data
797
+ await loadHistoryEntries(historyManifest, async (ref) => {
798
+ return downloadDataWithChunkAPI(
799
+ bee,
800
+ ref,
801
+ undefined,
802
+ undefined,
803
+ requestOptions,
804
+ )
805
+ })
806
+
807
+ // Get latest entry
808
+ const latestEntry = getLatestEntry(historyManifest)
809
+ if (!latestEntry || !latestEntry.metadata.encryptedGranteeListRef) {
810
+ return []
811
+ }
812
+
813
+ // Download and decrypt grantee list
814
+ const encryptedList = await downloadDataWithChunkAPI(
815
+ bee,
816
+ latestEntry.metadata.encryptedGranteeListRef,
817
+ undefined,
818
+ undefined,
819
+ requestOptions,
820
+ )
821
+
822
+ const grantees = decryptAndDeserializeGranteeList(
823
+ encryptedList,
824
+ publisherPrivateKey,
825
+ )
826
+
827
+ // Return as compressed hex strings
828
+ return grantees.map((grantee) => {
829
+ const compressed = compressPublicKey(grantee.x, grantee.y)
830
+ return uint8ArrayToHex(compressed)
831
+ })
832
+ }
833
+
834
+ /**
835
+ * Parse a compressed public key from hex string
836
+ */
837
+ export function parseCompressedPublicKey(hex: string): {
838
+ x: Uint8Array
839
+ y: Uint8Array
840
+ } {
841
+ const compressed = hexToUint8Array(hex)
842
+ return publicKeyFromCompressed(compressed)
843
+ }
844
+
845
+ // Re-export types and utilities
846
+ export { type ActEntry } from "./act"
847
+ export { type UncompressedPublicKey } from "./grantee-list"
848
+ export { type HistoryEntry, type HistoryEntryMetadata } from "./history"
849
+ export {
850
+ publicKeyFromPrivate,
851
+ compressPublicKey,
852
+ publicKeyFromCompressed,
853
+ } from "./crypto"