@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,658 @@
|
|
|
1
|
+
import { Reference, PrivateKey, Identifier } from "@ethersphere/bee-js"
|
|
2
|
+
import type {
|
|
3
|
+
Bee,
|
|
4
|
+
BeeRequestOptions,
|
|
5
|
+
Stamper,
|
|
6
|
+
UploadOptions,
|
|
7
|
+
EnvelopeWithBatchId,
|
|
8
|
+
} from "@ethersphere/bee-js"
|
|
9
|
+
import {
|
|
10
|
+
makeContentAddressedChunk,
|
|
11
|
+
makeEncryptedContentAddressedChunk,
|
|
12
|
+
calculateChunkAddress,
|
|
13
|
+
type EncryptedChunk,
|
|
14
|
+
} from "../chunk"
|
|
15
|
+
import { Binary } from "cafe-utility"
|
|
16
|
+
import { splitDataIntoChunks } from "./chunking"
|
|
17
|
+
import { buildEncryptedMerkleTree } from "./chunking-encrypted"
|
|
18
|
+
import type { UploadContext, UploadProgress } from "./types"
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Result of uploading encrypted data
|
|
22
|
+
*/
|
|
23
|
+
export interface UploadEncryptedDataResult {
|
|
24
|
+
reference: string
|
|
25
|
+
tagUid?: number
|
|
26
|
+
chunkAddresses: Uint8Array[] // Addresses of all uploaded chunks
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Simple Uint8ArrayWriter implementation for ChunkAdapter
|
|
31
|
+
*/
|
|
32
|
+
class SimpleUint8ArrayWriter {
|
|
33
|
+
cursor: number = 0
|
|
34
|
+
buffer: Uint8Array
|
|
35
|
+
|
|
36
|
+
constructor(buffer: Uint8Array) {
|
|
37
|
+
this.buffer = buffer
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
write(_reader: unknown): number {
|
|
41
|
+
throw new Error("SimpleUint8ArrayWriter.write() not implemented")
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
max(): number {
|
|
45
|
+
return this.buffer.length
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Adapter to convert EncryptedChunk to cafe-utility Chunk interface
|
|
51
|
+
* This allows the Stamper to work with encrypted chunks
|
|
52
|
+
*/
|
|
53
|
+
class EncryptedChunkAdapter {
|
|
54
|
+
span: bigint
|
|
55
|
+
writer: SimpleUint8ArrayWriter
|
|
56
|
+
|
|
57
|
+
constructor(private encryptedChunk: EncryptedChunk) {
|
|
58
|
+
this.span = encryptedChunk.span.toBigInt()
|
|
59
|
+
this.writer = new SimpleUint8ArrayWriter(encryptedChunk.data)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
hash(): Uint8Array {
|
|
63
|
+
return this.encryptedChunk.address.toUint8Array()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
build(): Uint8Array {
|
|
67
|
+
return this.encryptedChunk.data
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Upload encrypted data with client-side signing
|
|
73
|
+
* Handles chunking, encryption, merkle tree building, and progress reporting
|
|
74
|
+
*
|
|
75
|
+
* @param context - Upload context with bee instance and authentication
|
|
76
|
+
* @param data - Data to encrypt and upload
|
|
77
|
+
* @param encryptionKey - Optional 32-byte encryption key (generates random if not provided)
|
|
78
|
+
* @param options - Upload options
|
|
79
|
+
* @param onProgress - Progress callback
|
|
80
|
+
*/
|
|
81
|
+
export async function uploadEncryptedDataWithSigning(
|
|
82
|
+
context: UploadContext,
|
|
83
|
+
data: Uint8Array,
|
|
84
|
+
encryptionKey?: Uint8Array,
|
|
85
|
+
options?: UploadOptions,
|
|
86
|
+
onProgress?: (progress: UploadProgress) => void,
|
|
87
|
+
requestOptions?: BeeRequestOptions,
|
|
88
|
+
): Promise<UploadEncryptedDataResult> {
|
|
89
|
+
const { bee, stamper } = context
|
|
90
|
+
|
|
91
|
+
// Validate authentication method
|
|
92
|
+
if (!stamper) {
|
|
93
|
+
throw new Error("No authentication method available")
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Create a tag for tracking upload progress (required for fast deferred uploads)
|
|
97
|
+
// IMPORTANT: Tag is REQUIRED in dev mode - Bee's /chunks endpoint uses tag presence
|
|
98
|
+
// to determine deferred mode (deferred = tag != 0), and dev mode blocks non-deferred uploads
|
|
99
|
+
let tag: number | undefined = options?.tag
|
|
100
|
+
if (!tag) {
|
|
101
|
+
const tagResponse = await bee.createTag()
|
|
102
|
+
tag = tagResponse.uid
|
|
103
|
+
} else {
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Step 1: Split data into chunks
|
|
107
|
+
const chunkPayloads = splitDataIntoChunks(data)
|
|
108
|
+
let totalChunks = chunkPayloads.length
|
|
109
|
+
let processedChunks = 0
|
|
110
|
+
|
|
111
|
+
// Progress callback helper
|
|
112
|
+
const reportProgress = () => {
|
|
113
|
+
if (onProgress) {
|
|
114
|
+
onProgress({ total: totalChunks, processed: processedChunks })
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Step 2: Process and encrypt leaf chunks
|
|
119
|
+
const encryptedChunkRefs: Array<{
|
|
120
|
+
address: Uint8Array
|
|
121
|
+
key: Uint8Array
|
|
122
|
+
span: bigint
|
|
123
|
+
}> = []
|
|
124
|
+
|
|
125
|
+
// Track all uploaded chunk addresses for utilization
|
|
126
|
+
const uploadedChunkAddresses: Uint8Array[] = []
|
|
127
|
+
|
|
128
|
+
// Merge tag into options for all chunk uploads
|
|
129
|
+
const uploadOptionsWithTag = { ...options, tag }
|
|
130
|
+
|
|
131
|
+
for (const payload of chunkPayloads) {
|
|
132
|
+
// Create and encrypt content-addressed chunk
|
|
133
|
+
const encryptedChunk = makeEncryptedContentAddressedChunk(
|
|
134
|
+
payload,
|
|
135
|
+
encryptionKey,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
// Store reference with span (payload size for leaf chunks)
|
|
139
|
+
encryptedChunkRefs.push({
|
|
140
|
+
address: encryptedChunk.address.toUint8Array(),
|
|
141
|
+
key: encryptedChunk.encryptionKey,
|
|
142
|
+
span: BigInt(payload.length),
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
// DEBUG: Trace encryption key storage
|
|
146
|
+
|
|
147
|
+
// Upload chunk with signing
|
|
148
|
+
await uploadSingleEncryptedChunk(
|
|
149
|
+
bee,
|
|
150
|
+
stamper,
|
|
151
|
+
encryptedChunk,
|
|
152
|
+
uploadOptionsWithTag,
|
|
153
|
+
requestOptions,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
// Track uploaded chunk address for utilization
|
|
157
|
+
uploadedChunkAddresses.push(encryptedChunk.address.toUint8Array())
|
|
158
|
+
|
|
159
|
+
processedChunks++
|
|
160
|
+
reportProgress()
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Step 3: Build encrypted merkle tree (if multiple chunks)
|
|
164
|
+
let rootReference: Reference
|
|
165
|
+
|
|
166
|
+
if (encryptedChunkRefs.length === 1) {
|
|
167
|
+
// Single chunk - use direct reference (64 bytes)
|
|
168
|
+
const ref = new Uint8Array(64)
|
|
169
|
+
ref.set(encryptedChunkRefs[0].address, 0)
|
|
170
|
+
ref.set(encryptedChunkRefs[0].key, 32)
|
|
171
|
+
|
|
172
|
+
// DEBUG: Trace 64-byte reference construction
|
|
173
|
+
|
|
174
|
+
rootReference = new Reference(ref)
|
|
175
|
+
} else {
|
|
176
|
+
// Multiple chunks - build encrypted tree using bee-js's implementation
|
|
177
|
+
|
|
178
|
+
rootReference = await buildEncryptedMerkleTree(
|
|
179
|
+
encryptedChunkRefs,
|
|
180
|
+
async (encryptedChunkData) => {
|
|
181
|
+
// Upload the already-encrypted intermediate chunk
|
|
182
|
+
// encryptedChunkData = encryptedSpan (8 bytes) + encryptedPayload (4096 bytes) = 4104 bytes
|
|
183
|
+
// We need to upload this without any modification
|
|
184
|
+
|
|
185
|
+
// Calculate address for this intermediate chunk (needed for both stamper and utilization tracking)
|
|
186
|
+
const address = calculateChunkAddress(encryptedChunkData)
|
|
187
|
+
|
|
188
|
+
// Track this intermediate chunk address for utilization
|
|
189
|
+
uploadedChunkAddresses.push(address.toUint8Array())
|
|
190
|
+
|
|
191
|
+
// For client-side signing, use the calculated address
|
|
192
|
+
|
|
193
|
+
const envelope = stamper.stamp({
|
|
194
|
+
hash: () => address.toUint8Array(),
|
|
195
|
+
build: () => encryptedChunkData,
|
|
196
|
+
span: 0n, // not used by stamper.stamp
|
|
197
|
+
writer: undefined as any, // not used by stamper.stamp
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
await bee.uploadChunk(
|
|
201
|
+
envelope,
|
|
202
|
+
encryptedChunkData,
|
|
203
|
+
uploadOptionsWithTag,
|
|
204
|
+
requestOptions,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
// Count intermediate chunks in progress
|
|
208
|
+
totalChunks++
|
|
209
|
+
processedChunks++
|
|
210
|
+
reportProgress()
|
|
211
|
+
},
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Return result with 64-byte reference (128 hex chars)
|
|
216
|
+
return {
|
|
217
|
+
reference: rootReference.toHex(),
|
|
218
|
+
tagUid: tag,
|
|
219
|
+
chunkAddresses: uploadedChunkAddresses,
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Upload a single encrypted chunk with optional signing
|
|
225
|
+
*/
|
|
226
|
+
export async function uploadSingleEncryptedChunk(
|
|
227
|
+
bee: Bee,
|
|
228
|
+
stamper: Stamper,
|
|
229
|
+
encryptedChunk: EncryptedChunk,
|
|
230
|
+
options?: UploadOptions,
|
|
231
|
+
requestOptions?: BeeRequestOptions,
|
|
232
|
+
): Promise<void> {
|
|
233
|
+
// Client-side signing - use adapter for cafe-utility Chunk interface
|
|
234
|
+
const chunkAdapter = new EncryptedChunkAdapter(encryptedChunk)
|
|
235
|
+
const envelope = stamper.stamp(chunkAdapter)
|
|
236
|
+
await uploadSingleChunkWithEnvelope(
|
|
237
|
+
bee,
|
|
238
|
+
envelope,
|
|
239
|
+
encryptedChunk.data,
|
|
240
|
+
options,
|
|
241
|
+
requestOptions,
|
|
242
|
+
)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Upload a single encrypted chunk with optional signing
|
|
247
|
+
*/
|
|
248
|
+
async function uploadSingleChunkWithEnvelope(
|
|
249
|
+
bee: Bee,
|
|
250
|
+
envelope: EnvelopeWithBatchId,
|
|
251
|
+
data: Uint8Array,
|
|
252
|
+
options?: UploadOptions,
|
|
253
|
+
requestOptions?: BeeRequestOptions,
|
|
254
|
+
): Promise<void> {
|
|
255
|
+
// Use non-deferred mode for faster uploads (returns immediately)
|
|
256
|
+
// Note: pinning is incompatible with deferred mode, so disable it
|
|
257
|
+
const uploadOptions = { deferred: false, pin: false, ...options }
|
|
258
|
+
|
|
259
|
+
await bee.uploadChunk(envelope, data, uploadOptions, requestOptions)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Upload a single encrypted chunk with optional signing
|
|
264
|
+
*
|
|
265
|
+
* This is the unified interface for uploading encrypted chunks.
|
|
266
|
+
* Use this instead of direct Bee API calls with fetch.
|
|
267
|
+
*
|
|
268
|
+
* @param bee - Bee client instance
|
|
269
|
+
* @param stamper - Stamper for client-side signing
|
|
270
|
+
* @param payload - Raw chunk data to encrypt and upload (1-4096 bytes)
|
|
271
|
+
* @param encryptionKey - Encryption key (32 bytes)
|
|
272
|
+
* @param options - Upload options (deferred, tag, etc.)
|
|
273
|
+
*/
|
|
274
|
+
export async function uploadSingleChunkWithEncryption(
|
|
275
|
+
bee: Bee,
|
|
276
|
+
stamper: Stamper,
|
|
277
|
+
payload: Uint8Array,
|
|
278
|
+
encryptionKey: Uint8Array,
|
|
279
|
+
options?: UploadOptions,
|
|
280
|
+
): Promise<void> {
|
|
281
|
+
// Validate payload size (1-4096 bytes, encryption handles padding)
|
|
282
|
+
if (payload.length < 1 || payload.length > 4096) {
|
|
283
|
+
throw new Error(
|
|
284
|
+
`Invalid payload length: ${payload.length} (expected 1-4096)`,
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Validate encryption key
|
|
289
|
+
if (encryptionKey.length !== 32) {
|
|
290
|
+
throw new Error(
|
|
291
|
+
`Invalid encryption key length: ${encryptionKey.length} (expected 32)`,
|
|
292
|
+
)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Create encrypted content-addressed chunk
|
|
296
|
+
const encryptedChunk = makeEncryptedContentAddressedChunk(
|
|
297
|
+
payload,
|
|
298
|
+
encryptionKey,
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
// Upload using the existing function that handles both stamper and batch cases
|
|
302
|
+
await uploadSingleEncryptedChunk(bee, stamper, encryptedChunk, options)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Result of uploading an encrypted SOC
|
|
307
|
+
*/
|
|
308
|
+
export interface UploadEncryptedSOCResult {
|
|
309
|
+
socAddress: Uint8Array
|
|
310
|
+
encryptionKey: Uint8Array
|
|
311
|
+
tagUid?: number
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Result of uploading a SOC
|
|
316
|
+
*/
|
|
317
|
+
export interface UploadSOCResult {
|
|
318
|
+
socAddress: Uint8Array
|
|
319
|
+
tagUid?: number
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Upload chunk via direct fetch to /chunks endpoint
|
|
324
|
+
*
|
|
325
|
+
* This is a temporary workaround for bee.uploadChunk's 4104-byte size limit.
|
|
326
|
+
* Can be replaced with bee.uploadChunk when it supports SOC chunks (4201 bytes).
|
|
327
|
+
*
|
|
328
|
+
* @param bee - Bee client instance
|
|
329
|
+
* @param envelope - Envelope with postage stamp signature
|
|
330
|
+
* @param chunkData - Full chunk data (can be > 4104 bytes for SOC)
|
|
331
|
+
* @param options - Upload options
|
|
332
|
+
* @returns Reference to the uploaded chunk
|
|
333
|
+
*/
|
|
334
|
+
async function uploadChunkWithFetch(
|
|
335
|
+
bee: Bee,
|
|
336
|
+
envelope: EnvelopeWithBatchId,
|
|
337
|
+
chunkData: Uint8Array,
|
|
338
|
+
options?: UploadOptions,
|
|
339
|
+
): Promise<Reference> {
|
|
340
|
+
// Marshal the envelope to hex for the HTTP header
|
|
341
|
+
// Order: batchId (32) + index (8) + timestamp (8) + signature (65) = 113 bytes
|
|
342
|
+
const marshaledStamp = Binary.concatBytes(
|
|
343
|
+
envelope.batchId.toUint8Array(),
|
|
344
|
+
envelope.index,
|
|
345
|
+
envelope.timestamp,
|
|
346
|
+
envelope.signature,
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
// Convert to hex string (226 chars) - using Binary.uint8ArrayToHex for browser compatibility
|
|
350
|
+
const stampHex = Binary.uint8ArrayToHex(marshaledStamp)
|
|
351
|
+
|
|
352
|
+
// Prepare HTTP headers
|
|
353
|
+
const headers: Record<string, string> = {
|
|
354
|
+
"content-type": "application/octet-stream",
|
|
355
|
+
"swarm-postage-stamp": stampHex,
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Add optional headers
|
|
359
|
+
if (options?.tag) headers["swarm-tag"] = options.tag.toString()
|
|
360
|
+
if (options?.deferred !== undefined) {
|
|
361
|
+
headers["swarm-deferred-upload"] = options.deferred.toString()
|
|
362
|
+
}
|
|
363
|
+
if (options?.pin !== undefined) {
|
|
364
|
+
headers["swarm-pin"] = options.pin.toString()
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Make direct fetch call to /chunks endpoint
|
|
368
|
+
const response = await fetch(`${bee.url}/chunks`, {
|
|
369
|
+
method: "POST",
|
|
370
|
+
headers,
|
|
371
|
+
body: chunkData,
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
if (!response.ok) {
|
|
375
|
+
const errorText = await response.text()
|
|
376
|
+
throw new Error(
|
|
377
|
+
`Chunk upload failed: ${response.status} ${response.statusText} - ${errorText}`,
|
|
378
|
+
)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const result = await response.json()
|
|
382
|
+
|
|
383
|
+
return new Reference(result.reference)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Calculate SOC address from identifier and owner
|
|
388
|
+
*/
|
|
389
|
+
function makeSOCAddress(
|
|
390
|
+
identifier: Identifier,
|
|
391
|
+
ownerAddress: Uint8Array,
|
|
392
|
+
): Reference {
|
|
393
|
+
return new Reference(
|
|
394
|
+
Binary.keccak256(
|
|
395
|
+
Binary.concatBytes(identifier.toUint8Array(), ownerAddress),
|
|
396
|
+
),
|
|
397
|
+
)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Upload an encrypted Single Owner Chunk (SOC) using the fast chunk upload path
|
|
402
|
+
*
|
|
403
|
+
* This function constructs an encrypted SOC manually and uploads it via the regular
|
|
404
|
+
* /chunks endpoint for better performance compared to the /soc endpoint.
|
|
405
|
+
*
|
|
406
|
+
* SOC Structure (Book of Swarm 2.2.3, 2.2.4):
|
|
407
|
+
* - 32 bytes: identifier
|
|
408
|
+
* - 65 bytes: signature (r, s, v)
|
|
409
|
+
* - 8 bytes: span (from encrypted CAC)
|
|
410
|
+
* - up to 4096 bytes: encrypted payload (from encrypted CAC)
|
|
411
|
+
*
|
|
412
|
+
* The signature signs: hash(identifier + encrypted_CAC.address)
|
|
413
|
+
* SOC address: Keccak256(identifier + owner_address)
|
|
414
|
+
*
|
|
415
|
+
* @param bee - Bee client instance
|
|
416
|
+
* @param stamper - Stamper for client-side signing
|
|
417
|
+
* @param signer - SOC owner's private key
|
|
418
|
+
* @param identifier - 32-byte SOC identifier
|
|
419
|
+
* @param data - Payload data (1-4096 bytes)
|
|
420
|
+
* @param encryptionKey - Optional 32-byte encryption key (random if not provided)
|
|
421
|
+
* @param options - Upload options (tag, deferred, etc.)
|
|
422
|
+
* @returns SOC address, encryption key, and optional tag UID
|
|
423
|
+
*/
|
|
424
|
+
export async function uploadEncryptedSOC(
|
|
425
|
+
bee: Bee,
|
|
426
|
+
stamper: Stamper,
|
|
427
|
+
signer: PrivateKey,
|
|
428
|
+
identifier: Identifier,
|
|
429
|
+
data: Uint8Array,
|
|
430
|
+
encryptionKey?: Uint8Array,
|
|
431
|
+
options?: UploadOptions,
|
|
432
|
+
): Promise<UploadEncryptedSOCResult> {
|
|
433
|
+
// Validate data size (1-4096 bytes)
|
|
434
|
+
if (data.length < 1 || data.length > 4096) {
|
|
435
|
+
throw new Error(`Invalid data length: ${data.length} (expected 1-4096)`)
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Step 1: Create encrypted CAC chunk
|
|
439
|
+
const encryptedChunk = makeEncryptedContentAddressedChunk(data, encryptionKey)
|
|
440
|
+
|
|
441
|
+
// Step 2: Construct SOC structure
|
|
442
|
+
const owner = signer.publicKey().address()
|
|
443
|
+
|
|
444
|
+
// Sign: hash(identifier + encrypted_CAC.address)
|
|
445
|
+
const toSign = Binary.concatBytes(
|
|
446
|
+
identifier.toUint8Array(),
|
|
447
|
+
encryptedChunk.address.toUint8Array(),
|
|
448
|
+
)
|
|
449
|
+
const signature = signer.sign(toSign)
|
|
450
|
+
|
|
451
|
+
// Build SOC data: identifier (32) + signature (65) + encrypted_CAC.data
|
|
452
|
+
const socData = Binary.concatBytes(
|
|
453
|
+
identifier.toUint8Array(),
|
|
454
|
+
signature.toUint8Array(),
|
|
455
|
+
encryptedChunk.data,
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
// Calculate SOC address: Keccak256(identifier + owner_address)
|
|
459
|
+
const socAddress = makeSOCAddress(identifier, owner.toUint8Array())
|
|
460
|
+
|
|
461
|
+
// Step 3: Create tag for tracking (if not provided in options)
|
|
462
|
+
// IMPORTANT: Tag is REQUIRED in dev mode - Bee's /chunks endpoint uses tag presence
|
|
463
|
+
// to determine deferred mode (deferred = tag != 0), and dev mode blocks non-deferred uploads
|
|
464
|
+
let tag: number | undefined = options?.tag
|
|
465
|
+
if (!tag) {
|
|
466
|
+
const tagResponse = await bee.createTag()
|
|
467
|
+
tag = tagResponse.uid
|
|
468
|
+
} else {
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Step 4: Create envelope with stamper
|
|
472
|
+
const envelope = stamper.stamp({
|
|
473
|
+
hash: () => socAddress.toUint8Array(),
|
|
474
|
+
build: () => socData,
|
|
475
|
+
span: 0n, // not used by stamper.stamp
|
|
476
|
+
writer: undefined as any, // not used by stamper.stamp
|
|
477
|
+
})
|
|
478
|
+
|
|
479
|
+
// Step 5: Upload using direct fetch (bypasses bee.uploadChunk size check)
|
|
480
|
+
const uploadOptionsWithTag = { tag, deferred: false, pin: false, ...options }
|
|
481
|
+
await uploadChunkWithFetch(bee, envelope, socData, uploadOptionsWithTag)
|
|
482
|
+
|
|
483
|
+
return {
|
|
484
|
+
socAddress: socAddress.toUint8Array(),
|
|
485
|
+
encryptionKey: encryptedChunk.encryptionKey,
|
|
486
|
+
tagUid: tag,
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Upload a plain Single Owner Chunk (SOC) using the fast chunk upload path
|
|
492
|
+
*
|
|
493
|
+
* This constructs an unencrypted SOC and uploads it via /chunks to avoid /soc size limits.
|
|
494
|
+
*/
|
|
495
|
+
export async function uploadSOC(
|
|
496
|
+
bee: Bee,
|
|
497
|
+
stamper: Stamper,
|
|
498
|
+
signer: PrivateKey,
|
|
499
|
+
identifier: Identifier,
|
|
500
|
+
data: Uint8Array,
|
|
501
|
+
options?: UploadOptions,
|
|
502
|
+
): Promise<UploadSOCResult> {
|
|
503
|
+
if (data.length < 1 || data.length > 4096) {
|
|
504
|
+
throw new Error(`Invalid data length: ${data.length} (expected 1-4096)`)
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const cac = makeContentAddressedChunk(data)
|
|
508
|
+
// Bee recognizes SOCs by full SOC size (32+65+4104). Pad CAC to 4104 bytes
|
|
509
|
+
// so /chunks treats this as a SOC instead of a regular chunk, avoiding
|
|
510
|
+
// "stamp signature is invalid" errors.
|
|
511
|
+
const cacData = new Uint8Array(8 + 4096)
|
|
512
|
+
cacData.set(cac.data)
|
|
513
|
+
const owner = signer.publicKey().address()
|
|
514
|
+
|
|
515
|
+
const toSign = Binary.concatBytes(
|
|
516
|
+
identifier.toUint8Array(),
|
|
517
|
+
cac.address.toUint8Array(),
|
|
518
|
+
)
|
|
519
|
+
const signature = signer.sign(toSign)
|
|
520
|
+
|
|
521
|
+
const socData = Binary.concatBytes(
|
|
522
|
+
identifier.toUint8Array(),
|
|
523
|
+
signature.toUint8Array(),
|
|
524
|
+
cacData,
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
const socAddress = makeSOCAddress(identifier, owner.toUint8Array())
|
|
528
|
+
|
|
529
|
+
let tag: number | undefined = options?.tag
|
|
530
|
+
if (!tag) {
|
|
531
|
+
const tagResponse = await bee.createTag()
|
|
532
|
+
tag = tagResponse.uid
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const envelope = stamper.stamp({
|
|
536
|
+
hash: () => socAddress.toUint8Array(),
|
|
537
|
+
build: () => socData,
|
|
538
|
+
span: 0n,
|
|
539
|
+
writer: undefined as any,
|
|
540
|
+
})
|
|
541
|
+
|
|
542
|
+
const uploadOptionsWithTag = { tag, deferred: false, pin: false, ...options }
|
|
543
|
+
await uploadChunkWithFetch(bee, envelope, socData, uploadOptionsWithTag)
|
|
544
|
+
|
|
545
|
+
return {
|
|
546
|
+
socAddress: socAddress.toUint8Array(),
|
|
547
|
+
tagUid: tag,
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Upload SOC via the /soc/{owner}/{id} endpoint.
|
|
553
|
+
*
|
|
554
|
+
* Use this for small SOCs (< 4104 bytes) that need to preserve exact CAC size,
|
|
555
|
+
* such as v1 format feeds for /bzz/ compatibility.
|
|
556
|
+
*
|
|
557
|
+
* The /soc endpoint explicitly handles SOC uploads without size-based detection,
|
|
558
|
+
* avoiding "stamp signature is invalid" errors for small SOCs that would be
|
|
559
|
+
* misidentified as CAC by the /chunks endpoint.
|
|
560
|
+
*
|
|
561
|
+
* Key differences from uploadSOC:
|
|
562
|
+
* - Uses /soc/{owner}/{id}?sig=... endpoint (explicit SOC handling)
|
|
563
|
+
* - Does NOT pad CAC data (preserves exact payload size for v1 format)
|
|
564
|
+
* - Stamps using CAC address (what /soc endpoint expects)
|
|
565
|
+
*/
|
|
566
|
+
export async function uploadSOCViaSocEndpoint(
|
|
567
|
+
bee: Bee,
|
|
568
|
+
stamper: Stamper,
|
|
569
|
+
signer: PrivateKey,
|
|
570
|
+
identifier: Identifier,
|
|
571
|
+
data: Uint8Array,
|
|
572
|
+
options?: UploadOptions,
|
|
573
|
+
): Promise<UploadSOCResult> {
|
|
574
|
+
if (data.length < 1 || data.length > 4096) {
|
|
575
|
+
throw new Error(`Invalid data length: ${data.length} (expected 1-4096)`)
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Build CAC data (span + payload) - NO PADDING
|
|
579
|
+
// This preserves the exact size needed for v1 format (/bzz/ compatibility)
|
|
580
|
+
const cac = makeContentAddressedChunk(data)
|
|
581
|
+
const cacData = cac.data // span(8) + payload - NOT padded
|
|
582
|
+
|
|
583
|
+
const owner = signer.publicKey().address()
|
|
584
|
+
|
|
585
|
+
// Sign: hash(identifier + cac.address)
|
|
586
|
+
const toSign = Binary.concatBytes(
|
|
587
|
+
identifier.toUint8Array(),
|
|
588
|
+
cac.address.toUint8Array(),
|
|
589
|
+
)
|
|
590
|
+
const signature = signer.sign(toSign)
|
|
591
|
+
|
|
592
|
+
// Calculate SOC address (for return value)
|
|
593
|
+
const socAddress = makeSOCAddress(identifier, owner.toUint8Array())
|
|
594
|
+
|
|
595
|
+
// Debug logging for v1 format verification
|
|
596
|
+
|
|
597
|
+
// Create tag
|
|
598
|
+
let tag: number | undefined = options?.tag
|
|
599
|
+
if (!tag) {
|
|
600
|
+
const tagResponse = await bee.createTag()
|
|
601
|
+
tag = tagResponse.uid
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Stamp using SOC address (what Bee validates for /soc endpoint)
|
|
605
|
+
const envelope = stamper.stamp({
|
|
606
|
+
hash: () => socAddress.toUint8Array(),
|
|
607
|
+
build: () => cacData,
|
|
608
|
+
span: 0n,
|
|
609
|
+
writer: undefined as any,
|
|
610
|
+
})
|
|
611
|
+
|
|
612
|
+
// Marshal the envelope to hex for the HTTP header
|
|
613
|
+
// Order: batchId (32) + index (8) + timestamp (8) + signature (65) = 113 bytes
|
|
614
|
+
const marshaledStamp = Binary.concatBytes(
|
|
615
|
+
envelope.batchId.toUint8Array(),
|
|
616
|
+
envelope.index,
|
|
617
|
+
envelope.timestamp,
|
|
618
|
+
envelope.signature,
|
|
619
|
+
)
|
|
620
|
+
const stampHex = Binary.uint8ArrayToHex(marshaledStamp)
|
|
621
|
+
|
|
622
|
+
// Build URL with signature query parameter
|
|
623
|
+
const url = `${bee.url}/soc/${owner.toHex()}/${identifier.toHex()}?sig=${signature.toHex()}`
|
|
624
|
+
|
|
625
|
+
// Prepare HTTP headers (matching uploadChunkWithFetch pattern)
|
|
626
|
+
const headers: Record<string, string> = {
|
|
627
|
+
"content-type": "application/octet-stream",
|
|
628
|
+
"swarm-postage-stamp": stampHex,
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// Add optional headers only if defined (let Bee use defaults otherwise)
|
|
632
|
+
if (tag) headers["swarm-tag"] = tag.toString()
|
|
633
|
+
if (options?.deferred !== undefined) {
|
|
634
|
+
headers["swarm-deferred-upload"] = options.deferred.toString()
|
|
635
|
+
}
|
|
636
|
+
if (options?.pin !== undefined) {
|
|
637
|
+
headers["swarm-pin"] = options.pin.toString()
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// Upload via /soc endpoint
|
|
641
|
+
const response = await fetch(url, {
|
|
642
|
+
method: "POST",
|
|
643
|
+
headers,
|
|
644
|
+
body: cacData, // Raw CAC data (NOT full SOC structure)
|
|
645
|
+
})
|
|
646
|
+
|
|
647
|
+
if (!response.ok) {
|
|
648
|
+
const errorText = await response.text()
|
|
649
|
+
throw new Error(
|
|
650
|
+
`SOC upload failed: ${response.status} ${response.statusText} - ${errorText}`,
|
|
651
|
+
)
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
return {
|
|
655
|
+
socAddress: socAddress.toUint8Array(),
|
|
656
|
+
tagUid: tag,
|
|
657
|
+
}
|
|
658
|
+
}
|