@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,103 @@
1
+ /**
2
+ * Types for Sequential Feeds
3
+ */
4
+
5
+ import type {
6
+ Bee,
7
+ EthAddress,
8
+ Topic,
9
+ PrivateKey,
10
+ Stamper,
11
+ BeeRequestOptions,
12
+ } from "@ethersphere/bee-js"
13
+
14
+ /**
15
+ * Options for creating a sequential feed reader
16
+ */
17
+ export interface SequentialFeedOptions {
18
+ /** Bee instance for chunk operations */
19
+ bee: Bee
20
+
21
+ /** Feed topic (32 bytes) */
22
+ topic: Topic
23
+
24
+ /** Feed owner address */
25
+ owner: EthAddress
26
+ }
27
+
28
+ /**
29
+ * Options for creating a sequential feed writer
30
+ */
31
+ export interface SequentialFeedWriterOptions extends SequentialFeedOptions {
32
+ /** Private key for signing chunks */
33
+ signer: PrivateKey
34
+ }
35
+
36
+ /**
37
+ * Result from sequential feed lookup
38
+ */
39
+ export interface SequentialLookupResult {
40
+ /** Current (latest) index if found */
41
+ current?: bigint
42
+
43
+ /** Next index to use */
44
+ next: bigint
45
+ }
46
+
47
+ /**
48
+ * Interface for sequential feed finders (readers)
49
+ *
50
+ * Implementations: SyncSequentialFinder, AsyncSequentialFinder
51
+ */
52
+ export interface SequentialFinder {
53
+ /**
54
+ * Find the latest feed update index
55
+ *
56
+ * @param at - Ignored for sequential feeds (for compatibility with Go API)
57
+ * @param after - Hint of latest known index (0 if unknown)
58
+ * @returns Lookup result with current and next index
59
+ */
60
+ findAt(
61
+ at: bigint,
62
+ after?: bigint,
63
+ requestOptions?: BeeRequestOptions,
64
+ ): Promise<SequentialLookupResult>
65
+ }
66
+
67
+ /**
68
+ * Interface for sequential feed updaters (writers)
69
+ */
70
+ export interface SequentialUpdater {
71
+ /**
72
+ * Update feed with payload data
73
+ *
74
+ * @param payload - Payload to store
75
+ * @param stamper - Stamper object for stamping
76
+ * @returns SOC chunk address for utilization tracking
77
+ */
78
+ update(
79
+ payload: Uint8Array,
80
+ stamper: Stamper,
81
+ encryptionKey?: Uint8Array,
82
+ ): Promise<Uint8Array>
83
+
84
+ /**
85
+ * Get the owner address (derived from signer)
86
+ */
87
+ getOwner(): EthAddress
88
+
89
+ /**
90
+ * Get current state (for persistence/debugging)
91
+ */
92
+ getState(): { nextIndex: bigint }
93
+
94
+ /**
95
+ * Restore state (from persistence)
96
+ */
97
+ setState(state: { nextIndex: bigint }): void
98
+
99
+ /**
100
+ * Reset updater state (useful for testing or reinitialization)
101
+ */
102
+ reset(): void
103
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Basic Sequential Feed Updater
3
+ *
4
+ * Handles writing updates to sequential feeds by incrementing the index.
5
+ */
6
+
7
+ import { Binary } from "cafe-utility"
8
+ import type { Bee, Stamper } from "@ethersphere/bee-js"
9
+ import { EthAddress, Topic, PrivateKey, Identifier } from "@ethersphere/bee-js"
10
+ import { uploadEncryptedSOC, uploadSOC } from "../../upload-encrypted-data"
11
+ import type { SequentialUpdater } from "./types"
12
+
13
+ export class BasicSequentialUpdater implements SequentialUpdater {
14
+ private nextIndex: bigint = 0n
15
+
16
+ constructor(
17
+ private readonly bee: Bee,
18
+ private readonly topic: Topic,
19
+ private readonly signer: PrivateKey,
20
+ ) {}
21
+
22
+ async update(
23
+ payload: Uint8Array,
24
+ stamper: Stamper,
25
+ encryptionKey?: Uint8Array,
26
+ ): Promise<Uint8Array> {
27
+ const identifier = this.makeIdentifier(this.nextIndex)
28
+
29
+ const result = encryptionKey
30
+ ? await uploadEncryptedSOC(
31
+ this.bee,
32
+ stamper,
33
+ this.signer,
34
+ identifier,
35
+ payload,
36
+ encryptionKey,
37
+ { deferred: false },
38
+ )
39
+ : await uploadSOC(this.bee, stamper, this.signer, identifier, payload, {
40
+ deferred: false,
41
+ })
42
+
43
+ this.nextIndex += 1n
44
+
45
+ return result.socAddress
46
+ }
47
+
48
+ getOwner(): EthAddress {
49
+ return this.signer.publicKey().address()
50
+ }
51
+
52
+ getState(): { nextIndex: bigint } {
53
+ return { nextIndex: this.nextIndex }
54
+ }
55
+
56
+ setState(state: { nextIndex: bigint }): void {
57
+ this.nextIndex = state.nextIndex
58
+ }
59
+
60
+ reset(): void {
61
+ this.nextIndex = 0n
62
+ }
63
+
64
+ private makeIdentifier(index: bigint): Identifier {
65
+ const indexBytes = Binary.numberToUint64(index, "BE")
66
+ const identifierBytes = Binary.keccak256(
67
+ Binary.concatBytes(this.topic.toUint8Array(), indexBytes),
68
+ )
69
+ return new Identifier(identifierBytes)
70
+ }
71
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./types"
2
+ export * from "./chunking"
3
+ export * from "./upload-data"
4
+ export * from "./feeds"
5
+ export * from "./act"
@@ -0,0 +1,427 @@
1
+ import { describe, it, expect } from "vitest"
2
+ import {
3
+ buildMinimalManifest,
4
+ buildBzzCompatibleManifest,
5
+ extractReferenceFromManifest,
6
+ extractContentFromFlatManifest,
7
+ } from "./manifest-builder"
8
+
9
+ // Test vector from Bee Go: pkg/manifest/mantaray/marshal_test.go
10
+ // Valid manifest with "/" fork and metadata
11
+ const VALID_MANIFEST_HEX =
12
+ "00000000000000000000000000000000000000000000000000000000000000005768b3b6a7db56d21d1abff40d41cebfc83448fed8d7e9b06ec0d3b073f28f200000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000016012f0000000000000000000000000000000000000000000000000000000000e87f95c3d081c4fede769b6c69e27b435e525cbd25c6715c607e7c531e329639005d7b22776562736974652d696e6465782d646f63756d656e74223a2233356561656538316262363338303436393965633637316265323736326465626665346662643330636461646139303232393239646131613965366134366436227d0a"
13
+
14
+ // Version hash for v0.2 (current)
15
+ const VERSION_02_HASH =
16
+ "5768b3b6a7db56d21d1abff40d41cebfc83448fed8d7e9b06ec0d3b073f28f7b"
17
+
18
+ describe("manifest-builder", () => {
19
+ describe("buildMinimalManifest", () => {
20
+ it("should create a valid manifest with / fork", () => {
21
+ const contentRef =
22
+ "f78f17c6da518054b2ced74fb8fffd53a16b70685e3bb706399ceceaf679bd00"
23
+ const manifest = buildMinimalManifest(contentRef)
24
+
25
+ // Manifest should be exactly 192 bytes (header 128 + fork 64)
26
+ expect(manifest.length).toBe(192)
27
+
28
+ // First 32 bytes are obfuscation key (all zeros)
29
+ const obfuscationKey = manifest.slice(0, 32)
30
+ expect(obfuscationKey.every((b) => b === 0)).toBe(true)
31
+
32
+ // Bytes 32-62 should contain version hash
33
+ const versionHash = manifest.slice(32, 63)
34
+ expect(uint8ArrayToHex(versionHash)).toBe(VERSION_02_HASH.slice(0, 62))
35
+
36
+ // Byte 63 is refBytesCount = 32
37
+ expect(manifest[63]).toBe(32)
38
+
39
+ // Bytes 64-95 are entry (all zeros for root)
40
+ const entry = manifest.slice(64, 96)
41
+ expect(entry.every((b) => b === 0)).toBe(true)
42
+
43
+ // Bytes 96-127 are index bitmap (bit 47 set for '/')
44
+ const indexBitmap = manifest.slice(96, 128)
45
+ expect(indexBitmap[5]).toBe(0x80) // Byte 5, bit 7 set for '/'
46
+ })
47
+
48
+ it("should embed content reference correctly", () => {
49
+ const contentRef =
50
+ "f78f17c6da518054b2ced74fb8fffd53a16b70685e3bb706399ceceaf679bd00"
51
+ const manifest = buildMinimalManifest(contentRef)
52
+
53
+ // Fork starts at offset 128
54
+ // Flags at 128 = 0x02 (TYPE_VALUE: has targetAddress)
55
+ expect(manifest[128]).toBe(0x02)
56
+
57
+ // PrefixLen at 129 = 1
58
+ expect(manifest[129]).toBe(1)
59
+
60
+ // Prefix at 130 = '/' (0x2f)
61
+ expect(manifest[130]).toBe(0x2f)
62
+
63
+ // Reference at 160-191
64
+ const reference = manifest.slice(160, 192)
65
+ expect(uint8ArrayToHex(reference)).toBe(contentRef)
66
+ })
67
+
68
+ it("should round-trip correctly", () => {
69
+ const contentRef =
70
+ "f78f17c6da518054b2ced74fb8fffd53a16b70685e3bb706399ceceaf679bd00"
71
+ const manifest = buildMinimalManifest(contentRef)
72
+ const extracted = extractReferenceFromManifest(manifest)
73
+
74
+ expect(extracted).toBe(contentRef)
75
+ })
76
+
77
+ it("should work with different references", () => {
78
+ const refs = [
79
+ "0000000000000000000000000000000000000000000000000000000000000001",
80
+ "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
81
+ "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
82
+ ]
83
+
84
+ for (const ref of refs) {
85
+ const manifest = buildMinimalManifest(ref)
86
+ const extracted = extractReferenceFromManifest(manifest)
87
+ expect(extracted).toBe(ref)
88
+ }
89
+ })
90
+ })
91
+
92
+ describe("extractReferenceFromManifest", () => {
93
+ it("should extract reference from round-trip manifest", () => {
94
+ const contentRef =
95
+ "f78f17c6da518054b2ced74fb8fffd53a16b70685e3bb706399ceceaf679bd00"
96
+ const manifest = buildMinimalManifest(contentRef)
97
+
98
+ const extracted = extractReferenceFromManifest(manifest)
99
+ expect(extracted).toBe(contentRef)
100
+ })
101
+
102
+ it("should extract reference from Bee Go test vector", () => {
103
+ // Parse the valid manifest from Bee Go tests
104
+ const manifestBytes = hexToUint8Array(VALID_MANIFEST_HEX)
105
+
106
+ // The "/" fork reference in this test vector is:
107
+ // e87f95c3d081c4fede769b6c69e27b435e525cbd25c6715c607e7c531e329639
108
+ const extracted = extractReferenceFromManifest(manifestBytes)
109
+ expect(extracted).toBe(
110
+ "e87f95c3d081c4fede769b6c69e27b435e525cbd25c6715c607e7c531e329639",
111
+ )
112
+ })
113
+
114
+ it("should throw on manifest too small", () => {
115
+ const smallManifest = new Uint8Array(100)
116
+ expect(() => extractReferenceFromManifest(smallManifest)).toThrow(
117
+ "Manifest too small",
118
+ )
119
+ })
120
+
121
+ it("should throw on manifest without / fork", () => {
122
+ // Create a manifest with no "/" fork (index bitmap all zeros)
123
+ const manifest = buildMinimalManifest(
124
+ "f78f17c6da518054b2ced74fb8fffd53a16b70685e3bb706399ceceaf679bd00",
125
+ )
126
+ // Clear the "/" bit in index bitmap
127
+ manifest[96 + 5] = 0x00
128
+
129
+ expect(() => extractReferenceFromManifest(manifest)).toThrow(
130
+ 'does not contain "/" fork',
131
+ )
132
+ })
133
+
134
+ it("should throw on zero reference", () => {
135
+ // Create a manifest with zero reference
136
+ const manifest = buildMinimalManifest(
137
+ "f78f17c6da518054b2ced74fb8fffd53a16b70685e3bb706399ceceaf679bd00",
138
+ )
139
+ // Zero out the reference
140
+ manifest.fill(0, 160, 192)
141
+
142
+ expect(() => extractReferenceFromManifest(manifest)).toThrow(
143
+ "zero reference",
144
+ )
145
+ })
146
+ })
147
+
148
+ describe("Bee Go compatibility", () => {
149
+ it("should parse manifest with zero obfuscation key", () => {
150
+ // The VALID_MANIFEST_HEX starts with 32 zero bytes (unencrypted)
151
+ const manifestBytes = hexToUint8Array(VALID_MANIFEST_HEX)
152
+ const obfuscationKey = manifestBytes.slice(0, 32)
153
+
154
+ // Verify it's all zeros
155
+ expect(obfuscationKey.every((b) => b === 0)).toBe(true)
156
+ })
157
+
158
+ it("should recognize v0.2 version hash", () => {
159
+ const manifestBytes = hexToUint8Array(VALID_MANIFEST_HEX)
160
+ // Version hash is at bytes 32-63 (31 bytes, XORed with obfuscation key)
161
+ const versionHash = manifestBytes.slice(32, 63)
162
+ // Since obfuscation key is zero, no XOR needed
163
+ expect(uint8ArrayToHex(versionHash)).toBe(VERSION_02_HASH.slice(0, 62))
164
+ })
165
+
166
+ it("should handle Bee Go manifest with metadata", () => {
167
+ // The Bee Go test vector has metadata after the reference
168
+ const manifestBytes = hexToUint8Array(VALID_MANIFEST_HEX)
169
+
170
+ // Fork flags: TYPE_VALUE=0x02, TYPE_EDGE=0x04, TYPE_WITH_METADATA=0x10
171
+ // Test vector 0x16 = TYPE_VALUE(2) + TYPE_EDGE(4) + TYPE_WITH_METADATA(16)
172
+ expect(manifestBytes[128]).toBe(0x16)
173
+
174
+ // Should still extract the reference correctly
175
+ const extracted = extractReferenceFromManifest(manifestBytes)
176
+ expect(extracted).toBe(
177
+ "e87f95c3d081c4fede769b6c69e27b435e525cbd25c6715c607e7c531e329639",
178
+ )
179
+ })
180
+ })
181
+
182
+ describe("binary format verification", () => {
183
+ it("should produce correct binary layout", () => {
184
+ const contentRef =
185
+ "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
186
+ const manifest = buildMinimalManifest(contentRef)
187
+
188
+ // Verify complete structure
189
+ const hex = uint8ArrayToHex(manifest)
190
+
191
+ // Check obfuscation key (32 bytes = 64 hex chars)
192
+ expect(hex.slice(0, 64)).toBe("0".repeat(64))
193
+
194
+ // Check version hash starts at byte 32
195
+ expect(hex.slice(64, 66)).toBe("57") // First byte of version hash
196
+
197
+ // Check index bitmap has "/" bit set at byte 5
198
+ expect(hex.slice(192 + 10, 192 + 12)).toBe("80")
199
+
200
+ // Check fork flags
201
+ expect(hex.slice(256, 258)).toBe("02") // TYPE_VALUE flag (has targetAddress)
202
+
203
+ // Check prefix length
204
+ expect(hex.slice(258, 260)).toBe("01")
205
+
206
+ // Check prefix '/'
207
+ expect(hex.slice(260, 262)).toBe("2f")
208
+
209
+ // Check reference at offset 160 (320 hex chars)
210
+ expect(hex.slice(320, 384)).toBe(contentRef)
211
+ })
212
+ })
213
+
214
+ describe("Bee /bzz/ endpoint compatibility", () => {
215
+ it("should use correct TYPE_VALUE flag matching bee-js", () => {
216
+ // Verify our flag matches bee-js TYPE_VALUE = 2
217
+ const contentRef =
218
+ "f78f17c6da518054b2ced74fb8fffd53a16b70685e3bb706399ceceaf679bd00"
219
+ const manifest = buildMinimalManifest(contentRef)
220
+ const forkFlags = manifest[128]
221
+
222
+ // TYPE_VALUE = 2 should be set
223
+ expect(forkFlags & 0x02).toBe(0x02)
224
+ // TYPE_WITH_METADATA = 16 should NOT be set (no metadata)
225
+ expect(forkFlags & 0x10).toBe(0)
226
+ })
227
+
228
+ it("should not set TYPE_WITH_METADATA flag when no metadata", () => {
229
+ const contentRef =
230
+ "f78f17c6da518054b2ced74fb8fffd53a16b70685e3bb706399ceceaf679bd00"
231
+ const manifest = buildMinimalManifest(contentRef)
232
+
233
+ // Fork flags at offset 128 should be exactly 0x02 (TYPE_VALUE only)
234
+ expect(manifest[128]).toBe(0x02)
235
+
236
+ // Manifest should end at byte 192 (no metadata bytes after reference)
237
+ expect(manifest.length).toBe(192)
238
+ })
239
+
240
+ it("should have valid fork structure for Bee parsing", () => {
241
+ const contentRef =
242
+ "e87f95c3d081c4fede769b6c69e27b435e525cbd25c6715c607e7c531e329639"
243
+ const manifest = buildMinimalManifest(contentRef)
244
+
245
+ // Fork structure: flags(1) + prefixLen(1) + prefix(30) + reference(32)
246
+ const forkOffset = 128
247
+
248
+ // 1. Flags byte: 0x02 (TYPE_VALUE)
249
+ expect(manifest[forkOffset]).toBe(0x02)
250
+
251
+ // 2. Prefix length: 1
252
+ expect(manifest[forkOffset + 1]).toBe(1)
253
+
254
+ // 3. First prefix byte: '/' (0x2f)
255
+ expect(manifest[forkOffset + 2]).toBe(0x2f)
256
+
257
+ // 4. Remaining prefix bytes: zero-padded (29 bytes)
258
+ for (let i = 3; i < 32; i++) {
259
+ expect(manifest[forkOffset + i]).toBe(0)
260
+ }
261
+
262
+ // 5. Reference at offset 160 (forkOffset + 32)
263
+ const refOffset = forkOffset + 32
264
+ const extractedRef = uint8ArrayToHex(
265
+ manifest.slice(refOffset, refOffset + 32),
266
+ )
267
+ expect(extractedRef).toBe(contentRef)
268
+ })
269
+
270
+ it("should correctly parse TYPE_VALUE | TYPE_EDGE | TYPE_WITH_METADATA from Bee", () => {
271
+ // Bee Go test vector has flags = 0x16 = TYPE_VALUE(2) + TYPE_EDGE(4) + TYPE_WITH_METADATA(16)
272
+ const manifestBytes = hexToUint8Array(VALID_MANIFEST_HEX)
273
+
274
+ // Verify correct flag interpretation
275
+ const flags = manifestBytes[128]
276
+ expect(flags).toBe(0x16)
277
+
278
+ // TYPE_VALUE (0x02) should be set
279
+ expect(flags & 0x02).toBe(0x02)
280
+ // TYPE_EDGE (0x04) should be set
281
+ expect(flags & 0x04).toBe(0x04)
282
+ // TYPE_WITH_METADATA (0x10) should be set
283
+ expect(flags & 0x10).toBe(0x10)
284
+
285
+ // Despite having all these flags, extraction should still work
286
+ const extracted = extractReferenceFromManifest(manifestBytes)
287
+ expect(extracted).toBe(
288
+ "e87f95c3d081c4fede769b6c69e27b435e525cbd25c6715c607e7c531e329639",
289
+ )
290
+ })
291
+ })
292
+ })
293
+
294
+ describe("buildBzzCompatibleManifest", () => {
295
+ it("should return single manifest chunk (flat structure)", async () => {
296
+ const contentRef =
297
+ "f78f17c6da518054b2ced74fb8fffd53a16b70685e3bb706399ceceaf679bd00"
298
+ const result = await buildBzzCompatibleManifest(contentRef)
299
+
300
+ expect(result.manifestChunk).toBeDefined()
301
+ expect(result.manifestChunk.data).toBeInstanceOf(Uint8Array)
302
+ expect(result.manifestChunk.address).toHaveLength(64)
303
+ })
304
+
305
+ it("should embed content reference in index.bin fork", async () => {
306
+ const contentRef =
307
+ "f78f17c6da518054b2ced74fb8fffd53a16b70685e3bb706399ceceaf679bd00"
308
+ const result = await buildBzzCompatibleManifest(contentRef)
309
+
310
+ // The manifest should contain the content reference
311
+ const manifestHex = uint8ArrayToHex(result.manifestChunk.data)
312
+ expect(manifestHex).toContain(contentRef)
313
+ })
314
+
315
+ it("should include website-index-document metadata", async () => {
316
+ const contentRef =
317
+ "f78f17c6da518054b2ced74fb8fffd53a16b70685e3bb706399ceceaf679bd00"
318
+ const result = await buildBzzCompatibleManifest(contentRef)
319
+
320
+ const manifestHex = uint8ArrayToHex(result.manifestChunk.data)
321
+ expect(manifestHex).toContain(
322
+ uint8ArrayToHex(new TextEncoder().encode("website-index-document")),
323
+ )
324
+ expect(manifestHex).toContain(
325
+ uint8ArrayToHex(new TextEncoder().encode("index.bin")),
326
+ )
327
+ })
328
+
329
+ it("should work with different content references", async () => {
330
+ const refs = [
331
+ "0000000000000000000000000000000000000000000000000000000000000001",
332
+ "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
333
+ "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
334
+ ]
335
+
336
+ for (const ref of refs) {
337
+ const result = await buildBzzCompatibleManifest(ref)
338
+ expect(result.manifestChunk).toBeDefined()
339
+ // Manifest should contain the content reference
340
+ const manifestHex = uint8ArrayToHex(result.manifestChunk.data)
341
+ expect(manifestHex).toContain(ref)
342
+ }
343
+ })
344
+
345
+ it("should throw on invalid reference length", async () => {
346
+ const invalidRef = "abcd" // Too short
347
+ // bee-js Reference constructor throws its own error for invalid length
348
+ await expect(buildBzzCompatibleManifest(invalidRef)).rejects.toThrow()
349
+ })
350
+
351
+ it("should accept Uint8Array content reference", async () => {
352
+ const contentRefBytes = hexToUint8Array(
353
+ "f78f17c6da518054b2ced74fb8fffd53a16b70685e3bb706399ceceaf679bd00",
354
+ )
355
+ const result = await buildBzzCompatibleManifest(contentRefBytes)
356
+
357
+ expect(result.manifestChunk).toBeDefined()
358
+ })
359
+
360
+ it("should produce deterministic output for same input", async () => {
361
+ const contentRef =
362
+ "f78f17c6da518054b2ced74fb8fffd53a16b70685e3bb706399ceceaf679bd00"
363
+
364
+ const result1 = await buildBzzCompatibleManifest(contentRef)
365
+ const result2 = await buildBzzCompatibleManifest(contentRef)
366
+
367
+ expect(result1.manifestChunk.address).toBe(result2.manifestChunk.address)
368
+ })
369
+
370
+ it("should round-trip with extractContentFromFlatManifest", async () => {
371
+ const contentRef =
372
+ "f78f17c6da518054b2ced74fb8fffd53a16b70685e3bb706399ceceaf679bd00"
373
+ const result = await buildBzzCompatibleManifest(contentRef)
374
+
375
+ // Extract content reference from flat manifest
376
+ const extracted = extractContentFromFlatManifest(result.manifestChunk.data)
377
+ expect(extracted).toBe(contentRef)
378
+ })
379
+ })
380
+
381
+ describe("extractContentFromFlatManifest", () => {
382
+ it("should extract content reference from flat manifest", async () => {
383
+ const contentRef =
384
+ "f78f17c6da518054b2ced74fb8fffd53a16b70685e3bb706399ceceaf679bd00"
385
+ const result = await buildBzzCompatibleManifest(contentRef)
386
+
387
+ const extracted = extractContentFromFlatManifest(result.manifestChunk.data)
388
+ expect(extracted).toBe(contentRef)
389
+ })
390
+
391
+ it("should work with different references", async () => {
392
+ const refs = [
393
+ "0000000000000000000000000000000000000000000000000000000000000001",
394
+ "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
395
+ "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
396
+ ]
397
+
398
+ for (const ref of refs) {
399
+ const result = await buildBzzCompatibleManifest(ref)
400
+ const extracted = extractContentFromFlatManifest(
401
+ result.manifestChunk.data,
402
+ )
403
+ expect(extracted).toBe(ref)
404
+ }
405
+ })
406
+
407
+ it("should throw on manifest too small", () => {
408
+ const smallManifest = new Uint8Array(100)
409
+ expect(() => extractContentFromFlatManifest(smallManifest)).toThrow(
410
+ "Manifest too small",
411
+ )
412
+ })
413
+ })
414
+
415
+ function hexToUint8Array(hex: string): Uint8Array {
416
+ const bytes = new Uint8Array(hex.length / 2)
417
+ for (let i = 0; i < hex.length; i += 2) {
418
+ bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16)
419
+ }
420
+ return bytes
421
+ }
422
+
423
+ function uint8ArrayToHex(bytes: Uint8Array): string {
424
+ return Array.from(bytes)
425
+ .map((b) => b.toString(16).padStart(2, "0"))
426
+ .join("")
427
+ }