@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,214 @@
1
+ /**
2
+ * Unit tests for content-addressed chunk (CAC) creation
3
+ */
4
+
5
+ import { describe, it, expect } from "vitest"
6
+ import { Span } from "@ethersphere/bee-js"
7
+ import { makeContentAddressedChunk } from "./cac"
8
+ import { calculateChunkAddress } from "./bmt"
9
+
10
+ // ============================================================================
11
+ // makeContentAddressedChunk Tests
12
+ // ============================================================================
13
+
14
+ describe("makeContentAddressedChunk", () => {
15
+ describe("chunk structure", () => {
16
+ it("should create chunk with correct structure (data = span + payload)", () => {
17
+ const payload = new Uint8Array([1, 2, 3, 4, 5])
18
+ const chunk = makeContentAddressedChunk(payload)
19
+
20
+ // data should be span (8 bytes) + payload
21
+ expect(chunk.data.length).toBe(8 + payload.length)
22
+ expect(chunk.data.slice(8)).toEqual(payload)
23
+ })
24
+
25
+ it("should set span reflecting payload length", () => {
26
+ const payload = new Uint8Array([10, 20, 30])
27
+ const chunk = makeContentAddressedChunk(payload)
28
+
29
+ expect(chunk.span.toBigInt()).toBe(BigInt(payload.length))
30
+ })
31
+
32
+ it("should calculate address matching calculateChunkAddress result", () => {
33
+ const payload = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8])
34
+ const chunk = makeContentAddressedChunk(payload)
35
+
36
+ const expectedAddress = calculateChunkAddress(chunk.data)
37
+
38
+ expect(chunk.address.toUint8Array()).toEqual(
39
+ expectedAddress.toUint8Array(),
40
+ )
41
+ })
42
+ })
43
+
44
+ describe("string input handling", () => {
45
+ it("should accept string input and encode as UTF-8", () => {
46
+ const text = "Hello, World!"
47
+ const chunk = makeContentAddressedChunk(text)
48
+
49
+ const encoder = new TextEncoder()
50
+ const expectedPayload = encoder.encode(text)
51
+
52
+ expect(chunk.payload.toUint8Array()).toEqual(expectedPayload)
53
+ expect(chunk.span.toBigInt()).toBe(BigInt(expectedPayload.length))
54
+ })
55
+
56
+ it("should handle Unicode strings", () => {
57
+ const text = "Hello, 世界! 🌍"
58
+ const chunk = makeContentAddressedChunk(text)
59
+
60
+ const encoder = new TextEncoder()
61
+ const expectedPayload = encoder.encode(text)
62
+
63
+ expect(chunk.payload.toUint8Array()).toEqual(expectedPayload)
64
+ })
65
+
66
+ it("should handle empty-like strings (single char)", () => {
67
+ const text = "a"
68
+ const chunk = makeContentAddressedChunk(text)
69
+
70
+ expect(chunk.span.toBigInt()).toBe(BigInt(1))
71
+ })
72
+ })
73
+
74
+ describe("spanOverride parameter", () => {
75
+ it("should use Span override when provided", () => {
76
+ const payload = new Uint8Array([1, 2, 3, 4])
77
+ const customSpan = Span.fromBigInt(BigInt(1000))
78
+ const chunk = makeContentAddressedChunk(payload, customSpan)
79
+
80
+ expect(chunk.span.toBigInt()).toBe(BigInt(1000))
81
+ })
82
+
83
+ it("should use bigint override when provided", () => {
84
+ const payload = new Uint8Array([1, 2, 3, 4])
85
+ const chunk = makeContentAddressedChunk(payload, BigInt(2000))
86
+
87
+ expect(chunk.span.toBigInt()).toBe(BigInt(2000))
88
+ })
89
+
90
+ it("should produce different address with different spanOverride", () => {
91
+ const payload = new Uint8Array([1, 2, 3, 4])
92
+
93
+ const chunk1 = makeContentAddressedChunk(payload)
94
+ const chunk2 = makeContentAddressedChunk(payload, BigInt(100))
95
+
96
+ expect(chunk1.address.toUint8Array()).not.toEqual(
97
+ chunk2.address.toUint8Array(),
98
+ )
99
+ })
100
+ })
101
+
102
+ describe("error handling", () => {
103
+ it("should throw error on empty payload", () => {
104
+ const emptyPayload = new Uint8Array(0)
105
+
106
+ expect(() => makeContentAddressedChunk(emptyPayload)).toThrow(
107
+ /payload size .* exceeds limits/,
108
+ )
109
+ })
110
+
111
+ it("should throw error on empty string", () => {
112
+ expect(() => makeContentAddressedChunk("")).toThrow(
113
+ /payload size .* exceeds limits/,
114
+ )
115
+ })
116
+
117
+ it("should throw error on payload larger than 4096 bytes", () => {
118
+ const largePayload = new Uint8Array(4097)
119
+
120
+ expect(() => makeContentAddressedChunk(largePayload)).toThrow(
121
+ /payload size .* exceeds limits/,
122
+ )
123
+ })
124
+ })
125
+
126
+ describe("determinism", () => {
127
+ it("should produce same chunk for same payload", () => {
128
+ const payload = new Uint8Array([5, 10, 15, 20, 25])
129
+
130
+ const chunk1 = makeContentAddressedChunk(payload)
131
+ const chunk2 = makeContentAddressedChunk(payload)
132
+
133
+ expect(chunk1.data).toEqual(chunk2.data)
134
+ expect(chunk1.address.toUint8Array()).toEqual(
135
+ chunk2.address.toUint8Array(),
136
+ )
137
+ expect(chunk1.span.toBigInt()).toBe(chunk2.span.toBigInt())
138
+ })
139
+
140
+ it("should produce same chunk for same string", () => {
141
+ const text = "Test string for determinism"
142
+
143
+ const chunk1 = makeContentAddressedChunk(text)
144
+ const chunk2 = makeContentAddressedChunk(text)
145
+
146
+ expect(chunk1.data).toEqual(chunk2.data)
147
+ expect(chunk1.address.toUint8Array()).toEqual(
148
+ chunk2.address.toUint8Array(),
149
+ )
150
+ })
151
+ })
152
+
153
+ describe("payload size variations", () => {
154
+ it("should handle minimum payload size (1 byte)", () => {
155
+ const payload = new Uint8Array([42])
156
+ const chunk = makeContentAddressedChunk(payload)
157
+
158
+ expect(chunk.span.toBigInt()).toBe(BigInt(1))
159
+ expect(chunk.data.length).toBe(9) // 8 span + 1 payload
160
+ })
161
+
162
+ it("should handle maximum payload size (4096 bytes)", () => {
163
+ const payload = new Uint8Array(4096)
164
+ for (let i = 0; i < 4096; i++) {
165
+ payload[i] = i % 256
166
+ }
167
+ const chunk = makeContentAddressedChunk(payload)
168
+
169
+ expect(chunk.span.toBigInt()).toBe(BigInt(4096))
170
+ expect(chunk.data.length).toBe(8 + 4096)
171
+ })
172
+
173
+ it("should handle power-of-two sizes correctly", () => {
174
+ const sizes = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096]
175
+
176
+ for (const size of sizes) {
177
+ const payload = new Uint8Array(size)
178
+ payload.fill(42)
179
+ const chunk = makeContentAddressedChunk(payload)
180
+
181
+ expect(chunk.span.toBigInt()).toBe(BigInt(size))
182
+ expect(chunk.address.toUint8Array().length).toBe(32)
183
+ }
184
+ })
185
+ })
186
+
187
+ describe("edge cases", () => {
188
+ it("should handle payload with all zeros", () => {
189
+ const payload = new Uint8Array(100)
190
+ const chunk = makeContentAddressedChunk(payload)
191
+
192
+ expect(chunk.address.toUint8Array().length).toBe(32)
193
+ })
194
+
195
+ it("should handle payload with all 0xFF", () => {
196
+ const payload = new Uint8Array(100).fill(0xff)
197
+ const chunk = makeContentAddressedChunk(payload)
198
+
199
+ expect(chunk.address.toUint8Array().length).toBe(32)
200
+ })
201
+
202
+ it("should produce different chunks for different payloads", () => {
203
+ const payload1 = new Uint8Array([1, 2, 3])
204
+ const payload2 = new Uint8Array([4, 5, 6])
205
+
206
+ const chunk1 = makeContentAddressedChunk(payload1)
207
+ const chunk2 = makeContentAddressedChunk(payload2)
208
+
209
+ expect(chunk1.address.toUint8Array()).not.toEqual(
210
+ chunk2.address.toUint8Array(),
211
+ )
212
+ })
213
+ })
214
+ })
@@ -0,0 +1,65 @@
1
+ // Content-addressed chunk (CAC) utilities
2
+
3
+ import { Binary } from "cafe-utility"
4
+ import { Bytes, Reference, Span } from "@ethersphere/bee-js"
5
+ import { calculateChunkAddress } from "./bmt"
6
+ import { MAX_PAYLOAD_SIZE, MIN_PAYLOAD_SIZE } from "./constants"
7
+
8
+ const ENCODER = new TextEncoder()
9
+
10
+ /**
11
+ * Content addressed chunk interface
12
+ */
13
+ export interface ContentAddressedChunk {
14
+ readonly data: Uint8Array // span + payload
15
+ readonly span: Span
16
+ readonly payload: Bytes
17
+ readonly address: Reference // BMT hash
18
+ }
19
+
20
+ /**
21
+ * Creates a content addressed chunk from payload data
22
+ *
23
+ * @param payloadBytes the data to be stored in the chunk (1-4096 bytes)
24
+ * @param spanOverride optional span value to use instead of payload length
25
+ */
26
+ export function makeContentAddressedChunk(
27
+ payloadBytes: Uint8Array | string,
28
+ spanOverride?: Span | bigint,
29
+ ): ContentAddressedChunk {
30
+ if (!(payloadBytes instanceof Uint8Array)) {
31
+ payloadBytes = ENCODER.encode(payloadBytes)
32
+ }
33
+
34
+ if (
35
+ payloadBytes.length < MIN_PAYLOAD_SIZE ||
36
+ payloadBytes.length > MAX_PAYLOAD_SIZE
37
+ ) {
38
+ throw new RangeError(
39
+ `payload size ${payloadBytes.length} exceeds limits [${MIN_PAYLOAD_SIZE}, ${MAX_PAYLOAD_SIZE}]`,
40
+ )
41
+ }
42
+
43
+ // Determine span value
44
+ let span: Span
45
+ if (spanOverride instanceof Span) {
46
+ span = spanOverride
47
+ } else if (typeof spanOverride === "bigint") {
48
+ span = Span.fromBigInt(spanOverride)
49
+ } else {
50
+ span = Span.fromBigInt(BigInt(payloadBytes.length))
51
+ }
52
+
53
+ // Create chunk data (span + payload)
54
+ const chunkData = Binary.concatBytes(span.toUint8Array(), payloadBytes)
55
+
56
+ // Calculate BMT address
57
+ const address = calculateChunkAddress(chunkData)
58
+
59
+ return {
60
+ data: chunkData,
61
+ span,
62
+ payload: new Bytes(payloadBytes),
63
+ address,
64
+ }
65
+ }
@@ -0,0 +1,18 @@
1
+ // Chunk size constants
2
+ export const MIN_PAYLOAD_SIZE = 1
3
+ export const MAX_PAYLOAD_SIZE = 4096
4
+
5
+ // Span size (8 bytes for uint64 little-endian)
6
+ export const SPAN_SIZE = 8
7
+
8
+ // Reference sizes
9
+ export const UNENCRYPTED_REF_SIZE = 32
10
+ export const ENCRYPTED_REF_SIZE = 64
11
+
12
+ // SOC (Single Owner Chunk) constants
13
+ export const IDENTIFIER_SIZE = 32
14
+ export const SIGNATURE_SIZE = 65
15
+ export const SOC_HEADER_SIZE = IDENTIFIER_SIZE + SIGNATURE_SIZE
16
+
17
+ // Download concurrency
18
+ export const DEFAULT_DOWNLOAD_CONCURRENCY = 64
@@ -0,0 +1,385 @@
1
+ /**
2
+ * Unit tests for encrypted content-addressed chunk (CAC) creation and decryption
3
+ */
4
+
5
+ import { describe, it, expect } from "vitest"
6
+ import { Reference } from "@ethersphere/bee-js"
7
+ import {
8
+ makeEncryptedContentAddressedChunk,
9
+ decryptEncryptedChunk,
10
+ extractEncryptionKey,
11
+ extractChunkAddress,
12
+ } from "./encrypted-cac"
13
+ import { generateRandomKey } from "./encryption"
14
+
15
+ // ============================================================================
16
+ // makeEncryptedContentAddressedChunk Tests
17
+ // ============================================================================
18
+
19
+ describe("makeEncryptedContentAddressedChunk", () => {
20
+ describe("chunk structure", () => {
21
+ it("should create encrypted chunk with correct structure", () => {
22
+ const payload = new Uint8Array([1, 2, 3, 4, 5])
23
+ const chunk = makeEncryptedContentAddressedChunk(payload)
24
+
25
+ // data should be encrypted span (8 bytes) + encrypted data (4096 bytes)
26
+ expect(chunk.data.length).toBe(4104)
27
+ })
28
+
29
+ it("should produce encrypted data of 4104 bytes (8 span + 4096 data)", () => {
30
+ const payload = new Uint8Array([10, 20, 30])
31
+ const chunk = makeEncryptedContentAddressedChunk(payload)
32
+
33
+ expect(chunk.data.length).toBe(4104)
34
+ })
35
+
36
+ it("should produce reference of 64 bytes (address + key)", () => {
37
+ const payload = new Uint8Array([1, 2, 3, 4])
38
+ const chunk = makeEncryptedContentAddressedChunk(payload)
39
+
40
+ expect(chunk.reference.toUint8Array().length).toBe(64)
41
+ })
42
+
43
+ it("should include 32-byte encryption key", () => {
44
+ const payload = new Uint8Array([5, 6, 7, 8])
45
+ const chunk = makeEncryptedContentAddressedChunk(payload)
46
+
47
+ expect(chunk.encryptionKey.length).toBe(32)
48
+ })
49
+
50
+ it("should include 32-byte address", () => {
51
+ const payload = new Uint8Array([9, 10, 11, 12])
52
+ const chunk = makeEncryptedContentAddressedChunk(payload)
53
+
54
+ expect(chunk.address.toUint8Array().length).toBe(32)
55
+ })
56
+ })
57
+
58
+ describe("encryption key handling", () => {
59
+ it("should use custom encryption key when provided", () => {
60
+ const payload = new Uint8Array([1, 2, 3, 4])
61
+ const customKey = generateRandomKey()
62
+ const chunk = makeEncryptedContentAddressedChunk(payload, customKey)
63
+
64
+ expect(chunk.encryptionKey).toBe(customKey)
65
+ })
66
+
67
+ it("should generate random key when not provided", () => {
68
+ const payload = new Uint8Array([1, 2, 3, 4])
69
+
70
+ const chunk1 = makeEncryptedContentAddressedChunk(payload)
71
+ const chunk2 = makeEncryptedContentAddressedChunk(payload)
72
+
73
+ expect(chunk1.encryptionKey).not.toEqual(chunk2.encryptionKey)
74
+ })
75
+
76
+ it("should produce same encrypted data with same key", () => {
77
+ const payload = new Uint8Array([1, 2, 3, 4, 5])
78
+ const key = generateRandomKey()
79
+
80
+ const chunk1 = makeEncryptedContentAddressedChunk(payload, key)
81
+ const chunk2 = makeEncryptedContentAddressedChunk(payload, key)
82
+
83
+ expect(chunk1.data).toEqual(chunk2.data)
84
+ expect(chunk1.address.toUint8Array()).toEqual(
85
+ chunk2.address.toUint8Array(),
86
+ )
87
+ })
88
+
89
+ it("should produce different encrypted data with different keys", () => {
90
+ const payload = new Uint8Array([1, 2, 3, 4, 5])
91
+ const key1 = generateRandomKey()
92
+ const key2 = generateRandomKey()
93
+
94
+ const chunk1 = makeEncryptedContentAddressedChunk(payload, key1)
95
+ const chunk2 = makeEncryptedContentAddressedChunk(payload, key2)
96
+
97
+ expect(chunk1.data).not.toEqual(chunk2.data)
98
+ })
99
+ })
100
+
101
+ describe("string input handling", () => {
102
+ it("should accept string input and encode as UTF-8", () => {
103
+ const text = "Hello, Encrypted World!"
104
+ const chunk = makeEncryptedContentAddressedChunk(text)
105
+
106
+ expect(chunk.data.length).toBe(4104)
107
+ expect(chunk.reference.toUint8Array().length).toBe(64)
108
+ })
109
+
110
+ it("should handle Unicode strings", () => {
111
+ const text = "Encrypted 世界! 🔐"
112
+ const chunk = makeEncryptedContentAddressedChunk(text)
113
+
114
+ expect(chunk.data.length).toBe(4104)
115
+ })
116
+ })
117
+
118
+ describe("error handling", () => {
119
+ it("should throw error on empty payload", () => {
120
+ const emptyPayload = new Uint8Array(0)
121
+
122
+ expect(() => makeEncryptedContentAddressedChunk(emptyPayload)).toThrow(
123
+ /payload size .* exceeds limits/,
124
+ )
125
+ })
126
+
127
+ it("should throw error on empty string", () => {
128
+ expect(() => makeEncryptedContentAddressedChunk("")).toThrow(
129
+ /payload size .* exceeds limits/,
130
+ )
131
+ })
132
+
133
+ it("should throw error on payload larger than 4096 bytes", () => {
134
+ const largePayload = new Uint8Array(4097)
135
+
136
+ expect(() => makeEncryptedContentAddressedChunk(largePayload)).toThrow(
137
+ /payload size .* exceeds limits/,
138
+ )
139
+ })
140
+ })
141
+
142
+ describe("payload size variations", () => {
143
+ it("should handle minimum payload size (1 byte)", () => {
144
+ const payload = new Uint8Array([42])
145
+ const chunk = makeEncryptedContentAddressedChunk(payload)
146
+
147
+ expect(chunk.span.toBigInt()).toBe(BigInt(1))
148
+ expect(chunk.data.length).toBe(4104)
149
+ })
150
+
151
+ it("should handle maximum payload size (4096 bytes)", () => {
152
+ const payload = new Uint8Array(4096)
153
+ for (let i = 0; i < 4096; i++) {
154
+ payload[i] = i % 256
155
+ }
156
+ const chunk = makeEncryptedContentAddressedChunk(payload)
157
+
158
+ expect(chunk.span.toBigInt()).toBe(BigInt(4096))
159
+ expect(chunk.data.length).toBe(4104)
160
+ })
161
+ })
162
+ })
163
+
164
+ // ============================================================================
165
+ // decryptEncryptedChunk Tests
166
+ // ============================================================================
167
+
168
+ describe("decryptEncryptedChunk", () => {
169
+ describe("round-trip encryption/decryption", () => {
170
+ it("should recover original data after encrypt then decrypt", () => {
171
+ const payload = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8])
172
+ const chunk = makeEncryptedContentAddressedChunk(payload)
173
+
174
+ const decrypted = decryptEncryptedChunk(chunk.data, chunk.encryptionKey)
175
+
176
+ // First 8 bytes are span, rest is payload (padded to 4096)
177
+ const decryptedPayload = decrypted.slice(8, 8 + payload.length)
178
+ expect(decryptedPayload).toEqual(payload)
179
+ })
180
+
181
+ it("should preserve original span value after decryption", () => {
182
+ const payload = new Uint8Array([10, 20, 30, 40, 50])
183
+ const chunk = makeEncryptedContentAddressedChunk(payload)
184
+
185
+ const decrypted = decryptEncryptedChunk(chunk.data, chunk.encryptionKey)
186
+
187
+ // Extract span from decrypted data (first 8 bytes, little-endian)
188
+ const decryptedSpanView = new DataView(decrypted.buffer, 0, 8)
189
+ const decryptedSpan = decryptedSpanView.getBigUint64(0, true)
190
+
191
+ expect(decryptedSpan).toBe(BigInt(payload.length))
192
+ })
193
+
194
+ it("should round-trip string content", () => {
195
+ const text = "Hello, World! This is encrypted."
196
+ const encoder = new TextEncoder()
197
+ const expectedPayload = encoder.encode(text)
198
+
199
+ const chunk = makeEncryptedContentAddressedChunk(text)
200
+ const decrypted = decryptEncryptedChunk(chunk.data, chunk.encryptionKey)
201
+
202
+ const decryptedPayload = decrypted.slice(8, 8 + expectedPayload.length)
203
+ expect(decryptedPayload).toEqual(expectedPayload)
204
+ })
205
+
206
+ it("should round-trip maximum size payload", () => {
207
+ const payload = new Uint8Array(4096)
208
+ for (let i = 0; i < 4096; i++) {
209
+ payload[i] = i % 256
210
+ }
211
+
212
+ const chunk = makeEncryptedContentAddressedChunk(payload)
213
+ const decrypted = decryptEncryptedChunk(chunk.data, chunk.encryptionKey)
214
+
215
+ // For max size, decrypted should match exactly (no extra padding)
216
+ const decryptedPayload = decrypted.slice(8)
217
+ expect(decryptedPayload).toEqual(payload)
218
+ })
219
+
220
+ it("should round-trip various payload sizes", () => {
221
+ const sizes = [1, 10, 32, 64, 100, 256, 512, 1024, 2048, 4096]
222
+
223
+ for (const size of sizes) {
224
+ const payload = new Uint8Array(size)
225
+ for (let i = 0; i < size; i++) {
226
+ payload[i] = i % 256
227
+ }
228
+
229
+ const chunk = makeEncryptedContentAddressedChunk(payload)
230
+ const decrypted = decryptEncryptedChunk(chunk.data, chunk.encryptionKey)
231
+
232
+ const decryptedPayload = decrypted.slice(8, 8 + size)
233
+ expect(decryptedPayload).toEqual(payload)
234
+ }
235
+ })
236
+ })
237
+
238
+ describe("key sensitivity", () => {
239
+ it("should fail to decrypt with wrong key", () => {
240
+ const payload = new Uint8Array([1, 2, 3, 4, 5])
241
+ const chunk = makeEncryptedContentAddressedChunk(payload)
242
+ const wrongKey = generateRandomKey()
243
+
244
+ const decrypted = decryptEncryptedChunk(chunk.data, wrongKey)
245
+
246
+ // Decryption doesn't throw but produces wrong data
247
+ const decryptedPayload = decrypted.slice(8, 8 + payload.length)
248
+ expect(decryptedPayload).not.toEqual(payload)
249
+ })
250
+ })
251
+ })
252
+
253
+ // ============================================================================
254
+ // extractEncryptionKey Tests
255
+ // ============================================================================
256
+
257
+ describe("extractEncryptionKey", () => {
258
+ it("should return correct 32-byte key from 64-byte reference", () => {
259
+ const payload = new Uint8Array([1, 2, 3, 4])
260
+ const customKey = generateRandomKey()
261
+ const chunk = makeEncryptedContentAddressedChunk(payload, customKey)
262
+
263
+ const extractedKey = extractEncryptionKey(chunk.reference)
264
+
265
+ expect(extractedKey).toEqual(customKey)
266
+ expect(extractedKey.length).toBe(32)
267
+ })
268
+
269
+ it("should extract key that can decrypt the chunk", () => {
270
+ const payload = new Uint8Array([5, 10, 15, 20])
271
+ const chunk = makeEncryptedContentAddressedChunk(payload)
272
+
273
+ const extractedKey = extractEncryptionKey(chunk.reference)
274
+ const decrypted = decryptEncryptedChunk(chunk.data, extractedKey)
275
+
276
+ const decryptedPayload = decrypted.slice(8, 8 + payload.length)
277
+ expect(decryptedPayload).toEqual(payload)
278
+ })
279
+
280
+ it("should throw error on non-64-byte reference", () => {
281
+ // 32-byte reference (non-encrypted)
282
+ const shortRef = new Reference(new Uint8Array(32))
283
+ expect(() => extractEncryptionKey(shortRef)).toThrow(
284
+ /Invalid encrypted reference length/,
285
+ )
286
+
287
+ // Note: Reference constructor validates that bytes are 32 or 64, so we can't test
288
+ // with lengths like 128. The 32-byte case is sufficient to test our validation.
289
+ })
290
+ })
291
+
292
+ // ============================================================================
293
+ // extractChunkAddress Tests
294
+ // ============================================================================
295
+
296
+ describe("extractChunkAddress", () => {
297
+ it("should return correct 32-byte address from 64-byte reference", () => {
298
+ const payload = new Uint8Array([1, 2, 3, 4])
299
+ const chunk = makeEncryptedContentAddressedChunk(payload)
300
+
301
+ const extractedAddress = extractChunkAddress(chunk.reference)
302
+
303
+ expect(extractedAddress.toUint8Array()).toEqual(
304
+ chunk.address.toUint8Array(),
305
+ )
306
+ expect(extractedAddress.toUint8Array().length).toBe(32)
307
+ })
308
+
309
+ it("should extract address that matches chunk address", () => {
310
+ const payload = new Uint8Array([100, 200, 50, 25])
311
+ const key = generateRandomKey()
312
+ const chunk = makeEncryptedContentAddressedChunk(payload, key)
313
+
314
+ const extractedAddress = extractChunkAddress(chunk.reference)
315
+
316
+ expect(extractedAddress.toUint8Array()).toEqual(
317
+ chunk.address.toUint8Array(),
318
+ )
319
+ })
320
+
321
+ it("should throw error on non-64-byte reference", () => {
322
+ // 32-byte reference (non-encrypted)
323
+ const shortRef = new Reference(new Uint8Array(32))
324
+ expect(() => extractChunkAddress(shortRef)).toThrow(
325
+ /Invalid encrypted reference length/,
326
+ )
327
+
328
+ // Note: Reference constructor validates that bytes are 32 or 64, so we can't test
329
+ // with lengths like 128. The 32-byte case is sufficient to test our validation.
330
+ })
331
+
332
+ it("should extract address that differs from encryption key", () => {
333
+ const payload = new Uint8Array([1, 2, 3])
334
+ const chunk = makeEncryptedContentAddressedChunk(payload)
335
+
336
+ const address = extractChunkAddress(chunk.reference)
337
+ const key = extractEncryptionKey(chunk.reference)
338
+
339
+ expect(address.toUint8Array()).not.toEqual(key)
340
+ })
341
+ })
342
+
343
+ // ============================================================================
344
+ // Integration Tests
345
+ // ============================================================================
346
+
347
+ describe("encrypted-cac integration", () => {
348
+ it("should support complete workflow: create → extract → decrypt", () => {
349
+ const originalPayload = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
350
+
351
+ // Create encrypted chunk
352
+ const chunk = makeEncryptedContentAddressedChunk(originalPayload)
353
+
354
+ // Extract address and key from reference
355
+ const address = extractChunkAddress(chunk.reference)
356
+ const key = extractEncryptionKey(chunk.reference)
357
+
358
+ // Verify address matches
359
+ expect(address.toUint8Array()).toEqual(chunk.address.toUint8Array())
360
+
361
+ // Decrypt using extracted key
362
+ const decrypted = decryptEncryptedChunk(chunk.data, key)
363
+ const decryptedPayload = decrypted.slice(8, 8 + originalPayload.length)
364
+
365
+ expect(decryptedPayload).toEqual(originalPayload)
366
+ })
367
+
368
+ it("should allow reference to be serialized and deserialized", () => {
369
+ const payload = new Uint8Array([42, 43, 44, 45])
370
+ const chunk = makeEncryptedContentAddressedChunk(payload)
371
+
372
+ // Serialize reference to hex
373
+ const referenceHex = chunk.reference.toHex()
374
+
375
+ // Deserialize reference
376
+ const deserializedRef = new Reference(referenceHex)
377
+
378
+ // Extract and decrypt
379
+ const key = extractEncryptionKey(deserializedRef)
380
+ const decrypted = decryptEncryptedChunk(chunk.data, key)
381
+ const decryptedPayload = decrypted.slice(8, 8 + payload.length)
382
+
383
+ expect(decryptedPayload).toEqual(payload)
384
+ })
385
+ })