@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,658 @@
1
+ import { Reference, PrivateKey, Identifier } from "@ethersphere/bee-js"
2
+ import type {
3
+ Bee,
4
+ BeeRequestOptions,
5
+ Stamper,
6
+ UploadOptions,
7
+ EnvelopeWithBatchId,
8
+ } from "@ethersphere/bee-js"
9
+ import {
10
+ makeContentAddressedChunk,
11
+ makeEncryptedContentAddressedChunk,
12
+ calculateChunkAddress,
13
+ type EncryptedChunk,
14
+ } from "../chunk"
15
+ import { Binary } from "cafe-utility"
16
+ import { splitDataIntoChunks } from "./chunking"
17
+ import { buildEncryptedMerkleTree } from "./chunking-encrypted"
18
+ import type { UploadContext, UploadProgress } from "./types"
19
+
20
+ /**
21
+ * Result of uploading encrypted data
22
+ */
23
+ export interface UploadEncryptedDataResult {
24
+ reference: string
25
+ tagUid?: number
26
+ chunkAddresses: Uint8Array[] // Addresses of all uploaded chunks
27
+ }
28
+
29
+ /**
30
+ * Simple Uint8ArrayWriter implementation for ChunkAdapter
31
+ */
32
+ class SimpleUint8ArrayWriter {
33
+ cursor: number = 0
34
+ buffer: Uint8Array
35
+
36
+ constructor(buffer: Uint8Array) {
37
+ this.buffer = buffer
38
+ }
39
+
40
+ write(_reader: unknown): number {
41
+ throw new Error("SimpleUint8ArrayWriter.write() not implemented")
42
+ }
43
+
44
+ max(): number {
45
+ return this.buffer.length
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Adapter to convert EncryptedChunk to cafe-utility Chunk interface
51
+ * This allows the Stamper to work with encrypted chunks
52
+ */
53
+ class EncryptedChunkAdapter {
54
+ span: bigint
55
+ writer: SimpleUint8ArrayWriter
56
+
57
+ constructor(private encryptedChunk: EncryptedChunk) {
58
+ this.span = encryptedChunk.span.toBigInt()
59
+ this.writer = new SimpleUint8ArrayWriter(encryptedChunk.data)
60
+ }
61
+
62
+ hash(): Uint8Array {
63
+ return this.encryptedChunk.address.toUint8Array()
64
+ }
65
+
66
+ build(): Uint8Array {
67
+ return this.encryptedChunk.data
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Upload encrypted data with client-side signing
73
+ * Handles chunking, encryption, merkle tree building, and progress reporting
74
+ *
75
+ * @param context - Upload context with bee instance and authentication
76
+ * @param data - Data to encrypt and upload
77
+ * @param encryptionKey - Optional 32-byte encryption key (generates random if not provided)
78
+ * @param options - Upload options
79
+ * @param onProgress - Progress callback
80
+ */
81
+ export async function uploadEncryptedDataWithSigning(
82
+ context: UploadContext,
83
+ data: Uint8Array,
84
+ encryptionKey?: Uint8Array,
85
+ options?: UploadOptions,
86
+ onProgress?: (progress: UploadProgress) => void,
87
+ requestOptions?: BeeRequestOptions,
88
+ ): Promise<UploadEncryptedDataResult> {
89
+ const { bee, stamper } = context
90
+
91
+ // Validate authentication method
92
+ if (!stamper) {
93
+ throw new Error("No authentication method available")
94
+ }
95
+
96
+ // Create a tag for tracking upload progress (required for fast deferred uploads)
97
+ // IMPORTANT: Tag is REQUIRED in dev mode - Bee's /chunks endpoint uses tag presence
98
+ // to determine deferred mode (deferred = tag != 0), and dev mode blocks non-deferred uploads
99
+ let tag: number | undefined = options?.tag
100
+ if (!tag) {
101
+ const tagResponse = await bee.createTag()
102
+ tag = tagResponse.uid
103
+ } else {
104
+ }
105
+
106
+ // Step 1: Split data into chunks
107
+ const chunkPayloads = splitDataIntoChunks(data)
108
+ let totalChunks = chunkPayloads.length
109
+ let processedChunks = 0
110
+
111
+ // Progress callback helper
112
+ const reportProgress = () => {
113
+ if (onProgress) {
114
+ onProgress({ total: totalChunks, processed: processedChunks })
115
+ }
116
+ }
117
+
118
+ // Step 2: Process and encrypt leaf chunks
119
+ const encryptedChunkRefs: Array<{
120
+ address: Uint8Array
121
+ key: Uint8Array
122
+ span: bigint
123
+ }> = []
124
+
125
+ // Track all uploaded chunk addresses for utilization
126
+ const uploadedChunkAddresses: Uint8Array[] = []
127
+
128
+ // Merge tag into options for all chunk uploads
129
+ const uploadOptionsWithTag = { ...options, tag }
130
+
131
+ for (const payload of chunkPayloads) {
132
+ // Create and encrypt content-addressed chunk
133
+ const encryptedChunk = makeEncryptedContentAddressedChunk(
134
+ payload,
135
+ encryptionKey,
136
+ )
137
+
138
+ // Store reference with span (payload size for leaf chunks)
139
+ encryptedChunkRefs.push({
140
+ address: encryptedChunk.address.toUint8Array(),
141
+ key: encryptedChunk.encryptionKey,
142
+ span: BigInt(payload.length),
143
+ })
144
+
145
+ // DEBUG: Trace encryption key storage
146
+
147
+ // Upload chunk with signing
148
+ await uploadSingleEncryptedChunk(
149
+ bee,
150
+ stamper,
151
+ encryptedChunk,
152
+ uploadOptionsWithTag,
153
+ requestOptions,
154
+ )
155
+
156
+ // Track uploaded chunk address for utilization
157
+ uploadedChunkAddresses.push(encryptedChunk.address.toUint8Array())
158
+
159
+ processedChunks++
160
+ reportProgress()
161
+ }
162
+
163
+ // Step 3: Build encrypted merkle tree (if multiple chunks)
164
+ let rootReference: Reference
165
+
166
+ if (encryptedChunkRefs.length === 1) {
167
+ // Single chunk - use direct reference (64 bytes)
168
+ const ref = new Uint8Array(64)
169
+ ref.set(encryptedChunkRefs[0].address, 0)
170
+ ref.set(encryptedChunkRefs[0].key, 32)
171
+
172
+ // DEBUG: Trace 64-byte reference construction
173
+
174
+ rootReference = new Reference(ref)
175
+ } else {
176
+ // Multiple chunks - build encrypted tree using bee-js's implementation
177
+
178
+ rootReference = await buildEncryptedMerkleTree(
179
+ encryptedChunkRefs,
180
+ async (encryptedChunkData) => {
181
+ // Upload the already-encrypted intermediate chunk
182
+ // encryptedChunkData = encryptedSpan (8 bytes) + encryptedPayload (4096 bytes) = 4104 bytes
183
+ // We need to upload this without any modification
184
+
185
+ // Calculate address for this intermediate chunk (needed for both stamper and utilization tracking)
186
+ const address = calculateChunkAddress(encryptedChunkData)
187
+
188
+ // Track this intermediate chunk address for utilization
189
+ uploadedChunkAddresses.push(address.toUint8Array())
190
+
191
+ // For client-side signing, use the calculated address
192
+
193
+ const envelope = stamper.stamp({
194
+ hash: () => address.toUint8Array(),
195
+ build: () => encryptedChunkData,
196
+ span: 0n, // not used by stamper.stamp
197
+ writer: undefined as any, // not used by stamper.stamp
198
+ })
199
+
200
+ await bee.uploadChunk(
201
+ envelope,
202
+ encryptedChunkData,
203
+ uploadOptionsWithTag,
204
+ requestOptions,
205
+ )
206
+
207
+ // Count intermediate chunks in progress
208
+ totalChunks++
209
+ processedChunks++
210
+ reportProgress()
211
+ },
212
+ )
213
+ }
214
+
215
+ // Return result with 64-byte reference (128 hex chars)
216
+ return {
217
+ reference: rootReference.toHex(),
218
+ tagUid: tag,
219
+ chunkAddresses: uploadedChunkAddresses,
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Upload a single encrypted chunk with optional signing
225
+ */
226
+ export async function uploadSingleEncryptedChunk(
227
+ bee: Bee,
228
+ stamper: Stamper,
229
+ encryptedChunk: EncryptedChunk,
230
+ options?: UploadOptions,
231
+ requestOptions?: BeeRequestOptions,
232
+ ): Promise<void> {
233
+ // Client-side signing - use adapter for cafe-utility Chunk interface
234
+ const chunkAdapter = new EncryptedChunkAdapter(encryptedChunk)
235
+ const envelope = stamper.stamp(chunkAdapter)
236
+ await uploadSingleChunkWithEnvelope(
237
+ bee,
238
+ envelope,
239
+ encryptedChunk.data,
240
+ options,
241
+ requestOptions,
242
+ )
243
+ }
244
+
245
+ /**
246
+ * Upload a single encrypted chunk with optional signing
247
+ */
248
+ async function uploadSingleChunkWithEnvelope(
249
+ bee: Bee,
250
+ envelope: EnvelopeWithBatchId,
251
+ data: Uint8Array,
252
+ options?: UploadOptions,
253
+ requestOptions?: BeeRequestOptions,
254
+ ): Promise<void> {
255
+ // Use non-deferred mode for faster uploads (returns immediately)
256
+ // Note: pinning is incompatible with deferred mode, so disable it
257
+ const uploadOptions = { deferred: false, pin: false, ...options }
258
+
259
+ await bee.uploadChunk(envelope, data, uploadOptions, requestOptions)
260
+ }
261
+
262
+ /**
263
+ * Upload a single encrypted chunk with optional signing
264
+ *
265
+ * This is the unified interface for uploading encrypted chunks.
266
+ * Use this instead of direct Bee API calls with fetch.
267
+ *
268
+ * @param bee - Bee client instance
269
+ * @param stamper - Stamper for client-side signing
270
+ * @param payload - Raw chunk data to encrypt and upload (1-4096 bytes)
271
+ * @param encryptionKey - Encryption key (32 bytes)
272
+ * @param options - Upload options (deferred, tag, etc.)
273
+ */
274
+ export async function uploadSingleChunkWithEncryption(
275
+ bee: Bee,
276
+ stamper: Stamper,
277
+ payload: Uint8Array,
278
+ encryptionKey: Uint8Array,
279
+ options?: UploadOptions,
280
+ ): Promise<void> {
281
+ // Validate payload size (1-4096 bytes, encryption handles padding)
282
+ if (payload.length < 1 || payload.length > 4096) {
283
+ throw new Error(
284
+ `Invalid payload length: ${payload.length} (expected 1-4096)`,
285
+ )
286
+ }
287
+
288
+ // Validate encryption key
289
+ if (encryptionKey.length !== 32) {
290
+ throw new Error(
291
+ `Invalid encryption key length: ${encryptionKey.length} (expected 32)`,
292
+ )
293
+ }
294
+
295
+ // Create encrypted content-addressed chunk
296
+ const encryptedChunk = makeEncryptedContentAddressedChunk(
297
+ payload,
298
+ encryptionKey,
299
+ )
300
+
301
+ // Upload using the existing function that handles both stamper and batch cases
302
+ await uploadSingleEncryptedChunk(bee, stamper, encryptedChunk, options)
303
+ }
304
+
305
+ /**
306
+ * Result of uploading an encrypted SOC
307
+ */
308
+ export interface UploadEncryptedSOCResult {
309
+ socAddress: Uint8Array
310
+ encryptionKey: Uint8Array
311
+ tagUid?: number
312
+ }
313
+
314
+ /**
315
+ * Result of uploading a SOC
316
+ */
317
+ export interface UploadSOCResult {
318
+ socAddress: Uint8Array
319
+ tagUid?: number
320
+ }
321
+
322
+ /**
323
+ * Upload chunk via direct fetch to /chunks endpoint
324
+ *
325
+ * This is a temporary workaround for bee.uploadChunk's 4104-byte size limit.
326
+ * Can be replaced with bee.uploadChunk when it supports SOC chunks (4201 bytes).
327
+ *
328
+ * @param bee - Bee client instance
329
+ * @param envelope - Envelope with postage stamp signature
330
+ * @param chunkData - Full chunk data (can be > 4104 bytes for SOC)
331
+ * @param options - Upload options
332
+ * @returns Reference to the uploaded chunk
333
+ */
334
+ async function uploadChunkWithFetch(
335
+ bee: Bee,
336
+ envelope: EnvelopeWithBatchId,
337
+ chunkData: Uint8Array,
338
+ options?: UploadOptions,
339
+ ): Promise<Reference> {
340
+ // Marshal the envelope to hex for the HTTP header
341
+ // Order: batchId (32) + index (8) + timestamp (8) + signature (65) = 113 bytes
342
+ const marshaledStamp = Binary.concatBytes(
343
+ envelope.batchId.toUint8Array(),
344
+ envelope.index,
345
+ envelope.timestamp,
346
+ envelope.signature,
347
+ )
348
+
349
+ // Convert to hex string (226 chars) - using Binary.uint8ArrayToHex for browser compatibility
350
+ const stampHex = Binary.uint8ArrayToHex(marshaledStamp)
351
+
352
+ // Prepare HTTP headers
353
+ const headers: Record<string, string> = {
354
+ "content-type": "application/octet-stream",
355
+ "swarm-postage-stamp": stampHex,
356
+ }
357
+
358
+ // Add optional headers
359
+ if (options?.tag) headers["swarm-tag"] = options.tag.toString()
360
+ if (options?.deferred !== undefined) {
361
+ headers["swarm-deferred-upload"] = options.deferred.toString()
362
+ }
363
+ if (options?.pin !== undefined) {
364
+ headers["swarm-pin"] = options.pin.toString()
365
+ }
366
+
367
+ // Make direct fetch call to /chunks endpoint
368
+ const response = await fetch(`${bee.url}/chunks`, {
369
+ method: "POST",
370
+ headers,
371
+ body: chunkData,
372
+ })
373
+
374
+ if (!response.ok) {
375
+ const errorText = await response.text()
376
+ throw new Error(
377
+ `Chunk upload failed: ${response.status} ${response.statusText} - ${errorText}`,
378
+ )
379
+ }
380
+
381
+ const result = await response.json()
382
+
383
+ return new Reference(result.reference)
384
+ }
385
+
386
+ /**
387
+ * Calculate SOC address from identifier and owner
388
+ */
389
+ function makeSOCAddress(
390
+ identifier: Identifier,
391
+ ownerAddress: Uint8Array,
392
+ ): Reference {
393
+ return new Reference(
394
+ Binary.keccak256(
395
+ Binary.concatBytes(identifier.toUint8Array(), ownerAddress),
396
+ ),
397
+ )
398
+ }
399
+
400
+ /**
401
+ * Upload an encrypted Single Owner Chunk (SOC) using the fast chunk upload path
402
+ *
403
+ * This function constructs an encrypted SOC manually and uploads it via the regular
404
+ * /chunks endpoint for better performance compared to the /soc endpoint.
405
+ *
406
+ * SOC Structure (Book of Swarm 2.2.3, 2.2.4):
407
+ * - 32 bytes: identifier
408
+ * - 65 bytes: signature (r, s, v)
409
+ * - 8 bytes: span (from encrypted CAC)
410
+ * - up to 4096 bytes: encrypted payload (from encrypted CAC)
411
+ *
412
+ * The signature signs: hash(identifier + encrypted_CAC.address)
413
+ * SOC address: Keccak256(identifier + owner_address)
414
+ *
415
+ * @param bee - Bee client instance
416
+ * @param stamper - Stamper for client-side signing
417
+ * @param signer - SOC owner's private key
418
+ * @param identifier - 32-byte SOC identifier
419
+ * @param data - Payload data (1-4096 bytes)
420
+ * @param encryptionKey - Optional 32-byte encryption key (random if not provided)
421
+ * @param options - Upload options (tag, deferred, etc.)
422
+ * @returns SOC address, encryption key, and optional tag UID
423
+ */
424
+ export async function uploadEncryptedSOC(
425
+ bee: Bee,
426
+ stamper: Stamper,
427
+ signer: PrivateKey,
428
+ identifier: Identifier,
429
+ data: Uint8Array,
430
+ encryptionKey?: Uint8Array,
431
+ options?: UploadOptions,
432
+ ): Promise<UploadEncryptedSOCResult> {
433
+ // Validate data size (1-4096 bytes)
434
+ if (data.length < 1 || data.length > 4096) {
435
+ throw new Error(`Invalid data length: ${data.length} (expected 1-4096)`)
436
+ }
437
+
438
+ // Step 1: Create encrypted CAC chunk
439
+ const encryptedChunk = makeEncryptedContentAddressedChunk(data, encryptionKey)
440
+
441
+ // Step 2: Construct SOC structure
442
+ const owner = signer.publicKey().address()
443
+
444
+ // Sign: hash(identifier + encrypted_CAC.address)
445
+ const toSign = Binary.concatBytes(
446
+ identifier.toUint8Array(),
447
+ encryptedChunk.address.toUint8Array(),
448
+ )
449
+ const signature = signer.sign(toSign)
450
+
451
+ // Build SOC data: identifier (32) + signature (65) + encrypted_CAC.data
452
+ const socData = Binary.concatBytes(
453
+ identifier.toUint8Array(),
454
+ signature.toUint8Array(),
455
+ encryptedChunk.data,
456
+ )
457
+
458
+ // Calculate SOC address: Keccak256(identifier + owner_address)
459
+ const socAddress = makeSOCAddress(identifier, owner.toUint8Array())
460
+
461
+ // Step 3: Create tag for tracking (if not provided in options)
462
+ // IMPORTANT: Tag is REQUIRED in dev mode - Bee's /chunks endpoint uses tag presence
463
+ // to determine deferred mode (deferred = tag != 0), and dev mode blocks non-deferred uploads
464
+ let tag: number | undefined = options?.tag
465
+ if (!tag) {
466
+ const tagResponse = await bee.createTag()
467
+ tag = tagResponse.uid
468
+ } else {
469
+ }
470
+
471
+ // Step 4: Create envelope with stamper
472
+ const envelope = stamper.stamp({
473
+ hash: () => socAddress.toUint8Array(),
474
+ build: () => socData,
475
+ span: 0n, // not used by stamper.stamp
476
+ writer: undefined as any, // not used by stamper.stamp
477
+ })
478
+
479
+ // Step 5: Upload using direct fetch (bypasses bee.uploadChunk size check)
480
+ const uploadOptionsWithTag = { tag, deferred: false, pin: false, ...options }
481
+ await uploadChunkWithFetch(bee, envelope, socData, uploadOptionsWithTag)
482
+
483
+ return {
484
+ socAddress: socAddress.toUint8Array(),
485
+ encryptionKey: encryptedChunk.encryptionKey,
486
+ tagUid: tag,
487
+ }
488
+ }
489
+
490
+ /**
491
+ * Upload a plain Single Owner Chunk (SOC) using the fast chunk upload path
492
+ *
493
+ * This constructs an unencrypted SOC and uploads it via /chunks to avoid /soc size limits.
494
+ */
495
+ export async function uploadSOC(
496
+ bee: Bee,
497
+ stamper: Stamper,
498
+ signer: PrivateKey,
499
+ identifier: Identifier,
500
+ data: Uint8Array,
501
+ options?: UploadOptions,
502
+ ): Promise<UploadSOCResult> {
503
+ if (data.length < 1 || data.length > 4096) {
504
+ throw new Error(`Invalid data length: ${data.length} (expected 1-4096)`)
505
+ }
506
+
507
+ const cac = makeContentAddressedChunk(data)
508
+ // Bee recognizes SOCs by full SOC size (32+65+4104). Pad CAC to 4104 bytes
509
+ // so /chunks treats this as a SOC instead of a regular chunk, avoiding
510
+ // "stamp signature is invalid" errors.
511
+ const cacData = new Uint8Array(8 + 4096)
512
+ cacData.set(cac.data)
513
+ const owner = signer.publicKey().address()
514
+
515
+ const toSign = Binary.concatBytes(
516
+ identifier.toUint8Array(),
517
+ cac.address.toUint8Array(),
518
+ )
519
+ const signature = signer.sign(toSign)
520
+
521
+ const socData = Binary.concatBytes(
522
+ identifier.toUint8Array(),
523
+ signature.toUint8Array(),
524
+ cacData,
525
+ )
526
+
527
+ const socAddress = makeSOCAddress(identifier, owner.toUint8Array())
528
+
529
+ let tag: number | undefined = options?.tag
530
+ if (!tag) {
531
+ const tagResponse = await bee.createTag()
532
+ tag = tagResponse.uid
533
+ }
534
+
535
+ const envelope = stamper.stamp({
536
+ hash: () => socAddress.toUint8Array(),
537
+ build: () => socData,
538
+ span: 0n,
539
+ writer: undefined as any,
540
+ })
541
+
542
+ const uploadOptionsWithTag = { tag, deferred: false, pin: false, ...options }
543
+ await uploadChunkWithFetch(bee, envelope, socData, uploadOptionsWithTag)
544
+
545
+ return {
546
+ socAddress: socAddress.toUint8Array(),
547
+ tagUid: tag,
548
+ }
549
+ }
550
+
551
+ /**
552
+ * Upload SOC via the /soc/{owner}/{id} endpoint.
553
+ *
554
+ * Use this for small SOCs (< 4104 bytes) that need to preserve exact CAC size,
555
+ * such as v1 format feeds for /bzz/ compatibility.
556
+ *
557
+ * The /soc endpoint explicitly handles SOC uploads without size-based detection,
558
+ * avoiding "stamp signature is invalid" errors for small SOCs that would be
559
+ * misidentified as CAC by the /chunks endpoint.
560
+ *
561
+ * Key differences from uploadSOC:
562
+ * - Uses /soc/{owner}/{id}?sig=... endpoint (explicit SOC handling)
563
+ * - Does NOT pad CAC data (preserves exact payload size for v1 format)
564
+ * - Stamps using CAC address (what /soc endpoint expects)
565
+ */
566
+ export async function uploadSOCViaSocEndpoint(
567
+ bee: Bee,
568
+ stamper: Stamper,
569
+ signer: PrivateKey,
570
+ identifier: Identifier,
571
+ data: Uint8Array,
572
+ options?: UploadOptions,
573
+ ): Promise<UploadSOCResult> {
574
+ if (data.length < 1 || data.length > 4096) {
575
+ throw new Error(`Invalid data length: ${data.length} (expected 1-4096)`)
576
+ }
577
+
578
+ // Build CAC data (span + payload) - NO PADDING
579
+ // This preserves the exact size needed for v1 format (/bzz/ compatibility)
580
+ const cac = makeContentAddressedChunk(data)
581
+ const cacData = cac.data // span(8) + payload - NOT padded
582
+
583
+ const owner = signer.publicKey().address()
584
+
585
+ // Sign: hash(identifier + cac.address)
586
+ const toSign = Binary.concatBytes(
587
+ identifier.toUint8Array(),
588
+ cac.address.toUint8Array(),
589
+ )
590
+ const signature = signer.sign(toSign)
591
+
592
+ // Calculate SOC address (for return value)
593
+ const socAddress = makeSOCAddress(identifier, owner.toUint8Array())
594
+
595
+ // Debug logging for v1 format verification
596
+
597
+ // Create tag
598
+ let tag: number | undefined = options?.tag
599
+ if (!tag) {
600
+ const tagResponse = await bee.createTag()
601
+ tag = tagResponse.uid
602
+ }
603
+
604
+ // Stamp using SOC address (what Bee validates for /soc endpoint)
605
+ const envelope = stamper.stamp({
606
+ hash: () => socAddress.toUint8Array(),
607
+ build: () => cacData,
608
+ span: 0n,
609
+ writer: undefined as any,
610
+ })
611
+
612
+ // Marshal the envelope to hex for the HTTP header
613
+ // Order: batchId (32) + index (8) + timestamp (8) + signature (65) = 113 bytes
614
+ const marshaledStamp = Binary.concatBytes(
615
+ envelope.batchId.toUint8Array(),
616
+ envelope.index,
617
+ envelope.timestamp,
618
+ envelope.signature,
619
+ )
620
+ const stampHex = Binary.uint8ArrayToHex(marshaledStamp)
621
+
622
+ // Build URL with signature query parameter
623
+ const url = `${bee.url}/soc/${owner.toHex()}/${identifier.toHex()}?sig=${signature.toHex()}`
624
+
625
+ // Prepare HTTP headers (matching uploadChunkWithFetch pattern)
626
+ const headers: Record<string, string> = {
627
+ "content-type": "application/octet-stream",
628
+ "swarm-postage-stamp": stampHex,
629
+ }
630
+
631
+ // Add optional headers only if defined (let Bee use defaults otherwise)
632
+ if (tag) headers["swarm-tag"] = tag.toString()
633
+ if (options?.deferred !== undefined) {
634
+ headers["swarm-deferred-upload"] = options.deferred.toString()
635
+ }
636
+ if (options?.pin !== undefined) {
637
+ headers["swarm-pin"] = options.pin.toString()
638
+ }
639
+
640
+ // Upload via /soc endpoint
641
+ const response = await fetch(url, {
642
+ method: "POST",
643
+ headers,
644
+ body: cacData, // Raw CAC data (NOT full SOC structure)
645
+ })
646
+
647
+ if (!response.ok) {
648
+ const errorText = await response.text()
649
+ throw new Error(
650
+ `SOC upload failed: ${response.status} ${response.statusText} - ${errorText}`,
651
+ )
652
+ }
653
+
654
+ return {
655
+ socAddress: socAddress.toUint8Array(),
656
+ tagUid: tag,
657
+ }
658
+ }