@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,436 @@
1
+ /**
2
+ * Unit tests for ACT cryptographic primitives
3
+ */
4
+
5
+ import { describe, it, expect } from "vitest"
6
+ import {
7
+ publicKeyFromPrivate,
8
+ ecdhSharedSecret,
9
+ deriveKeys,
10
+ counterModeEncrypt,
11
+ counterModeDecrypt,
12
+ publicKeyFromCompressed,
13
+ compressPublicKey,
14
+ generateRandomKey,
15
+ } from "./crypto"
16
+
17
+ // Test vectors - known private key and expected public key
18
+ // Using a simple test vector that can be verified
19
+ const TEST_PRIVATE_KEY = new Uint8Array([
20
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
21
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
22
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
23
+ ])
24
+
25
+ // Generator point G for secp256k1 (private key = 1 gives G)
26
+ const GENERATOR_X = new Uint8Array([
27
+ 0x79, 0xbe, 0x66, 0x7e, 0xf9, 0xdc, 0xbb, 0xac, 0x55, 0xa0, 0x62, 0x95, 0xce,
28
+ 0x87, 0x0b, 0x07, 0x02, 0x9b, 0xfc, 0xdb, 0x2d, 0xce, 0x28, 0xd9, 0x59, 0xf2,
29
+ 0x81, 0x5b, 0x16, 0xf8, 0x17, 0x98,
30
+ ])
31
+
32
+ const GENERATOR_Y = new Uint8Array([
33
+ 0x48, 0x3a, 0xda, 0x77, 0x26, 0xa3, 0xc4, 0x65, 0x5d, 0xa4, 0xfb, 0xfc, 0x0e,
34
+ 0x11, 0x08, 0xa8, 0xfd, 0x17, 0xb4, 0x48, 0xa6, 0x85, 0x54, 0x19, 0x9c, 0x47,
35
+ 0xd0, 0x8f, 0xfb, 0x10, 0xd4, 0xb8,
36
+ ])
37
+
38
+ describe("publicKeyFromPrivate", () => {
39
+ it("should derive correct public key for private key = 1 (generator point)", () => {
40
+ const pubKey = publicKeyFromPrivate(TEST_PRIVATE_KEY)
41
+ expect(pubKey.x).toEqual(GENERATOR_X)
42
+ expect(pubKey.y).toEqual(GENERATOR_Y)
43
+ })
44
+
45
+ it("should throw error for invalid private key length", () => {
46
+ expect(() => publicKeyFromPrivate(new Uint8Array(16))).toThrow()
47
+ expect(() => publicKeyFromPrivate(new Uint8Array(64))).toThrow()
48
+ })
49
+
50
+ it("should derive different public keys for different private keys", () => {
51
+ const privKey1 = new Uint8Array(32)
52
+ privKey1[31] = 1
53
+ const privKey2 = new Uint8Array(32)
54
+ privKey2[31] = 2
55
+
56
+ const pubKey1 = publicKeyFromPrivate(privKey1)
57
+ const pubKey2 = publicKeyFromPrivate(privKey2)
58
+
59
+ expect(pubKey1.x).not.toEqual(pubKey2.x)
60
+ })
61
+ })
62
+
63
+ describe("ecdhSharedSecret", () => {
64
+ it("should compute same shared secret from both directions", () => {
65
+ // Alice's key pair
66
+ const alicePrivate = new Uint8Array(32)
67
+ alicePrivate[31] = 2
68
+ const alicePublic = publicKeyFromPrivate(alicePrivate)
69
+
70
+ // Bob's key pair
71
+ const bobPrivate = new Uint8Array(32)
72
+ bobPrivate[31] = 3
73
+ const bobPublic = publicKeyFromPrivate(bobPrivate)
74
+
75
+ // Alice computes shared secret with Bob's public key
76
+ const sharedAlice = ecdhSharedSecret(alicePrivate, bobPublic.x, bobPublic.y)
77
+
78
+ // Bob computes shared secret with Alice's public key
79
+ const sharedBob = ecdhSharedSecret(bobPrivate, alicePublic.x, alicePublic.y)
80
+
81
+ // Both should be equal
82
+ expect(sharedAlice).toEqual(sharedBob)
83
+ })
84
+
85
+ it("should return 32 bytes", () => {
86
+ const privKey = new Uint8Array(32)
87
+ privKey[31] = 5
88
+ const pubKey = publicKeyFromPrivate(privKey)
89
+
90
+ const shared = ecdhSharedSecret(privKey, pubKey.x, pubKey.y)
91
+ expect(shared.length).toBe(32)
92
+ })
93
+
94
+ it("should throw error for invalid key lengths", () => {
95
+ const validPriv = new Uint8Array(32)
96
+ validPriv[31] = 1
97
+ const pubKey = publicKeyFromPrivate(validPriv)
98
+
99
+ expect(() =>
100
+ ecdhSharedSecret(new Uint8Array(16), pubKey.x, pubKey.y),
101
+ ).toThrow()
102
+ expect(() =>
103
+ ecdhSharedSecret(validPriv, new Uint8Array(16), pubKey.y),
104
+ ).toThrow()
105
+ expect(() =>
106
+ ecdhSharedSecret(validPriv, pubKey.x, new Uint8Array(16)),
107
+ ).toThrow()
108
+ })
109
+ })
110
+
111
+ describe("deriveKeys", () => {
112
+ it("should derive different lookup and access key decryption keys", () => {
113
+ const privKey = new Uint8Array(32)
114
+ privKey[31] = 7
115
+ const pubKey = publicKeyFromPrivate(privKey)
116
+
117
+ const { lookupKey, accessKeyDecryptionKey } = deriveKeys(
118
+ privKey,
119
+ pubKey.x,
120
+ pubKey.y,
121
+ )
122
+
123
+ expect(lookupKey.length).toBe(32)
124
+ expect(accessKeyDecryptionKey.length).toBe(32)
125
+ expect(lookupKey).not.toEqual(accessKeyDecryptionKey)
126
+ })
127
+
128
+ it("should produce deterministic results", () => {
129
+ const privKey = new Uint8Array(32)
130
+ privKey[31] = 11
131
+ const pubKey = publicKeyFromPrivate(privKey)
132
+
133
+ const result1 = deriveKeys(privKey, pubKey.x, pubKey.y)
134
+ const result2 = deriveKeys(privKey, pubKey.x, pubKey.y)
135
+
136
+ expect(result1.lookupKey).toEqual(result2.lookupKey)
137
+ expect(result1.accessKeyDecryptionKey).toEqual(
138
+ result2.accessKeyDecryptionKey,
139
+ )
140
+ })
141
+
142
+ it("should produce different keys for different inputs", () => {
143
+ const privKey1 = new Uint8Array(32)
144
+ privKey1[31] = 13
145
+ const pubKey1 = publicKeyFromPrivate(privKey1)
146
+
147
+ const privKey2 = new Uint8Array(32)
148
+ privKey2[31] = 17
149
+ const pubKey2 = publicKeyFromPrivate(privKey2)
150
+
151
+ const result1 = deriveKeys(privKey1, pubKey1.x, pubKey1.y)
152
+ const result2 = deriveKeys(privKey2, pubKey2.x, pubKey2.y)
153
+
154
+ expect(result1.lookupKey).not.toEqual(result2.lookupKey)
155
+ expect(result1.accessKeyDecryptionKey).not.toEqual(
156
+ result2.accessKeyDecryptionKey,
157
+ )
158
+ })
159
+ })
160
+
161
+ describe("counterModeEncrypt/counterModeDecrypt", () => {
162
+ it("should encrypt and decrypt small data correctly", () => {
163
+ const key = generateRandomKey()
164
+ const plaintext = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8])
165
+
166
+ const ciphertext = counterModeEncrypt(plaintext, key)
167
+ const decrypted = counterModeDecrypt(ciphertext, key)
168
+
169
+ expect(decrypted).toEqual(plaintext)
170
+ })
171
+
172
+ it("should encrypt and decrypt exactly 32 bytes correctly", () => {
173
+ const key = generateRandomKey()
174
+ const plaintext = new Uint8Array(32)
175
+ for (let i = 0; i < 32; i++) {
176
+ plaintext[i] = i
177
+ }
178
+
179
+ const ciphertext = counterModeEncrypt(plaintext, key)
180
+ const decrypted = counterModeDecrypt(ciphertext, key)
181
+
182
+ expect(decrypted).toEqual(plaintext)
183
+ })
184
+
185
+ it("should encrypt and decrypt multiple blocks correctly", () => {
186
+ const key = generateRandomKey()
187
+ const plaintext = new Uint8Array(100)
188
+ for (let i = 0; i < 100; i++) {
189
+ plaintext[i] = i % 256
190
+ }
191
+
192
+ const ciphertext = counterModeEncrypt(plaintext, key)
193
+ const decrypted = counterModeDecrypt(ciphertext, key)
194
+
195
+ expect(decrypted).toEqual(plaintext)
196
+ })
197
+
198
+ it("should produce different ciphertext with different keys", () => {
199
+ const key1 = generateRandomKey()
200
+ const key2 = generateRandomKey()
201
+ const plaintext = new Uint8Array([1, 2, 3, 4, 5])
202
+
203
+ const ciphertext1 = counterModeEncrypt(plaintext, key1)
204
+ const ciphertext2 = counterModeEncrypt(plaintext, key2)
205
+
206
+ expect(ciphertext1).not.toEqual(ciphertext2)
207
+ })
208
+
209
+ it("should produce ciphertext different from plaintext", () => {
210
+ const key = generateRandomKey()
211
+ const plaintext = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
212
+
213
+ const ciphertext = counterModeEncrypt(plaintext, key)
214
+
215
+ expect(ciphertext).not.toEqual(plaintext)
216
+ })
217
+
218
+ it("should throw error for invalid key length", () => {
219
+ const plaintext = new Uint8Array([1, 2, 3])
220
+
221
+ expect(() => counterModeEncrypt(plaintext, new Uint8Array(16))).toThrow()
222
+ expect(() => counterModeEncrypt(plaintext, new Uint8Array(64))).toThrow()
223
+ })
224
+
225
+ it("should handle empty data", () => {
226
+ const key = generateRandomKey()
227
+ const plaintext = new Uint8Array(0)
228
+
229
+ const ciphertext = counterModeEncrypt(plaintext, key)
230
+ const decrypted = counterModeDecrypt(ciphertext, key)
231
+
232
+ expect(decrypted).toEqual(plaintext)
233
+ expect(decrypted.length).toBe(0)
234
+ })
235
+
236
+ it("should be symmetric (encrypt === decrypt)", () => {
237
+ const key = generateRandomKey()
238
+ const data = new Uint8Array([42, 43, 44, 45])
239
+
240
+ // Double encryption should return original
241
+ const encrypted = counterModeEncrypt(data, key)
242
+ const doubleEncrypted = counterModeEncrypt(encrypted, key)
243
+
244
+ expect(doubleEncrypted).toEqual(data)
245
+ })
246
+ })
247
+
248
+ describe("compressPublicKey", () => {
249
+ it("should compress to 33 bytes", () => {
250
+ const privKey = new Uint8Array(32)
251
+ privKey[31] = 1
252
+ const pubKey = publicKeyFromPrivate(privKey)
253
+
254
+ const compressed = compressPublicKey(pubKey.x, pubKey.y)
255
+ expect(compressed.length).toBe(33)
256
+ })
257
+
258
+ it("should use prefix 0x02 for even y", () => {
259
+ // Generator point has even y
260
+ const compressed = compressPublicKey(GENERATOR_X, GENERATOR_Y)
261
+ expect(compressed[0]).toBe(0x02)
262
+ })
263
+
264
+ it("should use prefix 0x03 for odd y", () => {
265
+ // Find a private key that gives odd y
266
+ // Private key = 3 gives a point with odd y
267
+ const privKey = new Uint8Array(32)
268
+ privKey[31] = 3
269
+ const pubKey = publicKeyFromPrivate(privKey)
270
+
271
+ const compressed = compressPublicKey(pubKey.x, pubKey.y)
272
+ // The y coordinate's parity determines the prefix
273
+ // Either 0x02 (even) or 0x03 (odd) - verify it's valid
274
+ expect([0x02, 0x03]).toContain(compressed[0])
275
+ })
276
+
277
+ it("should include x coordinate after prefix", () => {
278
+ const compressed = compressPublicKey(GENERATOR_X, GENERATOR_Y)
279
+ expect(compressed.slice(1)).toEqual(GENERATOR_X)
280
+ })
281
+ })
282
+
283
+ describe("publicKeyFromCompressed", () => {
284
+ it("should decompress to original coordinates", () => {
285
+ const privKey = new Uint8Array(32)
286
+ privKey[31] = 1
287
+ const pubKey = publicKeyFromPrivate(privKey)
288
+
289
+ const compressed = compressPublicKey(pubKey.x, pubKey.y)
290
+ const decompressed = publicKeyFromCompressed(compressed)
291
+
292
+ expect(decompressed.x).toEqual(pubKey.x)
293
+ expect(decompressed.y).toEqual(pubKey.y)
294
+ })
295
+
296
+ it("should handle prefix 0x02 (even y)", () => {
297
+ const compressed = compressPublicKey(GENERATOR_X, GENERATOR_Y)
298
+ expect(compressed[0]).toBe(0x02)
299
+
300
+ const decompressed = publicKeyFromCompressed(compressed)
301
+ expect(decompressed.x).toEqual(GENERATOR_X)
302
+ expect(decompressed.y).toEqual(GENERATOR_Y)
303
+ })
304
+
305
+ it("should handle any valid compressed key (roundtrip)", () => {
306
+ // Test multiple private keys to cover both even and odd y cases
307
+ const privKey = new Uint8Array(32)
308
+ privKey[31] = 5
309
+ const pubKey = publicKeyFromPrivate(privKey)
310
+
311
+ const compressed = compressPublicKey(pubKey.x, pubKey.y)
312
+ // Prefix should be 0x02 or 0x03
313
+ expect([0x02, 0x03]).toContain(compressed[0])
314
+
315
+ const decompressed = publicKeyFromCompressed(compressed)
316
+ expect(decompressed.x).toEqual(pubKey.x)
317
+ expect(decompressed.y).toEqual(pubKey.y)
318
+ })
319
+
320
+ it("should throw error for invalid prefix", () => {
321
+ const invalid = new Uint8Array(33)
322
+ invalid[0] = 0x04 // Uncompressed prefix
323
+ expect(() => publicKeyFromCompressed(invalid)).toThrow()
324
+
325
+ invalid[0] = 0x00
326
+ expect(() => publicKeyFromCompressed(invalid)).toThrow()
327
+ })
328
+
329
+ it("should throw error for invalid length", () => {
330
+ expect(() => publicKeyFromCompressed(new Uint8Array(32))).toThrow()
331
+ expect(() => publicKeyFromCompressed(new Uint8Array(34))).toThrow()
332
+ })
333
+
334
+ it("should roundtrip through compress/decompress", () => {
335
+ // Test multiple keys
336
+ for (let i = 1; i <= 10; i++) {
337
+ const privKey = new Uint8Array(32)
338
+ privKey[31] = i
339
+ const pubKey = publicKeyFromPrivate(privKey)
340
+
341
+ const compressed = compressPublicKey(pubKey.x, pubKey.y)
342
+ const decompressed = publicKeyFromCompressed(compressed)
343
+
344
+ expect(decompressed.x).toEqual(pubKey.x)
345
+ expect(decompressed.y).toEqual(pubKey.y)
346
+ }
347
+ })
348
+ })
349
+
350
+ describe("generateRandomKey", () => {
351
+ it("should return 32 bytes", () => {
352
+ const key = generateRandomKey()
353
+ expect(key.length).toBe(32)
354
+ })
355
+
356
+ it("should generate different keys each time", () => {
357
+ const key1 = generateRandomKey()
358
+ const key2 = generateRandomKey()
359
+ const key3 = generateRandomKey()
360
+
361
+ expect(key1).not.toEqual(key2)
362
+ expect(key2).not.toEqual(key3)
363
+ expect(key1).not.toEqual(key3)
364
+ })
365
+
366
+ it("should return Uint8Array", () => {
367
+ const key = generateRandomKey()
368
+ expect(key).toBeInstanceOf(Uint8Array)
369
+ })
370
+ })
371
+
372
+ describe("ECDH key agreement integration", () => {
373
+ it("should allow two parties to derive same lookup key", () => {
374
+ // Publisher generates key pair
375
+ const publisherPrivate = new Uint8Array(32)
376
+ crypto.getRandomValues(publisherPrivate)
377
+ const publisherPublic = publicKeyFromPrivate(publisherPrivate)
378
+
379
+ // Grantee generates key pair
380
+ const granteePrivate = new Uint8Array(32)
381
+ crypto.getRandomValues(granteePrivate)
382
+ const granteePublic = publicKeyFromPrivate(granteePrivate)
383
+
384
+ // Publisher derives keys using grantee's public key
385
+ const publisherKeys = deriveKeys(
386
+ publisherPrivate,
387
+ granteePublic.x,
388
+ granteePublic.y,
389
+ )
390
+
391
+ // Grantee derives keys using publisher's public key
392
+ const granteeKeys = deriveKeys(
393
+ granteePrivate,
394
+ publisherPublic.x,
395
+ publisherPublic.y,
396
+ )
397
+
398
+ // Both should derive the same lookup key and access key decryption key
399
+ expect(publisherKeys.lookupKey).toEqual(granteeKeys.lookupKey)
400
+ expect(publisherKeys.accessKeyDecryptionKey).toEqual(
401
+ granteeKeys.accessKeyDecryptionKey,
402
+ )
403
+ })
404
+
405
+ it("should produce unique lookup keys for different grantees", () => {
406
+ // Publisher key pair
407
+ const publisherPrivate = new Uint8Array(32)
408
+ publisherPrivate[31] = 100
409
+ const publisherPublic = publicKeyFromPrivate(publisherPrivate)
410
+
411
+ // Grantee 1
412
+ const grantee1Private = new Uint8Array(32)
413
+ grantee1Private[31] = 101
414
+ const grantee1Public = publicKeyFromPrivate(grantee1Private)
415
+
416
+ // Grantee 2
417
+ const grantee2Private = new Uint8Array(32)
418
+ grantee2Private[31] = 102
419
+ const grantee2Public = publicKeyFromPrivate(grantee2Private)
420
+
421
+ // Publisher derives keys for each grantee
422
+ const keys1 = deriveKeys(
423
+ publisherPrivate,
424
+ grantee1Public.x,
425
+ grantee1Public.y,
426
+ )
427
+ const keys2 = deriveKeys(
428
+ publisherPrivate,
429
+ grantee2Public.x,
430
+ grantee2Public.y,
431
+ )
432
+
433
+ // Lookup keys should be different
434
+ expect(keys1.lookupKey).not.toEqual(keys2.lookupKey)
435
+ })
436
+ })