@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.
- package/README.md +431 -0
- package/dist/chunk/bmt.d.ts +17 -0
- package/dist/chunk/bmt.d.ts.map +1 -0
- package/dist/chunk/cac.d.ts +18 -0
- package/dist/chunk/cac.d.ts.map +1 -0
- package/dist/chunk/constants.d.ts +10 -0
- package/dist/chunk/constants.d.ts.map +1 -0
- package/dist/chunk/encrypted-cac.d.ts +48 -0
- package/dist/chunk/encrypted-cac.d.ts.map +1 -0
- package/dist/chunk/encryption.d.ts +86 -0
- package/dist/chunk/encryption.d.ts.map +1 -0
- package/dist/chunk/index.d.ts +6 -0
- package/dist/chunk/index.d.ts.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/proxy/act/act.d.ts +78 -0
- package/dist/proxy/act/act.d.ts.map +1 -0
- package/dist/proxy/act/crypto.d.ts +44 -0
- package/dist/proxy/act/crypto.d.ts.map +1 -0
- package/dist/proxy/act/grantee-list.d.ts +82 -0
- package/dist/proxy/act/grantee-list.d.ts.map +1 -0
- package/dist/proxy/act/history.d.ts +183 -0
- package/dist/proxy/act/history.d.ts.map +1 -0
- package/dist/proxy/act/index.d.ts +104 -0
- package/dist/proxy/act/index.d.ts.map +1 -0
- package/dist/proxy/chunking-encrypted.d.ts +14 -0
- package/dist/proxy/chunking-encrypted.d.ts.map +1 -0
- package/dist/proxy/chunking.d.ts +15 -0
- package/dist/proxy/chunking.d.ts.map +1 -0
- package/dist/proxy/download-data.d.ts +16 -0
- package/dist/proxy/download-data.d.ts.map +1 -0
- package/dist/proxy/feed-manifest.d.ts +62 -0
- package/dist/proxy/feed-manifest.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/async-finder.d.ts +77 -0
- package/dist/proxy/feeds/epochs/async-finder.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/epoch.d.ts +88 -0
- package/dist/proxy/feeds/epochs/epoch.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/finder.d.ts +67 -0
- package/dist/proxy/feeds/epochs/finder.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/index.d.ts +35 -0
- package/dist/proxy/feeds/epochs/index.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/test-utils.d.ts +93 -0
- package/dist/proxy/feeds/epochs/test-utils.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/types.d.ts +109 -0
- package/dist/proxy/feeds/epochs/types.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/updater.d.ts +68 -0
- package/dist/proxy/feeds/epochs/updater.d.ts.map +1 -0
- package/dist/proxy/feeds/epochs/utils.d.ts +22 -0
- package/dist/proxy/feeds/epochs/utils.d.ts.map +1 -0
- package/dist/proxy/feeds/index.d.ts +5 -0
- package/dist/proxy/feeds/index.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/async-finder.d.ts +14 -0
- package/dist/proxy/feeds/sequence/async-finder.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/finder.d.ts +17 -0
- package/dist/proxy/feeds/sequence/finder.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/index.d.ts +23 -0
- package/dist/proxy/feeds/sequence/index.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/types.d.ts +80 -0
- package/dist/proxy/feeds/sequence/types.d.ts.map +1 -0
- package/dist/proxy/feeds/sequence/updater.d.ts +26 -0
- package/dist/proxy/feeds/sequence/updater.d.ts.map +1 -0
- package/dist/proxy/index.d.ts +6 -0
- package/dist/proxy/index.d.ts.map +1 -0
- package/dist/proxy/manifest-builder.d.ts +183 -0
- package/dist/proxy/manifest-builder.d.ts.map +1 -0
- package/dist/proxy/mantaray-encrypted.d.ts +27 -0
- package/dist/proxy/mantaray-encrypted.d.ts.map +1 -0
- package/dist/proxy/mantaray.d.ts +26 -0
- package/dist/proxy/mantaray.d.ts.map +1 -0
- package/dist/proxy/types.d.ts +29 -0
- package/dist/proxy/types.d.ts.map +1 -0
- package/dist/proxy/upload-data.d.ts +17 -0
- package/dist/proxy/upload-data.d.ts.map +1 -0
- package/dist/proxy/upload-encrypted-data.d.ts +103 -0
- package/dist/proxy/upload-encrypted-data.d.ts.map +1 -0
- package/dist/schemas.d.ts +240 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/storage/debounced-uploader.d.ts +62 -0
- package/dist/storage/debounced-uploader.d.ts.map +1 -0
- package/dist/storage/utilization-store.d.ts +108 -0
- package/dist/storage/utilization-store.d.ts.map +1 -0
- package/dist/swarm-id-auth.d.ts +74 -0
- package/dist/swarm-id-auth.d.ts.map +1 -0
- package/dist/swarm-id-auth.js +2 -0
- package/dist/swarm-id-auth.js.map +1 -0
- package/dist/swarm-id-client.d.ts +878 -0
- package/dist/swarm-id-client.d.ts.map +1 -0
- package/dist/swarm-id-client.js +2 -0
- package/dist/swarm-id-client.js.map +1 -0
- package/dist/swarm-id-proxy.d.ts +236 -0
- package/dist/swarm-id-proxy.d.ts.map +1 -0
- package/dist/swarm-id-proxy.js +2 -0
- package/dist/swarm-id-proxy.js.map +1 -0
- package/dist/swarm-id.esm.js +2 -0
- package/dist/swarm-id.esm.js.map +1 -0
- package/dist/swarm-id.umd.js +2 -0
- package/dist/swarm-id.umd.js.map +1 -0
- package/dist/sync/index.d.ts +9 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/key-derivation.d.ts +25 -0
- package/dist/sync/key-derivation.d.ts.map +1 -0
- package/dist/sync/restore-account.d.ts +28 -0
- package/dist/sync/restore-account.d.ts.map +1 -0
- package/dist/sync/serialization.d.ts +16 -0
- package/dist/sync/serialization.d.ts.map +1 -0
- package/dist/sync/store-interfaces.d.ts +53 -0
- package/dist/sync/store-interfaces.d.ts.map +1 -0
- package/dist/sync/sync-account.d.ts +44 -0
- package/dist/sync/sync-account.d.ts.map +1 -0
- package/dist/sync/types.d.ts +13 -0
- package/dist/sync/types.d.ts.map +1 -0
- package/dist/test-fixtures.d.ts +17 -0
- package/dist/test-fixtures.d.ts.map +1 -0
- package/dist/types-BD_VkNn0.js +2 -0
- package/dist/types-BD_VkNn0.js.map +1 -0
- package/dist/types-lJCaT-50.js +2 -0
- package/dist/types-lJCaT-50.js.map +1 -0
- package/dist/types.d.ts +2157 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/account-payload.d.ts +94 -0
- package/dist/utils/account-payload.d.ts.map +1 -0
- package/dist/utils/account-state-snapshot.d.ts +38 -0
- package/dist/utils/account-state-snapshot.d.ts.map +1 -0
- package/dist/utils/backup-encryption.d.ts +127 -0
- package/dist/utils/backup-encryption.d.ts.map +1 -0
- package/dist/utils/batch-utilization.d.ts +432 -0
- package/dist/utils/batch-utilization.d.ts.map +1 -0
- package/dist/utils/constants.d.ts +11 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/hex.d.ts +17 -0
- package/dist/utils/hex.d.ts.map +1 -0
- package/dist/utils/key-derivation.d.ts +92 -0
- package/dist/utils/key-derivation.d.ts.map +1 -0
- package/dist/utils/storage-managers.d.ts +65 -0
- package/dist/utils/storage-managers.d.ts.map +1 -0
- package/dist/utils/swarm-id-export.d.ts +24 -0
- package/dist/utils/swarm-id-export.d.ts.map +1 -0
- package/dist/utils/ttl.d.ts +49 -0
- package/dist/utils/ttl.d.ts.map +1 -0
- package/dist/utils/url.d.ts +41 -0
- package/dist/utils/url.d.ts.map +1 -0
- package/dist/utils/versioned-storage.d.ts +131 -0
- package/dist/utils/versioned-storage.d.ts.map +1 -0
- package/package.json +78 -0
- package/src/chunk/bmt.test.ts +217 -0
- package/src/chunk/bmt.ts +57 -0
- package/src/chunk/cac.test.ts +214 -0
- package/src/chunk/cac.ts +65 -0
- package/src/chunk/constants.ts +18 -0
- package/src/chunk/encrypted-cac.test.ts +385 -0
- package/src/chunk/encrypted-cac.ts +131 -0
- package/src/chunk/encryption.test.ts +352 -0
- package/src/chunk/encryption.ts +300 -0
- package/src/chunk/index.ts +47 -0
- package/src/index.ts +430 -0
- package/src/proxy/act/act.test.ts +278 -0
- package/src/proxy/act/act.ts +158 -0
- package/src/proxy/act/bee-compat.test.ts +948 -0
- package/src/proxy/act/crypto.test.ts +436 -0
- package/src/proxy/act/crypto.ts +376 -0
- package/src/proxy/act/grantee-list.test.ts +393 -0
- package/src/proxy/act/grantee-list.ts +239 -0
- package/src/proxy/act/history.test.ts +360 -0
- package/src/proxy/act/history.ts +413 -0
- package/src/proxy/act/index.test.ts +748 -0
- package/src/proxy/act/index.ts +853 -0
- package/src/proxy/chunking-encrypted.ts +95 -0
- package/src/proxy/chunking.ts +65 -0
- package/src/proxy/download-data.ts +448 -0
- package/src/proxy/feed-manifest.ts +174 -0
- package/src/proxy/feeds/epochs/async-finder.ts +372 -0
- package/src/proxy/feeds/epochs/epoch.test.ts +249 -0
- package/src/proxy/feeds/epochs/epoch.ts +181 -0
- package/src/proxy/feeds/epochs/finder.ts +282 -0
- package/src/proxy/feeds/epochs/index.ts +73 -0
- package/src/proxy/feeds/epochs/integration.test.ts +1336 -0
- package/src/proxy/feeds/epochs/test-utils.ts +274 -0
- package/src/proxy/feeds/epochs/types.ts +128 -0
- package/src/proxy/feeds/epochs/updater.ts +192 -0
- package/src/proxy/feeds/epochs/utils.ts +62 -0
- package/src/proxy/feeds/index.ts +5 -0
- package/src/proxy/feeds/sequence/async-finder.ts +31 -0
- package/src/proxy/feeds/sequence/finder.ts +73 -0
- package/src/proxy/feeds/sequence/index.ts +54 -0
- package/src/proxy/feeds/sequence/integration.test.ts +966 -0
- package/src/proxy/feeds/sequence/types.ts +103 -0
- package/src/proxy/feeds/sequence/updater.ts +71 -0
- package/src/proxy/index.ts +5 -0
- package/src/proxy/manifest-builder.test.ts +427 -0
- package/src/proxy/manifest-builder.ts +679 -0
- package/src/proxy/mantaray-encrypted.ts +78 -0
- package/src/proxy/mantaray.ts +104 -0
- package/src/proxy/types.ts +32 -0
- package/src/proxy/upload-data.ts +189 -0
- package/src/proxy/upload-encrypted-data.ts +658 -0
- package/src/schemas.ts +299 -0
- package/src/storage/debounced-uploader.ts +192 -0
- package/src/storage/utilization-store.ts +397 -0
- package/src/swarm-id-client.test.ts +99 -0
- package/src/swarm-id-client.ts +3095 -0
- package/src/swarm-id-proxy.ts +3891 -0
- package/src/sync/index.ts +28 -0
- package/src/sync/restore-account.ts +90 -0
- package/src/sync/serialization.ts +39 -0
- package/src/sync/store-interfaces.ts +62 -0
- package/src/sync/sync-account.test.ts +302 -0
- package/src/sync/sync-account.ts +396 -0
- package/src/sync/types.ts +11 -0
- package/src/test-fixtures.ts +109 -0
- package/src/types.ts +1651 -0
- package/src/utils/account-state-snapshot.test.ts +595 -0
- package/src/utils/account-state-snapshot.ts +94 -0
- package/src/utils/backup-encryption.test.ts +442 -0
- package/src/utils/backup-encryption.ts +352 -0
- package/src/utils/batch-utilization.ts +1309 -0
- package/src/utils/constants.ts +20 -0
- package/src/utils/hex.ts +27 -0
- package/src/utils/key-derivation.ts +197 -0
- package/src/utils/storage-managers.ts +365 -0
- package/src/utils/ttl.ts +129 -0
- package/src/utils/url.test.ts +136 -0
- package/src/utils/url.ts +71 -0
- 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,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
|
+
}
|