@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,131 @@
1
+ // Copyright 2024 The Swarm Authors. All rights reserved.
2
+ // Use of this source code is governed by a BSD-style
3
+ // license that can be found in the LICENSE file.
4
+
5
+ import { Binary } from "cafe-utility"
6
+ import { Bytes, Reference, Span } from "@ethersphere/bee-js"
7
+ import { calculateChunkAddress } from "./bmt"
8
+ import { MAX_PAYLOAD_SIZE, MIN_PAYLOAD_SIZE } from "./constants"
9
+ import { newChunkEncrypter, decryptChunkData, type Key } from "./encryption"
10
+
11
+ const ENCODER = new TextEncoder()
12
+
13
+ /**
14
+ * Encrypted chunk interface
15
+ *
16
+ * The reference includes both the chunk address and the encryption key (64 bytes total)
17
+ */
18
+ export interface EncryptedChunk {
19
+ readonly data: Uint8Array // encrypted span + encrypted data
20
+ readonly encryptionKey: Key // 32 bytes
21
+ readonly span: Span // original (unencrypted) span
22
+ readonly payload: Bytes // encrypted payload
23
+ readonly address: Reference // BMT hash of encrypted data
24
+ readonly reference: Reference // 64 bytes: address (32) + encryption key (32)
25
+ }
26
+
27
+ /**
28
+ * Creates an encrypted content addressed chunk
29
+ *
30
+ * Process:
31
+ * 1. Create chunk with span + payload
32
+ * 2. Encrypt the chunk data
33
+ * 3. Calculate BMT hash on encrypted data
34
+ * 4. Return reference = address + encryption key (64 bytes)
35
+ *
36
+ * @param payloadBytes the data to be stored in the chunk
37
+ * @param encryptionKey optional encryption key (if not provided, a random key will be generated)
38
+ */
39
+ export function makeEncryptedContentAddressedChunk(
40
+ payloadBytes: Uint8Array | string,
41
+ encryptionKey?: Key,
42
+ ): EncryptedChunk {
43
+ if (!(payloadBytes instanceof Uint8Array)) {
44
+ payloadBytes = ENCODER.encode(payloadBytes)
45
+ }
46
+
47
+ if (
48
+ payloadBytes.length < MIN_PAYLOAD_SIZE ||
49
+ payloadBytes.length > MAX_PAYLOAD_SIZE
50
+ ) {
51
+ throw new RangeError(
52
+ `payload size ${payloadBytes.length} exceeds limits [${MIN_PAYLOAD_SIZE}, ${MAX_PAYLOAD_SIZE}]`,
53
+ )
54
+ }
55
+
56
+ // Create the original chunk data (span + payload)
57
+ const span = Span.fromBigInt(BigInt(payloadBytes.length))
58
+ const chunkData = Binary.concatBytes(span.toUint8Array(), payloadBytes)
59
+
60
+ // Encrypt the chunk
61
+ const encrypter = newChunkEncrypter()
62
+ const { key, encryptedSpan, encryptedData } = encrypter.encryptChunk(
63
+ chunkData,
64
+ encryptionKey,
65
+ )
66
+
67
+ // Concatenate encrypted span and data
68
+ const encryptedChunkData = Binary.concatBytes(encryptedSpan, encryptedData)
69
+
70
+ // Calculate BMT address on encrypted data
71
+ const address = calculateChunkAddress(encryptedChunkData)
72
+
73
+ // Create 64-byte reference: address (32 bytes) + encryption key (32 bytes)
74
+ const reference = new Reference(
75
+ Binary.concatBytes(address.toUint8Array(), key),
76
+ )
77
+
78
+ return {
79
+ data: encryptedChunkData,
80
+ encryptionKey: key,
81
+ span,
82
+ payload: new Bytes(encryptedChunkData.slice(Span.LENGTH)),
83
+ address,
84
+ reference,
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Decrypts an encrypted chunk given the encryption key
90
+ *
91
+ * @param encryptedChunkData The encrypted chunk data (span + payload)
92
+ * @param encryptionKey The 32-byte encryption key
93
+ */
94
+ export function decryptEncryptedChunk(
95
+ encryptedChunkData: Uint8Array,
96
+ encryptionKey: Key,
97
+ ): Uint8Array {
98
+ return decryptChunkData(encryptionKey, encryptedChunkData)
99
+ }
100
+
101
+ /**
102
+ * Extracts encryption key from a 64-byte encrypted reference
103
+ *
104
+ * @param reference 64-byte reference (address + key)
105
+ */
106
+ export function extractEncryptionKey(reference: Reference): Key {
107
+ const refBytes = reference.toUint8Array()
108
+ if (refBytes.length !== 64) {
109
+ throw new Error(
110
+ `Invalid encrypted reference length: ${refBytes.length}, expected 64`,
111
+ )
112
+ }
113
+
114
+ return refBytes.slice(32, 64)
115
+ }
116
+
117
+ /**
118
+ * Extracts the chunk address from a 64-byte encrypted reference
119
+ *
120
+ * @param reference 64-byte reference (address + key)
121
+ */
122
+ export function extractChunkAddress(reference: Reference): Reference {
123
+ const refBytes = reference.toUint8Array()
124
+ if (refBytes.length !== 64) {
125
+ throw new Error(
126
+ `Invalid encrypted reference length: ${refBytes.length}, expected 64`,
127
+ )
128
+ }
129
+
130
+ return new Reference(refBytes.slice(0, 32))
131
+ }
@@ -0,0 +1,352 @@
1
+ /**
2
+ * Unit tests for chunk encryption primitives
3
+ */
4
+
5
+ import { describe, it, expect } from "vitest"
6
+ import {
7
+ Encryption,
8
+ generateRandomKey,
9
+ newSpanEncryption,
10
+ newDataEncryption,
11
+ newChunkEncrypter,
12
+ decryptChunkData,
13
+ KEY_LENGTH,
14
+ } from "./encryption"
15
+
16
+ // ============================================================================
17
+ // Encryption Class Tests
18
+ // ============================================================================
19
+
20
+ describe("Encryption class", () => {
21
+ describe("round-trip encrypt/decrypt", () => {
22
+ it("should round-trip encrypt/decrypt for various data sizes", () => {
23
+ const key = generateRandomKey()
24
+ const padding = 64
25
+
26
+ // Test multiple sizes smaller than padding
27
+ const sizes = [1, 8, 16, 31, 32, 33, 48, 63, 64]
28
+ for (const size of sizes) {
29
+ const data = new Uint8Array(size)
30
+ for (let i = 0; i < size; i++) {
31
+ data[i] = i % 256
32
+ }
33
+
34
+ const encrypter = new Encryption(key, padding, 0)
35
+ const encrypted = encrypter.encrypt(data)
36
+
37
+ encrypter.reset()
38
+ const decrypted = encrypter.decrypt(encrypted)
39
+
40
+ // Decrypted data has padding, so compare only original length
41
+ expect(decrypted.slice(0, size)).toEqual(data)
42
+ }
43
+ })
44
+
45
+ it("should pad data smaller than padding size", () => {
46
+ const key = generateRandomKey()
47
+ const padding = 64
48
+ const data = new Uint8Array([1, 2, 3, 4, 5])
49
+
50
+ const encrypter = new Encryption(key, padding, 0)
51
+ const encrypted = encrypter.encrypt(data)
52
+
53
+ expect(encrypted.length).toBe(padding)
54
+ })
55
+
56
+ it("should throw error on data larger than padding", () => {
57
+ const key = generateRandomKey()
58
+ const padding = 32
59
+ const data = new Uint8Array(64)
60
+
61
+ const encrypter = new Encryption(key, padding, 0)
62
+
63
+ expect(() => encrypter.encrypt(data)).toThrow(
64
+ /data length .* longer than padding/,
65
+ )
66
+ })
67
+
68
+ it("should be deterministic (same key + data = same ciphertext)", () => {
69
+ const key = generateRandomKey()
70
+ const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8])
71
+
72
+ const encrypter1 = new Encryption(key, 32, 0)
73
+ const encrypter2 = new Encryption(key, 32, 0)
74
+
75
+ const encrypted1 = encrypter1.encrypt(data)
76
+ const encrypted2 = encrypter2.encrypt(data)
77
+
78
+ expect(encrypted1).toEqual(encrypted2)
79
+ })
80
+
81
+ it("should produce different ciphertext with different keys", () => {
82
+ const key1 = generateRandomKey()
83
+ const key2 = generateRandomKey()
84
+ const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8])
85
+
86
+ const encrypter1 = new Encryption(key1, 32, 0)
87
+ const encrypter2 = new Encryption(key2, 32, 0)
88
+
89
+ const encrypted1 = encrypter1.encrypt(data)
90
+ const encrypted2 = encrypter2.encrypt(data)
91
+
92
+ expect(encrypted1).not.toEqual(encrypted2)
93
+ })
94
+
95
+ it("should reset counter correctly", () => {
96
+ const key = generateRandomKey()
97
+ const data = new Uint8Array([1, 2, 3, 4])
98
+ const padding = 32
99
+
100
+ const encrypter = new Encryption(key, padding, 0)
101
+
102
+ // First encryption
103
+ const encrypted1 = encrypter.encrypt(data)
104
+
105
+ // Reset and encrypt again
106
+ encrypter.reset()
107
+ const encrypted2 = encrypter.encrypt(data)
108
+
109
+ // Should produce same result after reset
110
+ expect(encrypted1).toEqual(encrypted2)
111
+ })
112
+
113
+ it("should encrypt without padding when padding is 0", () => {
114
+ const key = generateRandomKey()
115
+ const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8])
116
+
117
+ const encrypter = new Encryption(key, 0, 0)
118
+ const encrypted = encrypter.encrypt(data)
119
+
120
+ // Without padding, output length equals input length
121
+ expect(encrypted.length).toBe(data.length)
122
+ })
123
+ })
124
+
125
+ describe("key() method", () => {
126
+ it("should return the encryption key", () => {
127
+ const key = generateRandomKey()
128
+ const encrypter = new Encryption(key, 0, 0)
129
+
130
+ expect(encrypter.key()).toBe(key)
131
+ })
132
+ })
133
+
134
+ describe("decrypt validation", () => {
135
+ it("should throw error when decrypt data length differs from padding", () => {
136
+ const key = generateRandomKey()
137
+ const padding = 64
138
+ const wrongSizeData = new Uint8Array(32)
139
+
140
+ const decrypter = new Encryption(key, padding, 0)
141
+
142
+ expect(() => decrypter.decrypt(wrongSizeData)).toThrow(
143
+ /data length .* different than padding/,
144
+ )
145
+ })
146
+ })
147
+ })
148
+
149
+ // ============================================================================
150
+ // generateRandomKey Tests
151
+ // ============================================================================
152
+
153
+ describe("generateRandomKey", () => {
154
+ it("should return correct default length (32 bytes)", () => {
155
+ const key = generateRandomKey()
156
+ expect(key.length).toBe(KEY_LENGTH)
157
+ })
158
+
159
+ it("should support custom length", () => {
160
+ const key16 = generateRandomKey(16)
161
+ const key64 = generateRandomKey(64)
162
+
163
+ expect(key16.length).toBe(16)
164
+ expect(key64.length).toBe(64)
165
+ })
166
+
167
+ it("should generate different keys on each call", () => {
168
+ const key1 = generateRandomKey()
169
+ const key2 = generateRandomKey()
170
+ const key3 = generateRandomKey()
171
+
172
+ expect(key1).not.toEqual(key2)
173
+ expect(key2).not.toEqual(key3)
174
+ expect(key1).not.toEqual(key3)
175
+ })
176
+
177
+ it("should return Uint8Array", () => {
178
+ const key = generateRandomKey()
179
+ expect(key).toBeInstanceOf(Uint8Array)
180
+ })
181
+ })
182
+
183
+ // ============================================================================
184
+ // newSpanEncryption / newDataEncryption Tests
185
+ // ============================================================================
186
+
187
+ describe("newSpanEncryption", () => {
188
+ it("should return valid encrypter", () => {
189
+ const key = generateRandomKey()
190
+ const encrypter = newSpanEncryption(key)
191
+
192
+ expect(encrypter.key()).toBe(key)
193
+ expect(typeof encrypter.encrypt).toBe("function")
194
+ expect(typeof encrypter.decrypt).toBe("function")
195
+ expect(typeof encrypter.reset).toBe("function")
196
+ })
197
+
198
+ it("should encrypt span data (8 bytes)", () => {
199
+ const key = generateRandomKey()
200
+ const span = new Uint8Array([1, 0, 0, 0, 0, 0, 0, 0]) // length 1 in little-endian
201
+
202
+ const encrypter = newSpanEncryption(key)
203
+ const encrypted = encrypter.encrypt(span)
204
+
205
+ // Span encryption has no padding (padding = 0)
206
+ expect(encrypted.length).toBe(8)
207
+ expect(encrypted).not.toEqual(span)
208
+ })
209
+ })
210
+
211
+ describe("newDataEncryption", () => {
212
+ it("should return valid encrypter", () => {
213
+ const key = generateRandomKey()
214
+ const encrypter = newDataEncryption(key)
215
+
216
+ expect(encrypter.key()).toBe(key)
217
+ expect(typeof encrypter.encrypt).toBe("function")
218
+ expect(typeof encrypter.decrypt).toBe("function")
219
+ expect(typeof encrypter.reset).toBe("function")
220
+ })
221
+
222
+ it("should pad data to 4096 bytes", () => {
223
+ const key = generateRandomKey()
224
+ const data = new Uint8Array([1, 2, 3, 4, 5])
225
+
226
+ const encrypter = newDataEncryption(key)
227
+ const encrypted = encrypter.encrypt(data)
228
+
229
+ expect(encrypted.length).toBe(4096)
230
+ })
231
+ })
232
+
233
+ // ============================================================================
234
+ // newChunkEncrypter + decryptChunkData Tests
235
+ // ============================================================================
236
+
237
+ describe("newChunkEncrypter + decryptChunkData", () => {
238
+ it("should round-trip chunk encryption/decryption", () => {
239
+ const chunkEncrypter = newChunkEncrypter()
240
+
241
+ // Create chunk data: 8-byte span + payload
242
+ const span = new Uint8Array([5, 0, 0, 0, 0, 0, 0, 0])
243
+ const payload = new Uint8Array([1, 2, 3, 4, 5])
244
+ const chunkData = new Uint8Array(8 + payload.length)
245
+ chunkData.set(span)
246
+ chunkData.set(payload, 8)
247
+
248
+ const { key, encryptedSpan, encryptedData } =
249
+ chunkEncrypter.encryptChunk(chunkData)
250
+
251
+ // Verify encrypted parts
252
+ expect(encryptedSpan.length).toBe(8)
253
+ expect(encryptedData.length).toBe(4096)
254
+
255
+ // Decrypt
256
+ const encryptedChunkData = new Uint8Array(8 + 4096)
257
+ encryptedChunkData.set(encryptedSpan)
258
+ encryptedChunkData.set(encryptedData, 8)
259
+
260
+ const decrypted = decryptChunkData(key, encryptedChunkData)
261
+
262
+ // Verify decryption recovers original data (span + payload portion)
263
+ expect(decrypted.slice(0, 8)).toEqual(span)
264
+ expect(decrypted.slice(8, 8 + payload.length)).toEqual(payload)
265
+ })
266
+
267
+ it("should produce encrypted span of 8 bytes", () => {
268
+ const chunkEncrypter = newChunkEncrypter()
269
+ const chunkData = new Uint8Array(8 + 100) // span + 100 bytes payload
270
+
271
+ const { encryptedSpan } = chunkEncrypter.encryptChunk(chunkData)
272
+
273
+ expect(encryptedSpan.length).toBe(8)
274
+ })
275
+
276
+ it("should produce encrypted data padded to 4096 bytes", () => {
277
+ const chunkEncrypter = newChunkEncrypter()
278
+ const chunkData = new Uint8Array(8 + 100) // span + 100 bytes payload
279
+
280
+ const { encryptedData } = chunkEncrypter.encryptChunk(chunkData)
281
+
282
+ expect(encryptedData.length).toBe(4096)
283
+ })
284
+
285
+ it("should produce total encrypted chunk of 4104 bytes (8 + 4096)", () => {
286
+ const chunkEncrypter = newChunkEncrypter()
287
+ const chunkData = new Uint8Array(8 + 100)
288
+
289
+ const { encryptedSpan, encryptedData } =
290
+ chunkEncrypter.encryptChunk(chunkData)
291
+
292
+ const totalLength = encryptedSpan.length + encryptedData.length
293
+ expect(totalLength).toBe(4104)
294
+ })
295
+
296
+ it("should use provided encryption key", () => {
297
+ const chunkEncrypter = newChunkEncrypter()
298
+ const customKey = generateRandomKey()
299
+ const chunkData = new Uint8Array(8 + 50)
300
+
301
+ const { key } = chunkEncrypter.encryptChunk(chunkData, customKey)
302
+
303
+ expect(key).toBe(customKey)
304
+ })
305
+
306
+ it("should generate random key when not provided", () => {
307
+ const chunkEncrypter = newChunkEncrypter()
308
+ const chunkData = new Uint8Array(8 + 50)
309
+
310
+ const result1 = chunkEncrypter.encryptChunk(chunkData)
311
+ const result2 = chunkEncrypter.encryptChunk(chunkData)
312
+
313
+ expect(result1.key).not.toEqual(result2.key)
314
+ })
315
+
316
+ it("should produce different encrypted output with different keys", () => {
317
+ const chunkEncrypter = newChunkEncrypter()
318
+ const chunkData = new Uint8Array(8 + 50)
319
+ chunkData.fill(42)
320
+
321
+ const key1 = generateRandomKey()
322
+ const key2 = generateRandomKey()
323
+
324
+ const result1 = chunkEncrypter.encryptChunk(chunkData, key1)
325
+ const result2 = chunkEncrypter.encryptChunk(chunkData, key2)
326
+
327
+ expect(result1.encryptedSpan).not.toEqual(result2.encryptedSpan)
328
+ expect(result1.encryptedData).not.toEqual(result2.encryptedData)
329
+ })
330
+
331
+ it("should handle maximum payload size (4096 bytes data)", () => {
332
+ const chunkEncrypter = newChunkEncrypter()
333
+ const chunkData = new Uint8Array(8 + 4096) // span + max payload
334
+ for (let i = 0; i < chunkData.length; i++) {
335
+ chunkData[i] = i % 256
336
+ }
337
+
338
+ const { key, encryptedSpan, encryptedData } =
339
+ chunkEncrypter.encryptChunk(chunkData)
340
+
341
+ expect(encryptedSpan.length).toBe(8)
342
+ expect(encryptedData.length).toBe(4096)
343
+
344
+ // Verify round-trip
345
+ const encryptedChunkData = new Uint8Array(8 + 4096)
346
+ encryptedChunkData.set(encryptedSpan)
347
+ encryptedChunkData.set(encryptedData, 8)
348
+
349
+ const decrypted = decryptChunkData(key, encryptedChunkData)
350
+ expect(decrypted).toEqual(chunkData)
351
+ })
352
+ })