@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,948 @@
1
+ /**
2
+ * Bee Compatibility Test Vectors
3
+ *
4
+ * These tests verify our ACT cryptographic implementation produces identical
5
+ * outputs to Bee's implementation using test vectors extracted from Bee's codebase.
6
+ *
7
+ * Test vectors from:
8
+ * - bee/pkg/accesscontrol/access_test.go (private keys, lookup keys)
9
+ * - bee/pkg/crypto/dh_test.go (ECDH shared secret)
10
+ */
11
+
12
+ import { describe, it, expect } from "vitest"
13
+ import {
14
+ publicKeyFromPrivate,
15
+ ecdhSharedSecret,
16
+ deriveKeys,
17
+ publicKeyFromCompressed,
18
+ compressPublicKey,
19
+ counterModeEncrypt,
20
+ counterModeDecrypt,
21
+ generateRandomKey,
22
+ } from "./crypto"
23
+ import { findEntryByLookupKey, type ActEntry } from "./act"
24
+
25
+ // =============================================================================
26
+ // Test Vectors from Bee
27
+ // =============================================================================
28
+
29
+ // Fixed private keys from bee/pkg/accesscontrol/access_test.go
30
+ const BEE_PRIVATE_KEYS = {
31
+ KEY_0: "a786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfa",
32
+ KEY_1: "b786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfb",
33
+ KEY_2: "c786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfc",
34
+ }
35
+
36
+ // Expected lookup keys when Key 1 is the publisher (from setupAccessLogic())
37
+ // See bee/pkg/accesscontrol/access_test.go TestAddNewGranteeToContent
38
+ const BEE_EXPECTED_LOOKUP_KEYS = {
39
+ // ECDH(Key1_priv, Key0_pub) - adding Key 0 as grantee
40
+ KEY1_WITH_KEY0_PUB:
41
+ "b6ee086390c280eeb9824c331a4427596f0c8510d5564bc1b6168d0059a46e2b",
42
+ // ECDH(Key1_priv, Key1_pub) - publisher self-lookup
43
+ KEY1_SELF: "a13678e81f9d939b9401a3ad7e548d2ceb81c50f8c76424296e83a1ad79c0df0",
44
+ // ECDH(Key1_priv, Key2_pub) - adding Key 2 as grantee
45
+ KEY1_WITH_KEY2_PUB:
46
+ "d5e9a6499ca74f5b8b958a4b89b7338045b2baa9420e115443a8050e26986564",
47
+ }
48
+
49
+ // ECDH test vector from bee/pkg/crypto/dh_test.go
50
+ const BEE_ECDH_TEST = {
51
+ PRIVATE_KEY:
52
+ "c786dd84b61485de12146fd9c4c02d87e8fd95f0542765cb7fc3d2e428c0bcfa",
53
+ PUBLIC_KEY_COMPRESSED:
54
+ "0271e574ad8f6a6c998c84c27df18124fddd906aba9d852150da4223edde14044f",
55
+ SALT: "cb7e692f211f8ae4f858ff56ce8a4fc0e40bae1a36f8283f0ceb6bb4be133f1e",
56
+ EXPECTED_SHARED_KEY:
57
+ "9edbd3beeb48c090158ccb82d679c5ea2bcb74850d34fe55c10b32e16b822007",
58
+ }
59
+
60
+ // Test reference from Bee tests (used for encrypt/decrypt verification)
61
+ const BEE_TEST_REFERENCE =
62
+ "39a5ea87b141fe44aa609c3327ecd896c0e2122897f5f4bbacf74db1033c5559"
63
+
64
+ // Values from actual Bee debug logs for CTR verification
65
+ const BEE_CTR_DEBUG = {
66
+ ENCRYPTED_REF:
67
+ "daae5cf4b3978362e7975548de9139ee6cfd5b4422179b23a55eded1b975ea52",
68
+ ACCESS_KEY:
69
+ "f264f1e4a6c0104a295bc650b80dc38635eb56ba047b6c9b41bd7241cf4bbf99",
70
+ DECRYPTED_REF:
71
+ "ffe4a41dcc711ab78148a37c92599c116a75e2ee02a16cd651eee9a077e7af5b",
72
+ }
73
+
74
+ // =============================================================================
75
+ // Helper Functions
76
+ // =============================================================================
77
+
78
+ /**
79
+ * Convert hex string to Uint8Array
80
+ */
81
+ function hexToBytes(hex: string): Uint8Array {
82
+ const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex
83
+ const bytes = new Uint8Array(cleanHex.length / 2)
84
+ for (let i = 0; i < bytes.length; i++) {
85
+ bytes[i] = parseInt(cleanHex.substring(i * 2, i * 2 + 2), 16)
86
+ }
87
+ return bytes
88
+ }
89
+
90
+ /**
91
+ * Convert Uint8Array to hex string
92
+ */
93
+ function bytesToHex(bytes: Uint8Array): string {
94
+ return Array.from(bytes)
95
+ .map((b) => b.toString(16).padStart(2, "0"))
96
+ .join("")
97
+ }
98
+
99
+ // =============================================================================
100
+ // Tests
101
+ // =============================================================================
102
+
103
+ describe("Bee Compatibility Tests", () => {
104
+ describe("Public Key Generation from Private Key", () => {
105
+ it("should generate consistent public key for Key 0", () => {
106
+ const privKey = hexToBytes(BEE_PRIVATE_KEYS.KEY_0)
107
+ const pubKey = publicKeyFromPrivate(privKey)
108
+
109
+ // Verify the public key is 32 bytes each for x and y
110
+ expect(pubKey.x.length).toBe(32)
111
+ expect(pubKey.y.length).toBe(32)
112
+
113
+ // Verify roundtrip through compression
114
+ const compressed = compressPublicKey(pubKey.x, pubKey.y)
115
+ const decompressed = publicKeyFromCompressed(compressed)
116
+ expect(decompressed.x).toEqual(pubKey.x)
117
+ expect(decompressed.y).toEqual(pubKey.y)
118
+ })
119
+
120
+ it("should generate different public keys for each private key", () => {
121
+ const privKey0 = hexToBytes(BEE_PRIVATE_KEYS.KEY_0)
122
+ const privKey1 = hexToBytes(BEE_PRIVATE_KEYS.KEY_1)
123
+ const privKey2 = hexToBytes(BEE_PRIVATE_KEYS.KEY_2)
124
+
125
+ const pubKey0 = publicKeyFromPrivate(privKey0)
126
+ const pubKey1 = publicKeyFromPrivate(privKey1)
127
+ const pubKey2 = publicKeyFromPrivate(privKey2)
128
+
129
+ expect(bytesToHex(pubKey0.x)).not.toBe(bytesToHex(pubKey1.x))
130
+ expect(bytesToHex(pubKey1.x)).not.toBe(bytesToHex(pubKey2.x))
131
+ expect(bytesToHex(pubKey0.x)).not.toBe(bytesToHex(pubKey2.x))
132
+ })
133
+ })
134
+
135
+ describe("ECDH Shared Secret", () => {
136
+ it("should compute shared secret with correct length from Bee ECDH test vector keys", () => {
137
+ const privKey = hexToBytes(BEE_ECDH_TEST.PRIVATE_KEY)
138
+ const compressedPubKey = hexToBytes(BEE_ECDH_TEST.PUBLIC_KEY_COMPRESSED)
139
+
140
+ // Decompress the public key
141
+ const pubKey = publicKeyFromCompressed(compressedPubKey)
142
+
143
+ // Compute ECDH shared secret (x-coordinate only)
144
+ const sharedSecret = ecdhSharedSecret(privKey, pubKey.x, pubKey.y)
145
+
146
+ expect(sharedSecret.length).toBe(32)
147
+ })
148
+
149
+ it("should correctly decompress and use Bee ECDH test public key", () => {
150
+ // Verify the public key from ECDH test can be decompressed and used
151
+ const compressedPubKey = hexToBytes(BEE_ECDH_TEST.PUBLIC_KEY_COMPRESSED)
152
+ const pubKey = publicKeyFromCompressed(compressedPubKey)
153
+
154
+ // Verify coordinates are valid 32-byte values
155
+ expect(pubKey.x.length).toBe(32)
156
+ expect(pubKey.y.length).toBe(32)
157
+
158
+ // Verify the x-coordinate matches what's in the compressed key (bytes 1-32)
159
+ expect(bytesToHex(pubKey.x)).toBe(
160
+ BEE_ECDH_TEST.PUBLIC_KEY_COMPRESSED.slice(2),
161
+ )
162
+
163
+ // Verify re-compression produces original
164
+ const recompressed = compressPublicKey(pubKey.x, pubKey.y)
165
+ expect(bytesToHex(recompressed)).toBe(BEE_ECDH_TEST.PUBLIC_KEY_COMPRESSED)
166
+ })
167
+
168
+ it("should produce symmetric ECDH results between two parties", () => {
169
+ const privKey0 = hexToBytes(BEE_PRIVATE_KEYS.KEY_0)
170
+ const privKey1 = hexToBytes(BEE_PRIVATE_KEYS.KEY_1)
171
+
172
+ const pubKey0 = publicKeyFromPrivate(privKey0)
173
+ const pubKey1 = publicKeyFromPrivate(privKey1)
174
+
175
+ // Key0 computes shared secret with Key1's public
176
+ const shared01 = ecdhSharedSecret(privKey0, pubKey1.x, pubKey1.y)
177
+
178
+ // Key1 computes shared secret with Key0's public
179
+ const shared10 = ecdhSharedSecret(privKey1, pubKey0.x, pubKey0.y)
180
+
181
+ // Both should be equal (ECDH symmetry)
182
+ expect(bytesToHex(shared01)).toBe(bytesToHex(shared10))
183
+ })
184
+ })
185
+
186
+ describe("Key Derivation - Lookup Keys", () => {
187
+ it("should derive correct lookup key for Publisher (Key 1) with Key 0 public", () => {
188
+ // Bee's setupAccessLogic() uses Key 1 as publisher
189
+ const privKey1 = hexToBytes(BEE_PRIVATE_KEYS.KEY_1)
190
+ const privKey0 = hexToBytes(BEE_PRIVATE_KEYS.KEY_0)
191
+ const pubKey0 = publicKeyFromPrivate(privKey0)
192
+
193
+ const { lookupKey } = deriveKeys(privKey1, pubKey0.x, pubKey0.y)
194
+
195
+ expect(bytesToHex(lookupKey)).toBe(
196
+ BEE_EXPECTED_LOOKUP_KEYS.KEY1_WITH_KEY0_PUB,
197
+ )
198
+ })
199
+
200
+ it("should derive correct lookup key for Publisher (Key 1) self-lookup", () => {
201
+ const privKey1 = hexToBytes(BEE_PRIVATE_KEYS.KEY_1)
202
+ const pubKey1 = publicKeyFromPrivate(privKey1)
203
+
204
+ const { lookupKey } = deriveKeys(privKey1, pubKey1.x, pubKey1.y)
205
+
206
+ expect(bytesToHex(lookupKey)).toBe(BEE_EXPECTED_LOOKUP_KEYS.KEY1_SELF)
207
+ })
208
+
209
+ it("should derive correct lookup key for Publisher (Key 1) with Key 2 public", () => {
210
+ const privKey1 = hexToBytes(BEE_PRIVATE_KEYS.KEY_1)
211
+ const privKey2 = hexToBytes(BEE_PRIVATE_KEYS.KEY_2)
212
+ const pubKey2 = publicKeyFromPrivate(privKey2)
213
+
214
+ const { lookupKey } = deriveKeys(privKey1, pubKey2.x, pubKey2.y)
215
+
216
+ expect(bytesToHex(lookupKey)).toBe(
217
+ BEE_EXPECTED_LOOKUP_KEYS.KEY1_WITH_KEY2_PUB,
218
+ )
219
+ })
220
+ })
221
+
222
+ describe("Symmetric ECDH Property", () => {
223
+ it("should produce same lookup key from both directions (Key0 <-> Key1)", () => {
224
+ const privKey0 = hexToBytes(BEE_PRIVATE_KEYS.KEY_0)
225
+ const privKey1 = hexToBytes(BEE_PRIVATE_KEYS.KEY_1)
226
+
227
+ const pubKey0 = publicKeyFromPrivate(privKey0)
228
+ const pubKey1 = publicKeyFromPrivate(privKey1)
229
+
230
+ // Key 1 derives lookup key with Key 0's public key
231
+ const keys10 = deriveKeys(privKey1, pubKey0.x, pubKey0.y)
232
+
233
+ // Key 0 derives lookup key with Key 1's public key
234
+ const keys01 = deriveKeys(privKey0, pubKey1.x, pubKey1.y)
235
+
236
+ // Both should produce the same lookup key (ECDH symmetry)
237
+ expect(bytesToHex(keys10.lookupKey)).toBe(
238
+ BEE_EXPECTED_LOOKUP_KEYS.KEY1_WITH_KEY0_PUB,
239
+ )
240
+ expect(bytesToHex(keys01.lookupKey)).toBe(
241
+ BEE_EXPECTED_LOOKUP_KEYS.KEY1_WITH_KEY0_PUB,
242
+ )
243
+ expect(bytesToHex(keys10.lookupKey)).toBe(bytesToHex(keys01.lookupKey))
244
+
245
+ // Access key decryption keys should also match
246
+ expect(bytesToHex(keys10.accessKeyDecryptionKey)).toBe(
247
+ bytesToHex(keys01.accessKeyDecryptionKey),
248
+ )
249
+ })
250
+
251
+ it("should produce same lookup key from both directions (Key1 <-> Key2)", () => {
252
+ const privKey1 = hexToBytes(BEE_PRIVATE_KEYS.KEY_1)
253
+ const privKey2 = hexToBytes(BEE_PRIVATE_KEYS.KEY_2)
254
+
255
+ const pubKey1 = publicKeyFromPrivate(privKey1)
256
+ const pubKey2 = publicKeyFromPrivate(privKey2)
257
+
258
+ // Key 1 derives lookup key with Key 2's public key
259
+ const keys12 = deriveKeys(privKey1, pubKey2.x, pubKey2.y)
260
+
261
+ // Key 2 derives lookup key with Key 1's public key
262
+ const keys21 = deriveKeys(privKey2, pubKey1.x, pubKey1.y)
263
+
264
+ // Both should produce the same lookup key (ECDH symmetry)
265
+ expect(bytesToHex(keys12.lookupKey)).toBe(
266
+ BEE_EXPECTED_LOOKUP_KEYS.KEY1_WITH_KEY2_PUB,
267
+ )
268
+ expect(bytesToHex(keys21.lookupKey)).toBe(
269
+ BEE_EXPECTED_LOOKUP_KEYS.KEY1_WITH_KEY2_PUB,
270
+ )
271
+ })
272
+
273
+ it("should produce same lookup key from both directions (Key0 <-> Key2)", () => {
274
+ const privKey0 = hexToBytes(BEE_PRIVATE_KEYS.KEY_0)
275
+ const privKey2 = hexToBytes(BEE_PRIVATE_KEYS.KEY_2)
276
+
277
+ const pubKey0 = publicKeyFromPrivate(privKey0)
278
+ const pubKey2 = publicKeyFromPrivate(privKey2)
279
+
280
+ // Key0 derives lookup key with Key2's public key
281
+ const keys02 = deriveKeys(privKey0, pubKey2.x, pubKey2.y)
282
+
283
+ // Key2 derives lookup key with Key0's public key
284
+ const keys20 = deriveKeys(privKey2, pubKey0.x, pubKey0.y)
285
+
286
+ // Both should produce the same lookup key (ECDH symmetry)
287
+ expect(bytesToHex(keys02.lookupKey)).toBe(bytesToHex(keys20.lookupKey))
288
+ expect(bytesToHex(keys02.accessKeyDecryptionKey)).toBe(
289
+ bytesToHex(keys20.accessKeyDecryptionKey),
290
+ )
291
+ })
292
+ })
293
+
294
+ describe("Public Key Compression/Decompression", () => {
295
+ it("should correctly decompress Bee ECDH test public key", () => {
296
+ const compressedPubKey = hexToBytes(BEE_ECDH_TEST.PUBLIC_KEY_COMPRESSED)
297
+
298
+ // Decompress
299
+ const decompressed = publicKeyFromCompressed(compressedPubKey)
300
+
301
+ // Verify x and y are 32 bytes each
302
+ expect(decompressed.x.length).toBe(32)
303
+ expect(decompressed.y.length).toBe(32)
304
+
305
+ // Re-compress and verify it matches original
306
+ const recompressed = compressPublicKey(decompressed.x, decompressed.y)
307
+ expect(bytesToHex(recompressed)).toBe(
308
+ bytesToHex(hexToBytes(BEE_ECDH_TEST.PUBLIC_KEY_COMPRESSED)),
309
+ )
310
+ })
311
+
312
+ it("should roundtrip compress/decompress for all Bee test keys", () => {
313
+ const privateKeys = [
314
+ BEE_PRIVATE_KEYS.KEY_0,
315
+ BEE_PRIVATE_KEYS.KEY_1,
316
+ BEE_PRIVATE_KEYS.KEY_2,
317
+ ]
318
+
319
+ for (const privKeyHex of privateKeys) {
320
+ const privKey = hexToBytes(privKeyHex)
321
+ const pubKey = publicKeyFromPrivate(privKey)
322
+
323
+ // Compress
324
+ const compressed = compressPublicKey(pubKey.x, pubKey.y)
325
+ expect(compressed.length).toBe(33)
326
+ expect([0x02, 0x03]).toContain(compressed[0])
327
+
328
+ // Decompress
329
+ const decompressed = publicKeyFromCompressed(compressed)
330
+
331
+ // Verify roundtrip
332
+ expect(bytesToHex(decompressed.x)).toBe(bytesToHex(pubKey.x))
333
+ expect(bytesToHex(decompressed.y)).toBe(bytesToHex(pubKey.y))
334
+ }
335
+ })
336
+
337
+ it("should handle both even and odd y coordinates", () => {
338
+ const privateKeys = [
339
+ BEE_PRIVATE_KEYS.KEY_0,
340
+ BEE_PRIVATE_KEYS.KEY_1,
341
+ BEE_PRIVATE_KEYS.KEY_2,
342
+ ]
343
+
344
+ const prefixes: number[] = []
345
+
346
+ for (const privKeyHex of privateKeys) {
347
+ const privKey = hexToBytes(privKeyHex)
348
+ const pubKey = publicKeyFromPrivate(privKey)
349
+ const compressed = compressPublicKey(pubKey.x, pubKey.y)
350
+ prefixes.push(compressed[0])
351
+ }
352
+
353
+ // Verify we have valid prefixes (0x02 or 0x03)
354
+ for (const prefix of prefixes) {
355
+ expect([0x02, 0x03]).toContain(prefix)
356
+ }
357
+ })
358
+ })
359
+
360
+ describe("Key Derivation Algorithm Verification", () => {
361
+ it("should use correct nonces for lookup key (0x00) and akd key (0x01)", () => {
362
+ const privKey1 = hexToBytes(BEE_PRIVATE_KEYS.KEY_1)
363
+ const pubKey1 = publicKeyFromPrivate(privKey1)
364
+
365
+ const { lookupKey, accessKeyDecryptionKey } = deriveKeys(
366
+ privKey1,
367
+ pubKey1.x,
368
+ pubKey1.y,
369
+ )
370
+
371
+ // lookupKey and accessKeyDecryptionKey should be different
372
+ // (same input but different nonces: 0x00 vs 0x01)
373
+ expect(bytesToHex(lookupKey)).not.toBe(bytesToHex(accessKeyDecryptionKey))
374
+
375
+ // Verify the lookup key matches Bee's expected value for Key1 self-lookup
376
+ expect(bytesToHex(lookupKey)).toBe(BEE_EXPECTED_LOOKUP_KEYS.KEY1_SELF)
377
+ })
378
+
379
+ it("should produce 32-byte keys", () => {
380
+ const privKey0 = hexToBytes(BEE_PRIVATE_KEYS.KEY_0)
381
+ const pubKey0 = publicKeyFromPrivate(privKey0)
382
+
383
+ const { lookupKey, accessKeyDecryptionKey } = deriveKeys(
384
+ privKey0,
385
+ pubKey0.x,
386
+ pubKey0.y,
387
+ )
388
+
389
+ expect(lookupKey.length).toBe(32)
390
+ expect(accessKeyDecryptionKey.length).toBe(32)
391
+ })
392
+ })
393
+
394
+ describe("Reference Encryption/Decryption - Publisher (TestDecryptRef_Publisher equivalent)", () => {
395
+ it("should allow publisher to encrypt and decrypt their own reference", () => {
396
+ // Setup: Key 1 is the publisher
397
+ const publisherPrivKey = hexToBytes(BEE_PRIVATE_KEYS.KEY_1)
398
+ const publisherPubKey = publicKeyFromPrivate(publisherPrivKey)
399
+
400
+ // Test reference to encrypt
401
+ const testReference = hexToBytes(BEE_TEST_REFERENCE)
402
+
403
+ // Step 1: Generate a random access key
404
+ const accessKey = generateRandomKey()
405
+ expect(accessKey.length).toBe(32)
406
+
407
+ // Step 2: Encrypt the reference with the access key
408
+ const encryptedReference = counterModeEncrypt(testReference, accessKey)
409
+ expect(encryptedReference.length).toBe(testReference.length)
410
+ expect(bytesToHex(encryptedReference)).not.toBe(bytesToHex(testReference))
411
+
412
+ // Step 3: Create ACT entry for publisher (self-lookup)
413
+ // Publisher derives keys with their own public key
414
+ const { lookupKey, accessKeyDecryptionKey } = deriveKeys(
415
+ publisherPrivKey,
416
+ publisherPubKey.x,
417
+ publisherPubKey.y,
418
+ )
419
+
420
+ // Verify lookup key matches expected value for Key1 self-lookup
421
+ expect(bytesToHex(lookupKey)).toBe(BEE_EXPECTED_LOOKUP_KEYS.KEY1_SELF)
422
+
423
+ // Encrypt the access key with the accessKeyDecryptionKey
424
+ const encryptedAccessKey = counterModeEncrypt(
425
+ accessKey,
426
+ accessKeyDecryptionKey,
427
+ )
428
+
429
+ // Create ACT with single entry for publisher
430
+ const actEntries: ActEntry[] = [
431
+ {
432
+ lookupKey,
433
+ encryptedAccessKey,
434
+ },
435
+ ]
436
+
437
+ // Step 4: Publisher looks up their entry in ACT (using array directly)
438
+ const {
439
+ lookupKey: derivedLookupKey,
440
+ accessKeyDecryptionKey: derivedAkdKey,
441
+ } = deriveKeys(publisherPrivKey, publisherPubKey.x, publisherPubKey.y)
442
+
443
+ const entry = findEntryByLookupKey(actEntries, derivedLookupKey)
444
+ expect(entry).toBeDefined()
445
+
446
+ // Step 5: Publisher decrypts the access key
447
+ const decryptedAccessKey = counterModeDecrypt(
448
+ entry!.encryptedAccessKey,
449
+ derivedAkdKey,
450
+ )
451
+ expect(bytesToHex(decryptedAccessKey)).toBe(bytesToHex(accessKey))
452
+
453
+ // Step 6: Publisher decrypts the reference
454
+ const decryptedReference = counterModeDecrypt(
455
+ encryptedReference,
456
+ decryptedAccessKey,
457
+ )
458
+ expect(bytesToHex(decryptedReference)).toBe(BEE_TEST_REFERENCE)
459
+ })
460
+
461
+ it("should produce consistent encryption across multiple operations", () => {
462
+ const testReference = hexToBytes(BEE_TEST_REFERENCE)
463
+
464
+ // Use a fixed access key for deterministic testing
465
+ const fixedAccessKey = hexToBytes(
466
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
467
+ )
468
+
469
+ // Encrypt twice with same key should produce same result
470
+ const encrypted1 = counterModeEncrypt(testReference, fixedAccessKey)
471
+ const encrypted2 = counterModeEncrypt(testReference, fixedAccessKey)
472
+ expect(bytesToHex(encrypted1)).toBe(bytesToHex(encrypted2))
473
+
474
+ // Decrypt should return original
475
+ const decrypted = counterModeDecrypt(encrypted1, fixedAccessKey)
476
+ expect(bytesToHex(decrypted)).toBe(BEE_TEST_REFERENCE)
477
+
478
+ // Counter mode is symmetric
479
+ const reencrypted = counterModeEncrypt(decrypted, fixedAccessKey)
480
+ expect(bytesToHex(reencrypted)).toBe(bytesToHex(encrypted1))
481
+ })
482
+ })
483
+
484
+ describe("Reference Encryption/Decryption - Grantee (TestDecryptRefWithGrantee_Success equivalent)", () => {
485
+ it("should allow grantee to decrypt reference shared by publisher", () => {
486
+ // Setup: Key 1 is the publisher, Key 2 is the grantee
487
+ const publisherPrivKey = hexToBytes(BEE_PRIVATE_KEYS.KEY_1)
488
+ const publisherPubKey = publicKeyFromPrivate(publisherPrivKey)
489
+
490
+ const granteePrivKey = hexToBytes(BEE_PRIVATE_KEYS.KEY_2)
491
+ const granteePubKey = publicKeyFromPrivate(granteePrivKey)
492
+
493
+ // Test reference to encrypt
494
+ const testReference = hexToBytes(BEE_TEST_REFERENCE)
495
+
496
+ // Step 1: Publisher generates a random access key
497
+ const accessKey = generateRandomKey()
498
+
499
+ // Step 2: Publisher encrypts the reference with the access key
500
+ const encryptedReference = counterModeEncrypt(testReference, accessKey)
501
+
502
+ // Step 3: Publisher creates ACT entries for themselves AND the grantee
503
+
504
+ // Entry for publisher (self-lookup)
505
+ const publisherKeys = deriveKeys(
506
+ publisherPrivKey,
507
+ publisherPubKey.x,
508
+ publisherPubKey.y,
509
+ )
510
+ const encryptedAccessKeyForPublisher = counterModeEncrypt(
511
+ accessKey,
512
+ publisherKeys.accessKeyDecryptionKey,
513
+ )
514
+
515
+ // Entry for grantee (publisher derives with grantee's public key)
516
+ const granteeEntryKeys = deriveKeys(
517
+ publisherPrivKey,
518
+ granteePubKey.x,
519
+ granteePubKey.y,
520
+ )
521
+ const encryptedAccessKeyForGrantee = counterModeEncrypt(
522
+ accessKey,
523
+ granteeEntryKeys.accessKeyDecryptionKey,
524
+ )
525
+
526
+ // Verify lookup keys match expected values
527
+ expect(bytesToHex(publisherKeys.lookupKey)).toBe(
528
+ BEE_EXPECTED_LOOKUP_KEYS.KEY1_SELF,
529
+ )
530
+ expect(bytesToHex(granteeEntryKeys.lookupKey)).toBe(
531
+ BEE_EXPECTED_LOOKUP_KEYS.KEY1_WITH_KEY2_PUB,
532
+ )
533
+
534
+ // Create ACT with both entries
535
+ const actEntries: ActEntry[] = [
536
+ {
537
+ lookupKey: publisherKeys.lookupKey,
538
+ encryptedAccessKey: encryptedAccessKeyForPublisher,
539
+ },
540
+ {
541
+ lookupKey: granteeEntryKeys.lookupKey,
542
+ encryptedAccessKey: encryptedAccessKeyForGrantee,
543
+ },
544
+ ]
545
+
546
+ // Step 4: Grantee derives keys using their private key + publisher's public key
547
+ const granteeDerivation = deriveKeys(
548
+ granteePrivKey,
549
+ publisherPubKey.x,
550
+ publisherPubKey.y,
551
+ )
552
+
553
+ // Due to ECDH symmetry, grantee's derivation should produce same lookup key
554
+ expect(bytesToHex(granteeDerivation.lookupKey)).toBe(
555
+ BEE_EXPECTED_LOOKUP_KEYS.KEY1_WITH_KEY2_PUB,
556
+ )
557
+
558
+ // Step 5: Grantee looks up their entry in ACT
559
+ const granteeEntry = findEntryByLookupKey(
560
+ actEntries,
561
+ granteeDerivation.lookupKey,
562
+ )
563
+ expect(granteeEntry).toBeDefined()
564
+
565
+ // Step 6: Grantee decrypts the access key
566
+ const decryptedAccessKey = counterModeDecrypt(
567
+ granteeEntry!.encryptedAccessKey,
568
+ granteeDerivation.accessKeyDecryptionKey,
569
+ )
570
+ expect(bytesToHex(decryptedAccessKey)).toBe(bytesToHex(accessKey))
571
+
572
+ // Step 7: Grantee decrypts the reference
573
+ const decryptedReference = counterModeDecrypt(
574
+ encryptedReference,
575
+ decryptedAccessKey,
576
+ )
577
+ expect(bytesToHex(decryptedReference)).toBe(BEE_TEST_REFERENCE)
578
+ })
579
+
580
+ it("should not allow non-grantee to decrypt reference", () => {
581
+ // Setup: Key 1 is the publisher, Key 2 is the grantee, Key 0 is NOT a grantee
582
+ const publisherPrivKey = hexToBytes(BEE_PRIVATE_KEYS.KEY_1)
583
+ const publisherPubKey = publicKeyFromPrivate(publisherPrivKey)
584
+
585
+ const granteePrivKey = hexToBytes(BEE_PRIVATE_KEYS.KEY_2)
586
+ const granteePubKey = publicKeyFromPrivate(granteePrivKey)
587
+
588
+ const nonGranteePrivKey = hexToBytes(BEE_PRIVATE_KEYS.KEY_0)
589
+
590
+ // Test reference
591
+ const testReference = hexToBytes(BEE_TEST_REFERENCE)
592
+
593
+ // Publisher creates ACT only for themselves and Key 2 (not Key 0)
594
+ const accessKey = generateRandomKey()
595
+
596
+ // Create entries for publisher and grantee only
597
+ const publisherKeys = deriveKeys(
598
+ publisherPrivKey,
599
+ publisherPubKey.x,
600
+ publisherPubKey.y,
601
+ )
602
+ const granteeEntryKeys = deriveKeys(
603
+ publisherPrivKey,
604
+ granteePubKey.x,
605
+ granteePubKey.y,
606
+ )
607
+
608
+ const actEntries: ActEntry[] = [
609
+ {
610
+ lookupKey: publisherKeys.lookupKey,
611
+ encryptedAccessKey: counterModeEncrypt(
612
+ accessKey,
613
+ publisherKeys.accessKeyDecryptionKey,
614
+ ),
615
+ },
616
+ {
617
+ lookupKey: granteeEntryKeys.lookupKey,
618
+ encryptedAccessKey: counterModeEncrypt(
619
+ accessKey,
620
+ granteeEntryKeys.accessKeyDecryptionKey,
621
+ ),
622
+ },
623
+ ]
624
+
625
+ // Non-grantee (Key 0) tries to derive keys with publisher's public key
626
+ const nonGranteeDerivation = deriveKeys(
627
+ nonGranteePrivKey,
628
+ publisherPubKey.x,
629
+ publisherPubKey.y,
630
+ )
631
+
632
+ // Non-grantee's lookup key should NOT match any entry in the ACT
633
+ const nonGranteeEntry = findEntryByLookupKey(
634
+ actEntries,
635
+ nonGranteeDerivation.lookupKey,
636
+ )
637
+
638
+ // Entry should not be found (Key 0 was not added as a grantee)
639
+ expect(nonGranteeEntry).toBeUndefined()
640
+ })
641
+
642
+ it("should allow multiple grantees to independently decrypt", () => {
643
+ // Setup: Key 1 is the publisher, Key 0 and Key 2 are grantees
644
+ const publisherPrivKey = hexToBytes(BEE_PRIVATE_KEYS.KEY_1)
645
+ const publisherPubKey = publicKeyFromPrivate(publisherPrivKey)
646
+
647
+ const grantee0PrivKey = hexToBytes(BEE_PRIVATE_KEYS.KEY_0)
648
+ const grantee0PubKey = publicKeyFromPrivate(grantee0PrivKey)
649
+
650
+ const grantee2PrivKey = hexToBytes(BEE_PRIVATE_KEYS.KEY_2)
651
+ const grantee2PubKey = publicKeyFromPrivate(grantee2PrivKey)
652
+
653
+ // Test reference
654
+ const testReference = hexToBytes(BEE_TEST_REFERENCE)
655
+
656
+ // Publisher creates ACT for themselves and both grantees
657
+ const accessKey = generateRandomKey()
658
+ const encryptedReference = counterModeEncrypt(testReference, accessKey)
659
+
660
+ // Derive keys for all parties
661
+ const publisherKeys = deriveKeys(
662
+ publisherPrivKey,
663
+ publisherPubKey.x,
664
+ publisherPubKey.y,
665
+ )
666
+ const grantee0EntryKeys = deriveKeys(
667
+ publisherPrivKey,
668
+ grantee0PubKey.x,
669
+ grantee0PubKey.y,
670
+ )
671
+ const grantee2EntryKeys = deriveKeys(
672
+ publisherPrivKey,
673
+ grantee2PubKey.x,
674
+ grantee2PubKey.y,
675
+ )
676
+
677
+ // Verify lookup keys match expected values
678
+ expect(bytesToHex(publisherKeys.lookupKey)).toBe(
679
+ BEE_EXPECTED_LOOKUP_KEYS.KEY1_SELF,
680
+ )
681
+ expect(bytesToHex(grantee0EntryKeys.lookupKey)).toBe(
682
+ BEE_EXPECTED_LOOKUP_KEYS.KEY1_WITH_KEY0_PUB,
683
+ )
684
+ expect(bytesToHex(grantee2EntryKeys.lookupKey)).toBe(
685
+ BEE_EXPECTED_LOOKUP_KEYS.KEY1_WITH_KEY2_PUB,
686
+ )
687
+
688
+ // Create ACT with all entries
689
+ const actEntries: ActEntry[] = [
690
+ {
691
+ lookupKey: publisherKeys.lookupKey,
692
+ encryptedAccessKey: counterModeEncrypt(
693
+ accessKey,
694
+ publisherKeys.accessKeyDecryptionKey,
695
+ ),
696
+ },
697
+ {
698
+ lookupKey: grantee0EntryKeys.lookupKey,
699
+ encryptedAccessKey: counterModeEncrypt(
700
+ accessKey,
701
+ grantee0EntryKeys.accessKeyDecryptionKey,
702
+ ),
703
+ },
704
+ {
705
+ lookupKey: grantee2EntryKeys.lookupKey,
706
+ encryptedAccessKey: counterModeEncrypt(
707
+ accessKey,
708
+ grantee2EntryKeys.accessKeyDecryptionKey,
709
+ ),
710
+ },
711
+ ]
712
+
713
+ expect(actEntries.length).toBe(3)
714
+
715
+ // Grantee 0 decrypts
716
+ const grantee0Derivation = deriveKeys(
717
+ grantee0PrivKey,
718
+ publisherPubKey.x,
719
+ publisherPubKey.y,
720
+ )
721
+ const grantee0Entry = findEntryByLookupKey(
722
+ actEntries,
723
+ grantee0Derivation.lookupKey,
724
+ )
725
+ expect(grantee0Entry).toBeDefined()
726
+
727
+ const decryptedAccessKey0 = counterModeDecrypt(
728
+ grantee0Entry!.encryptedAccessKey,
729
+ grantee0Derivation.accessKeyDecryptionKey,
730
+ )
731
+ const decryptedRef0 = counterModeDecrypt(
732
+ encryptedReference,
733
+ decryptedAccessKey0,
734
+ )
735
+ expect(bytesToHex(decryptedRef0)).toBe(BEE_TEST_REFERENCE)
736
+
737
+ // Grantee 2 decrypts
738
+ const grantee2Derivation = deriveKeys(
739
+ grantee2PrivKey,
740
+ publisherPubKey.x,
741
+ publisherPubKey.y,
742
+ )
743
+ const grantee2Entry = findEntryByLookupKey(
744
+ actEntries,
745
+ grantee2Derivation.lookupKey,
746
+ )
747
+ expect(grantee2Entry).toBeDefined()
748
+
749
+ const decryptedAccessKey2 = counterModeDecrypt(
750
+ grantee2Entry!.encryptedAccessKey,
751
+ grantee2Derivation.accessKeyDecryptionKey,
752
+ )
753
+ const decryptedRef2 = counterModeDecrypt(
754
+ encryptedReference,
755
+ decryptedAccessKey2,
756
+ )
757
+ expect(bytesToHex(decryptedRef2)).toBe(BEE_TEST_REFERENCE)
758
+ })
759
+ })
760
+
761
+ describe("ACT Entry Creation - TestAddPublisher equivalent", () => {
762
+ // Equivalent to bee/pkg/accesscontrol/access_test.go:137-161 (TestAddPublisher)
763
+ // This test verifies that adding a publisher entry to the ACT produces
764
+ // the correct lookup key and encrypted access key structure.
765
+
766
+ it("should create publisher ACT entry with correct lookup key", () => {
767
+ // Key 1 is the access logic owner (from setupAccessLogic in Bee)
768
+ // Adding Key 0 as a grantee
769
+ const publisherPrivKey = hexToBytes(BEE_PRIVATE_KEYS.KEY_1)
770
+ const granteePrivKey = hexToBytes(BEE_PRIVATE_KEYS.KEY_0)
771
+ const granteePubKey = publicKeyFromPrivate(granteePrivKey)
772
+
773
+ // Generate a random access key (simulating what Bee does)
774
+ const accessKey = generateRandomKey()
775
+
776
+ // Publisher derives keys for the grantee
777
+ const { lookupKey, accessKeyDecryptionKey } = deriveKeys(
778
+ publisherPrivKey,
779
+ granteePubKey.x,
780
+ granteePubKey.y,
781
+ )
782
+
783
+ // Verify lookup key matches expected value from Bee's test
784
+ expect(bytesToHex(lookupKey)).toBe(
785
+ BEE_EXPECTED_LOOKUP_KEYS.KEY1_WITH_KEY0_PUB,
786
+ )
787
+
788
+ // Encrypt the access key
789
+ const encryptedAccessKey = counterModeEncrypt(
790
+ accessKey,
791
+ accessKeyDecryptionKey,
792
+ )
793
+
794
+ // Verify encrypted access key has correct length (32 bytes)
795
+ const ENCRYPTED_ACCESS_KEY_LENGTH = 32
796
+ expect(encryptedAccessKey.length).toBe(ENCRYPTED_ACCESS_KEY_LENGTH)
797
+
798
+ // Verify the ACT entry can be created
799
+ const actEntry: ActEntry = {
800
+ lookupKey,
801
+ encryptedAccessKey,
802
+ }
803
+
804
+ // Verify entry can be found by lookup key
805
+ const found = findEntryByLookupKey([actEntry], lookupKey)
806
+ expect(found).toBeDefined()
807
+ expect(bytesToHex(found!.lookupKey)).toBe(
808
+ BEE_EXPECTED_LOOKUP_KEYS.KEY1_WITH_KEY0_PUB,
809
+ )
810
+ expect(found!.encryptedAccessKey.length).toBe(ENCRYPTED_ACCESS_KEY_LENGTH)
811
+ })
812
+ })
813
+
814
+ describe("ACT Entry Creation - TestAddNewGranteeToContent equivalent", () => {
815
+ // Equivalent to bee/pkg/accesscontrol/access_test.go:163-214 (TestAddNewGranteeToContent)
816
+ // This test verifies that adding multiple grantees to the same ACT produces
817
+ // correct lookup keys for each grantee.
818
+
819
+ it("should add multiple grantees with correct lookup keys", () => {
820
+ // Key 1 is the access logic owner (from setupAccessLogic in Bee)
821
+ const publisherPrivKey = hexToBytes(BEE_PRIVATE_KEYS.KEY_1)
822
+ const publisherPubKey = publicKeyFromPrivate(publisherPrivKey)
823
+
824
+ const grantee0PrivKey = hexToBytes(BEE_PRIVATE_KEYS.KEY_0)
825
+ const grantee0PubKey = publicKeyFromPrivate(grantee0PrivKey)
826
+
827
+ const grantee2PrivKey = hexToBytes(BEE_PRIVATE_KEYS.KEY_2)
828
+ const grantee2PubKey = publicKeyFromPrivate(grantee2PrivKey)
829
+
830
+ // Generate a random access key (same key used for all grantees)
831
+ const accessKey = generateRandomKey()
832
+ const actEntries: ActEntry[] = []
833
+
834
+ // Add Key 0 as grantee (first addition)
835
+ const keys0 = deriveKeys(
836
+ publisherPrivKey,
837
+ grantee0PubKey.x,
838
+ grantee0PubKey.y,
839
+ )
840
+ expect(bytesToHex(keys0.lookupKey)).toBe(
841
+ BEE_EXPECTED_LOOKUP_KEYS.KEY1_WITH_KEY0_PUB,
842
+ )
843
+ actEntries.push({
844
+ lookupKey: keys0.lookupKey,
845
+ encryptedAccessKey: counterModeEncrypt(
846
+ accessKey,
847
+ keys0.accessKeyDecryptionKey,
848
+ ),
849
+ })
850
+
851
+ // Add Key 1 (self) as grantee (second addition)
852
+ const keys1 = deriveKeys(
853
+ publisherPrivKey,
854
+ publisherPubKey.x,
855
+ publisherPubKey.y,
856
+ )
857
+ expect(bytesToHex(keys1.lookupKey)).toBe(
858
+ BEE_EXPECTED_LOOKUP_KEYS.KEY1_SELF,
859
+ )
860
+ actEntries.push({
861
+ lookupKey: keys1.lookupKey,
862
+ encryptedAccessKey: counterModeEncrypt(
863
+ accessKey,
864
+ keys1.accessKeyDecryptionKey,
865
+ ),
866
+ })
867
+
868
+ // Add Key 2 as grantee (third addition)
869
+ const keys2 = deriveKeys(
870
+ publisherPrivKey,
871
+ grantee2PubKey.x,
872
+ grantee2PubKey.y,
873
+ )
874
+ expect(bytesToHex(keys2.lookupKey)).toBe(
875
+ BEE_EXPECTED_LOOKUP_KEYS.KEY1_WITH_KEY2_PUB,
876
+ )
877
+ actEntries.push({
878
+ lookupKey: keys2.lookupKey,
879
+ encryptedAccessKey: counterModeEncrypt(
880
+ accessKey,
881
+ keys2.accessKeyDecryptionKey,
882
+ ),
883
+ })
884
+
885
+ // Verify all 3 entries are created
886
+ expect(actEntries.length).toBe(3)
887
+
888
+ // Verify all encrypted access keys have correct length (32 bytes)
889
+ const ENCRYPTED_ACCESS_KEY_LENGTH = 32
890
+ for (const entry of actEntries) {
891
+ expect(entry.encryptedAccessKey.length).toBe(
892
+ ENCRYPTED_ACCESS_KEY_LENGTH,
893
+ )
894
+ }
895
+
896
+ // Verify all 3 lookup keys are unique
897
+ const lookupKeySet = new Set(
898
+ actEntries.map((e) => bytesToHex(e.lookupKey)),
899
+ )
900
+ expect(lookupKeySet.size).toBe(3)
901
+
902
+ // Verify each lookup key can be found in entries
903
+ const entry0 = findEntryByLookupKey(actEntries, keys0.lookupKey)
904
+ const entry1 = findEntryByLookupKey(actEntries, keys1.lookupKey)
905
+ const entry2 = findEntryByLookupKey(actEntries, keys2.lookupKey)
906
+
907
+ expect(entry0).toBeDefined()
908
+ expect(entry1).toBeDefined()
909
+ expect(entry2).toBeDefined()
910
+
911
+ // Verify each grantee can decrypt the access key and it matches
912
+ const decrypted0 = counterModeDecrypt(
913
+ entry0!.encryptedAccessKey,
914
+ keys0.accessKeyDecryptionKey,
915
+ )
916
+ const decrypted1 = counterModeDecrypt(
917
+ entry1!.encryptedAccessKey,
918
+ keys1.accessKeyDecryptionKey,
919
+ )
920
+ const decrypted2 = counterModeDecrypt(
921
+ entry2!.encryptedAccessKey,
922
+ keys2.accessKeyDecryptionKey,
923
+ )
924
+
925
+ expect(bytesToHex(decrypted0)).toBe(bytesToHex(accessKey))
926
+ expect(bytesToHex(decrypted1)).toBe(bytesToHex(accessKey))
927
+ expect(bytesToHex(decrypted2)).toBe(bytesToHex(accessKey))
928
+ })
929
+ })
930
+
931
+ describe("CTR Mode Verification with Bee Debug Logs", () => {
932
+ it("should decrypt to same value as Bee", () => {
933
+ const ourDecrypted = counterModeEncrypt(
934
+ hexToBytes(BEE_CTR_DEBUG.ENCRYPTED_REF),
935
+ hexToBytes(BEE_CTR_DEBUG.ACCESS_KEY),
936
+ )
937
+ expect(bytesToHex(ourDecrypted)).toBe(BEE_CTR_DEBUG.DECRYPTED_REF)
938
+ })
939
+
940
+ it("should encrypt original to same value Bee received", () => {
941
+ const ourEncrypted = counterModeEncrypt(
942
+ hexToBytes(BEE_CTR_DEBUG.DECRYPTED_REF),
943
+ hexToBytes(BEE_CTR_DEBUG.ACCESS_KEY),
944
+ )
945
+ expect(bytesToHex(ourEncrypted)).toBe(BEE_CTR_DEBUG.ENCRYPTED_REF)
946
+ })
947
+ })
948
+ })