@dstorage-tech/dstorage-sdk 0.0.4

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 (106) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +530 -0
  3. package/dist/KeypairEncryptionAdapter-AE75YC4W-AOWNSH2M.mjs +9 -0
  4. package/dist/KeypairEncryptionAdapter-AE75YC4W-BAVHRNNJ.mjs +1 -0
  5. package/dist/KeypairEncryptionAdapter-AE75YC4W-BEYO4RUO.mjs +1 -0
  6. package/dist/KeypairEncryptionAdapter-AE75YC4W-VAOH6AAL.mjs +9 -0
  7. package/dist/KeypairEncryptionAdapter-XUKCLRFU-6ERIZN2M.mjs +1 -0
  8. package/dist/KeypairEncryptionAdapter-XUKCLRFU-AYDAZ2TZ.mjs +9 -0
  9. package/dist/KeypairEncryptionAdapter-XUKCLRFU-ERPYK5S4.mjs +8 -0
  10. package/dist/MnemonicEncryptionAdapter-J6IE5OKA-6DFVUVD6.mjs +1 -0
  11. package/dist/MnemonicEncryptionAdapter-J6IE5OKA-O5AJPX42.mjs +1 -0
  12. package/dist/MnemonicEncryptionAdapter-J6IE5OKA-SI6TBLTQ.mjs +9 -0
  13. package/dist/MnemonicEncryptionAdapter-J6IE5OKA-WEPFOTSJ.mjs +9 -0
  14. package/dist/MnemonicEncryptionAdapter-RCHUETBP-DZPBGQG6.mjs +8 -0
  15. package/dist/MnemonicEncryptionAdapter-RCHUETBP-ELAMJMMO.mjs +1 -0
  16. package/dist/MnemonicEncryptionAdapter-RCHUETBP-M7NHGSHO.mjs +9 -0
  17. package/dist/PasswordEncryptionAdapter-CWOXUGR4-2YZ64B4A.mjs +1 -0
  18. package/dist/PasswordEncryptionAdapter-CWOXUGR4-DCEGC2NX.mjs +1 -0
  19. package/dist/PasswordEncryptionAdapter-CWOXUGR4-QEGGYPFJ.mjs +13 -0
  20. package/dist/PasswordEncryptionAdapter-CWOXUGR4-ZYUBUYUI.mjs +13 -0
  21. package/dist/PasswordEncryptionAdapter-ECHND672-3DEFO6FP.mjs +13 -0
  22. package/dist/PasswordEncryptionAdapter-ECHND672-MUTHAMZY.mjs +12 -0
  23. package/dist/PasswordEncryptionAdapter-ECHND672-XVBPHPPT.mjs +1 -0
  24. package/dist/browser.d.mts +2418 -0
  25. package/dist/browser.mjs +5175 -0
  26. package/dist/chunk-25I6ZXUC.mjs +9403 -0
  27. package/dist/chunk-4X5K43JF.mjs +62 -0
  28. package/dist/chunk-4YMF57VY.mjs +1 -0
  29. package/dist/chunk-5MZ7FPPY.mjs +1 -0
  30. package/dist/chunk-5P4EW2YL.mjs +1 -0
  31. package/dist/chunk-5QOKJEH3.mjs +8 -0
  32. package/dist/chunk-5RW7SGA7.mjs +1 -0
  33. package/dist/chunk-62RSKZ6T.mjs +118 -0
  34. package/dist/chunk-6IAQVMSM.mjs +102 -0
  35. package/dist/chunk-BBJCQFWP.mjs +275 -0
  36. package/dist/chunk-BWLEAAFW.mjs +2599 -0
  37. package/dist/chunk-EYF3263P.mjs +2049 -0
  38. package/dist/chunk-FJ7LSZOW.mjs +5537 -0
  39. package/dist/chunk-GUYENY5N.mjs +1 -0
  40. package/dist/chunk-HDD6SUU4.mjs +276 -0
  41. package/dist/chunk-JMPRZK5D.mjs +62 -0
  42. package/dist/chunk-L5QD6GHB.mjs +101 -0
  43. package/dist/chunk-O37NHERH.mjs +17 -0
  44. package/dist/chunk-OLGVVM5Y.mjs +102 -0
  45. package/dist/chunk-PKHRAYFD.mjs +1 -0
  46. package/dist/chunk-PLAOCKSS.mjs +101 -0
  47. package/dist/chunk-QKA2DCR6.mjs +118 -0
  48. package/dist/chunk-RPSRFFXN.mjs +275 -0
  49. package/dist/chunk-TESUOXRJ.mjs +1 -0
  50. package/dist/chunk-TMLXCRJU.mjs +1 -0
  51. package/dist/chunk-TNRKN4LB.mjs +1 -0
  52. package/dist/chunk-TSZ37JDY.mjs +2596 -0
  53. package/dist/chunk-U3MS6KMF.mjs +1 -0
  54. package/dist/chunk-UBALMYIY.mjs +2049 -0
  55. package/dist/chunk-UDYXMII2.mjs +121 -0
  56. package/dist/chunk-UJCSKKID.mjs +30 -0
  57. package/dist/chunk-UOOF4V2Y.mjs +1 -0
  58. package/dist/chunk-VDZ7F7MP.mjs +1329 -0
  59. package/dist/chunk-X4LEBNK6.mjs +276 -0
  60. package/dist/chunk-Z2TH6U4C.mjs +3 -0
  61. package/dist/chunk-Z6DUAI5B.mjs +121 -0
  62. package/dist/chunk-ZEPZ4QDD.mjs +1 -0
  63. package/dist/chunk-ZGUY5EIR.mjs +1 -0
  64. package/dist/cjs-RMZ3L2IO.mjs +5 -0
  65. package/dist/contract-api-AO4RGPLT-4HRCMSCE.mjs +279 -0
  66. package/dist/contract-api-AO4RGPLT-ADH6ZDKC.mjs +279 -0
  67. package/dist/contract-api-AO4RGPLT-ENC72WCG.mjs +280 -0
  68. package/dist/contract-api-AO4RGPLT-PAMIPEI3.mjs +1 -0
  69. package/dist/contract-api-AO4RGPLT-W3RSVP5A.mjs +1 -0
  70. package/dist/contract-api-AO4RGPLT-WENYDV6I.mjs +1 -0
  71. package/dist/contract-api-TJ7JPHZA-6FUBJ6DS.mjs +279 -0
  72. package/dist/contract-api-TJ7JPHZA-ZWP3EH7D.mjs +279 -0
  73. package/dist/contracts/dataregistry/managed/keys/removeReference.prover +0 -0
  74. package/dist/contracts/dataregistry/managed/keys/removeReference.verifier +0 -0
  75. package/dist/contracts/dataregistry/managed/keys/storeReference.prover +0 -0
  76. package/dist/contracts/dataregistry/managed/keys/storeReference.verifier +0 -0
  77. package/dist/contracts/dataregistry/managed/keys/updateReference.prover +0 -0
  78. package/dist/contracts/dataregistry/managed/keys/updateReference.verifier +0 -0
  79. package/dist/contracts/dataregistry/managed/zkir/removeReference.bzkir +0 -0
  80. package/dist/contracts/dataregistry/managed/zkir/removeReference.zkir +138 -0
  81. package/dist/contracts/dataregistry/managed/zkir/storeReference.bzkir +0 -0
  82. package/dist/contracts/dataregistry/managed/zkir/storeReference.zkir +158 -0
  83. package/dist/contracts/dataregistry/managed/zkir/updateReference.bzkir +0 -0
  84. package/dist/contracts/dataregistry/managed/zkir/updateReference.zkir +190 -0
  85. package/dist/dataregistry-simulator-E6RTGGWT-5YEGBW7P.mjs +108 -0
  86. package/dist/dataregistry-simulator-E6RTGGWT-DJS4MHBD.mjs +109 -0
  87. package/dist/dataregistry-simulator-E6RTGGWT-QGCYR3VF.mjs +1 -0
  88. package/dist/dist-655VYUPJ.mjs +1 -0
  89. package/dist/dist-INTAFXQ7.mjs +65 -0
  90. package/dist/dist-MEZZWKP4.mjs +1 -0
  91. package/dist/dist-MOLWJ4Y7.mjs +64 -0
  92. package/dist/dist-UD4WRMYJ.mjs +65 -0
  93. package/dist/errors.d.mts +117 -0
  94. package/dist/errors.d.ts +117 -0
  95. package/dist/errors.js +2308 -0
  96. package/dist/errors.mjs +2278 -0
  97. package/dist/esm5-AGBDHMNS.mjs +1 -0
  98. package/dist/esm5-GFOBLUFI.mjs +350 -0
  99. package/dist/in-memory-private-state-NGOP37V7-DXWZE42Q.mjs +48 -0
  100. package/dist/in-memory-private-state-NGOP37V7-QXT3RUEH.mjs +49 -0
  101. package/dist/in-memory-private-state-NGOP37V7-XFJFNFCQ.mjs +1 -0
  102. package/dist/index.d.mts +2843 -0
  103. package/dist/index.d.ts +2843 -0
  104. package/dist/index.js +19639 -0
  105. package/dist/index.mjs +5177 -0
  106. package/package.json +87 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Gabriel Viganotti, Analia Rufinetto
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,530 @@
1
+ # dStorage SDK
2
+
3
+ > Privacy-first data layer for dApps — client-side encryption + decentralised storage + on-chain coordination in a single SDK.
4
+
5
+ For teams building privacy-sensitive dApps (health, identity, finance, and enterprise workflows) that need strong client-side confidentiality without operating plaintext data infrastructure.
6
+
7
+ ---
8
+
9
+ ## What it does
10
+
11
+ Instead of dApps collecting and storing user data on centralised servers, dStorage shifts data ownership to the user:
12
+
13
+ - **Encrypts** data client-side using keys derived from a pluggable encryption adapter (password, wallet signature, mnemonic, or custom)
14
+ - **Pays** storage and chain networks using adapter-defined tokens (MOCK, AR, DUST)
15
+ - **Stores** encrypted blobs on decentralised storage (Mock, Arweave Local, Arweave, ...)
16
+ - **Writes** cryptographic references on-chain (Mock, Midnight)
17
+ - **Decrypts** locally on the user side — dApp operators and backend servers never see raw user data
18
+
19
+ Privacy becomes an **architectural guarantee**, not a compliance promise.
20
+
21
+ Most applications store user data on servers they control. That means users have to trust that the service provider won't read, sell, or lose their data — and history shows that trust is often misplaced. dStorage takes a different approach: data is encrypted on the user's device before it ever leaves, stored on a decentralised network where no single party has control, and a tamper-proof on-chain record ties the data back to its owner. The service provider never sees the plaintext. Neither does anyone else.
22
+
23
+ The way it works is straightforward. When a user uploads something, the SDK encrypts it locally using a key derived from an encryption adapter — a key that only they can regenerate. The encrypted blob goes to a storage network (Arweave, Arweave Local, or a mock for development). A lightweight reference — just a storage ID — is then written to a blockchain, giving the owner a verifiable receipt. To retrieve the data, the process runs in reverse: look up the reference on-chain, fetch the blob/s, decrypt them locally. At no point does any server hold an unencrypted copy.
24
+
25
+ For developers, the SDK is designed to get out of the way. You configure it once with the storage and chain backends that fit your stack, call `init()`, and use `store` and `retrieveByRefId`. You can start entirely with in-memory mocks (no network, no tokens, no wallet extension), and swap in network-backed adapters when you are ready.
26
+
27
+ ---
28
+
29
+ ## SDK Usage
30
+
31
+ ```sh
32
+ npm install @dstorage-tech/dstorage-sdk
33
+ ```
34
+
35
+ ```typescript
36
+ import {
37
+ DStorage,
38
+ MockStorageAdapter,
39
+ MockChainAdapter,
40
+ PasswordEncryptionAdapter,
41
+ } from "@dstorage-tech/dstorage-sdk";
42
+
43
+ // 1. Configure the SDK with your chosen adapters and encryption adapter(s)
44
+ const sdk = new DStorage({
45
+ storageAdapter: new MockStorageAdapter(), // swap for ArweaveStorageAdapter, ArweaveLocalStorageAdapter, etc.
46
+ chainAdapter: new MockChainAdapter(), // swap for MidnightChainAdapter
47
+ encryptionAdapters: [
48
+ new PasswordEncryptionAdapter({
49
+ password: "correct-horse-battery-staple",
50
+ salt: "myapp:v1:alice@example.com", // app + user identifier — store this alongside your data
51
+ }),
52
+ // Multiple adapters are supported — any one of them can independently decrypt.
53
+ // e.g. add a MnemonicEncryptionAdapter({ mnemonic: "..." }) as a backup key.
54
+ ],
55
+ });
56
+
57
+ // 2. Init — derives the encryption key and prepares the chain contract
58
+ await sdk.init();
59
+
60
+ // 3. Encrypt, store, and write an on-chain reference in one call
61
+ const { chainRefId, storageId } = await sdk.store(
62
+ new TextEncoder().encode("Hello dStorage!"),
63
+ );
64
+
65
+ // 4. Retrieve and decrypt by on-chain reference ID
66
+ const { bytes } = await sdk.retrieveByRefId(chainRefId);
67
+ console.log(new TextDecoder().decode(bytes)); // "Hello dStorage!"
68
+
69
+ // 5. Or retrieve directly by storage ID
70
+ const { bytes: direct } = await sdk.retrieveByStorageId(storageId);
71
+ console.log(new TextDecoder().decode(direct)); // "Hello dStorage!"
72
+ ```
73
+
74
+ `store()` also accepts an options object as the second argument:
75
+
76
+ ```typescript
77
+ const { chainRefId } = await sdk.store(bytes, {
78
+ metadata: { filename: "report.pdf", version: "2" },
79
+ onProgress: ({ phase, chunksUploaded, totalChunks }) => {
80
+ console.log(`${phase}: ${chunksUploaded}/${totalChunks} chunks`);
81
+ },
82
+ // isPublic: true, // opt-in: stores and references unencrypted (irreversible — see below)
83
+ });
84
+ ```
85
+
86
+ ### Arweave Local (development)
87
+
88
+ [arlocal](https://github.com/textury/arlocal) is a local Arweave node for development and testing. `ArweaveLocalStorageAdapter` targets it by default (`localhost:1984`).
89
+
90
+ **Path A — auto-generated test wallet** (recommended for local dev and tests):
91
+
92
+ ```typescript
93
+ import { ArweaveLocalStorageAdapter } from "@dstorage-tech/dstorage-sdk";
94
+
95
+ // Spins up a funded wallet against the running arlocal instance
96
+ const { adapter: storageAdapter } =
97
+ await ArweaveLocalStorageAdapter.createWithTestWallet({
98
+ fundAr: 10, // AR tokens to mint (default: 10)
99
+ });
100
+ ```
101
+
102
+ **Path B — existing wallet key file** (e.g. a pre-funded JWK on disk):
103
+
104
+ ```typescript
105
+ import fs from "node:fs";
106
+ import { ArweaveLocalStorageAdapter } from "@dstorage-tech/dstorage-sdk";
107
+
108
+ const walletKey = JSON.parse(fs.readFileSync("arweave-wallet.json", "utf8"));
109
+ const storageAdapter = new ArweaveLocalStorageAdapter({ walletKey });
110
+ ```
111
+
112
+ **Path C — custom gateway host and port** (e.g. arlocal running on a different machine or port):
113
+
114
+ ```typescript
115
+ import { ArweaveLocalStorageAdapter } from "@dstorage-tech/dstorage-sdk";
116
+
117
+ const storageAdapter = new ArweaveLocalStorageAdapter({
118
+ gateway: { host: "192.168.1.42", port: 4000, protocol: "http" },
119
+ });
120
+ ```
121
+
122
+ ---
123
+
124
+ ## Browser Usage
125
+
126
+ The same `npm install @dstorage-tech/dstorage-sdk` import works in the browser — no
127
+ separate package, no subpath required:
128
+
129
+ ```typescript
130
+ import { DStorage, ArweaveStorageAdapter } from "@dstorage-tech/dstorage-sdk";
131
+ ```
132
+
133
+ Bundlers configured for a browser target (Vite, webpack 5+, Rollup, esbuild) resolve the
134
+ plain import to a pre-built, minified, browser-safe bundle automatically via the
135
+ `"browser"` condition in `package.json`'s `exports` map — it excludes all Node.js-only
136
+ code (`node:fs`, LevelDB, etc.). For tooling that doesn't resolve conditional exports, or
137
+ for explicit clarity, the same bundle is also available at the `/browser` subpath:
138
+
139
+ ```typescript
140
+ import {
141
+ DStorage,
142
+ ArweaveStorageAdapter,
143
+ } from "@dstorage-tech/dstorage-sdk/browser";
144
+ ```
145
+
146
+ Only `MidnightChainAdapter`'s Node.js **provider/facade mode** (LevelDB private state,
147
+ filesystem-based ZK artifacts) is unavailable in the browser bundle — **connector mode**
148
+ (browser wallet extension) works normally. Every other adapter (storage, payment, mock,
149
+ simulator, HTTP gateway) is fully included.
150
+
151
+ ---
152
+
153
+ ## Midnight ZK Artifacts
154
+
155
+ `MidnightChainAdapter` writes on-chain references through dStorage's own `DataRegistry`
156
+ contract — the same fixed contract for every consumer, not something you compile
157
+ yourself. Its compiled ZK artifacts (prover/verifier keys and ZKIR) ship inside this
158
+ package at `dist/contracts/dataregistry/managed/{keys,zkir}`.
159
+
160
+ **Node.js (`walletMode: "provider"`)** — point `zkArtifactsPath` at the bundled copy.
161
+ Resolving it via the package's own `package.json` (rather than hardcoding a
162
+ `node_modules` path) works regardless of hoisting or package manager:
163
+
164
+ ```typescript
165
+ import { createRequire } from "node:module";
166
+ import path from "node:path";
167
+
168
+ const pkgJsonPath = createRequire(import.meta.url).resolve(
169
+ "@dstorage-tech/dstorage-sdk/package.json",
170
+ );
171
+ const zkArtifactsPath = path.join(
172
+ path.dirname(pkgJsonPath),
173
+ "dist/contracts/dataregistry/managed",
174
+ );
175
+
176
+ const chainAdapter = new MidnightChainAdapter({
177
+ walletMode: "provider",
178
+ walletProvider,
179
+ privateStatePassword,
180
+ zkArtifactsPath,
181
+ // ...
182
+ });
183
+ ```
184
+
185
+ **Browser (`walletMode: "connector"`)** — the browser bundle can't read from
186
+ `node_modules`, so copy the same directory into your app's own static assets as part of
187
+ your build (mirroring how any Midnight dApp serves its compiled contract artifacts —
188
+ see the [Midnight docs](https://docs.midnight.network/) for the general pattern), then
189
+ point `zkConfigBaseUrl` at wherever you serve it from:
190
+
191
+ ```sh
192
+ cp -a node_modules/@dstorage-tech/dstorage-sdk/dist/contracts/dataregistry/managed/keys ./public/
193
+ cp -a node_modules/@dstorage-tech/dstorage-sdk/dist/contracts/dataregistry/managed/zkir ./public/
194
+ ```
195
+
196
+ ```typescript
197
+ const chainAdapter = new MidnightChainAdapter({
198
+ walletMode: "connector",
199
+ zkConfigBaseUrl: window.location.origin, // serves keys/ and zkir/ from public/
200
+ // ...
201
+ });
202
+ ```
203
+
204
+ ---
205
+
206
+ ## Error Handling
207
+
208
+ Every error thrown by the SDK is a `DStorageError` — a subclass of `Error` that always carries a numeric `.code`. Use the `isDStorageError` type guard to distinguish SDK errors from unexpected runtime failures:
209
+
210
+ ```typescript
211
+ import { isDStorageError } from "@dstorage-tech/dstorage-sdk";
212
+
213
+ try {
214
+ await sdk.store(bytes);
215
+ } catch (err) {
216
+ if (isDStorageError(err)) {
217
+ console.error(`SDK error (code ${err.code}): ${err.message}`);
218
+ } else {
219
+ throw err; // should not happen — all SDK errors are DStorageError
220
+ }
221
+ }
222
+ ```
223
+
224
+ Error codes are namespaced by package:
225
+
226
+ | Range | Package |
227
+ | ----------- | ---------------------- |
228
+ | 10000–10999 | `@dstorage/core` |
229
+ | 11000–11999 | `@dstorage/chain` |
230
+ | 12000–12999 | `@dstorage/crypto` |
231
+ | 13000–13999 | `@dstorage/encryption` |
232
+ | 14000–14999 | `@dstorage/payment` |
233
+ | 15000–15999 | `@dstorage/storage` |
234
+
235
+ Code `10999` (`UNKNOWN_ERROR`) is a catch-all — it means an error escaped without an explicit code. If you see it, it is a bug worth reporting.
236
+
237
+ ### Handling partial failures
238
+
239
+ `store()` uploads content first, then writes the on-chain reference. If the chain write fails after a successful storage upload, `store()` throws a `StorePartialError` — a `DStorageError` subclass with a `recovery` payload. Use it with `registerReference()` to retry only the chain write without re-uploading:
240
+
241
+ ```typescript
242
+ import { isStorePartialError } from "@dstorage-tech/dstorage-sdk";
243
+
244
+ try {
245
+ await sdk.store(bytes);
246
+ } catch (err) {
247
+ if (isStorePartialError(err)) {
248
+ const { storageId, storageProvider, keyEnvelope } = err.recovery;
249
+ const { chainRefId } = await sdk.registerReference({
250
+ storageId,
251
+ storageProvider,
252
+ keyEnvelope,
253
+ });
254
+ }
255
+ }
256
+ ```
257
+
258
+ For resilience against crashes before the chain write, persist the recovery payload from the `"stored"` progress phase:
259
+
260
+ ```typescript
261
+ await sdk.store(bytes, {
262
+ onProgress(p) {
263
+ if (p.phase === "stored" && p.recovery) {
264
+ localStorage.setItem("pending-store", JSON.stringify(p.recovery));
265
+ }
266
+ },
267
+ });
268
+ localStorage.removeItem("pending-store"); // clear on success
269
+ ```
270
+
271
+ `keyEnvelope` is the DEK already wrapped under your KEK — safe to persist. The raw DEK is never exposed.
272
+
273
+ ---
274
+
275
+ ## Encryption Design
276
+
277
+ ### Encryption scheme
278
+
279
+ The SDK uses **XChaCha20-Poly1305** as its single symmetric AEAD encryption scheme:
280
+
281
+ | Scheme | Nonce | Tag | Birthday bound | Notes |
282
+ | ---------------------- | ----- | ---- | ---------------- | ------------------------------------------------------ |
283
+ | **XChaCha20-Poly1305** | 24 B | 16 B | 2⁸⁰ (negligible) | 192-bit nonce; safe for random generation indefinitely |
284
+
285
+ XChaCha20-Poly1305's 192-bit nonce pushes the birthday bound to 2⁸⁰, making random nonce collision a non-issue in practice.
286
+
287
+ ### Two-layer encryption
288
+
289
+ The SDK encrypts two distinct things using the same derived key:
290
+
291
+ | Layer | What is encrypted | Where it lives |
292
+ | ------------------- | -------------------------------------------- | ------------------------------------ |
293
+ | **Data payload** | The user's file / content bytes | Off-chain storage (Arweave, Mock, …) |
294
+ | **Storage pointer** | The storage network content ID (`storageId`) | On-chain (`DataRegistry` contract) |
295
+
296
+ Encrypting the storage pointer means a blockchain observer cannot learn which decentralised-storage resource belongs to which wallet, even though the on-chain record is public.
297
+
298
+ #### Public mode (`isPublic: true`)
299
+
300
+ The SDK supports an explicit opt-in public mode:
301
+
302
+ ```typescript
303
+ await sdk.store(data, { isPublic: true });
304
+ ```
305
+
306
+ In public mode **neither layer is encrypted**:
307
+
308
+ - The data payload is stored as raw bytes on the storage network — anyone with the storageId can read it.
309
+ - The storageId is written to the chain as plaintext — anyone watching the chain can see which storage resource belongs to which on-chain record.
310
+ - `init()` is still required, same as private uploads.
311
+ - When a `chainAdapter` is configured, at least one encryption adapter is still required even in public mode — it's used to derive the owner secret needed to later update or remove the reference, not to encrypt the payload. Configuring no encryption adapters at all only works in storage-only mode (no `chainAdapter`).
312
+ - The on-chain `encryptionScheme` field is set to `""` (empty string), which the SDK uses on read to skip decryption entirely.
313
+
314
+ > **Warning**: Public mode is a permanent, irreversible choice per upload. Once data is stored unencrypted on Arweave (or another permanent storage network) it cannot be made private retroactively. Only use `isPublic: true` for data you explicitly intend to be world-readable.
315
+
316
+ The default (`isPublic` omitted or `false`) always encrypts both layers.
317
+
318
+ **Key reuse across layers is safe** because XChaCha20-Poly1305 generates a fresh random nonce per encryption call. The data payload and the storageId are encrypted in separate calls with independently generated nonces, so there is no cross-layer nonce collision risk.
319
+
320
+ ### Key derivation
321
+
322
+ The raw seed bytes returned by the `EncryptionAdapter` are used to derive a Key Encryption Key (KEK) via HKDF-SHA-256. A fresh random Data Encryption Key (DEK) is generated per upload, encrypted under the KEK, and stored alongside the on-chain reference in the `keyEnvelope` field.
323
+
324
+ | Layer | Primitive | Salt (fixed) | Info label |
325
+ | ----- | ------------ | ----------------------- | -------------------------------------- |
326
+ | KEK | HKDF-SHA-256 | `"dstorage-xchacha-v1"` | `"dstorage:xchacha20poly1305:v1"` |
327
+ | DEK | random | — | 32 bytes from `crypto.getRandomValues` |
328
+
329
+ ### Encryption adapters
330
+
331
+ The `@dstorage/encryption` package is responsible for producing the raw seed bytes that feed the key-derivation functions above.
332
+
333
+ | Adapter | Seed source | Post-quantum safety | Cross-device portability |
334
+ | --------------------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
335
+ | `PasswordEncryptionAdapter` | scrypt(password, caller-supplied salt) | ⚠️ 128-bit PQ at the data layer (NIST minimum). Key encapsulation safety depends on password entropy — use `generatePqsPassword()` for machine-generated PQ-safe input. | ✅ Same seed on any device — depends only on password + salt |
336
+ | `MnemonicEncryptionAdapter` | BIP-39 mnemonic phrase (**24 words required**) or 64-byte hex seed | ✅ 128-bit PQ — unconditional (CSPRNG-generated 256-bit entropy; Grover's halves to 128-bit). Fewer than 24 words is rejected at construction. | ✅ Same seed wherever the same mnemonic/hex is imported |
337
+ | `KeypairEncryptionAdapter` | ML-KEM public/secret key pair (NIST FIPS 203 — post-quantum) | ✅ 192-bit PQ via ML-KEM768 — unconditional, no password or entropy requirement. | ⚠️ Requires the Secret Key for decryption — not re-derivable unless created via `fromPassword()` / `fromMnemonic()` |
338
+
339
+ #### Cross-device portability
340
+
341
+ | Scenario | Expected outcome |
342
+ | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
343
+ | Same password + same salt on a different device (`PasswordEncryptionAdapter`) | ✅ Same seed — scrypt output depends only on password, salt, and KDF params; no wallet required. |
344
+ | BIP-39 mnemonic imported on a different machine (`MnemonicEncryptionAdapter`) | ✅ Same seed — the mnemonic is the only input; derivation is purely deterministic. |
345
+ | Different password, same salt (`PasswordEncryptionAdapter`) | ❌ Different seed — intentional; each password produces an independent key. |
346
+ | ML-KEM keypair from `generateKeypair()` re-imported (`KeypairEncryptionAdapter`) | ✅ Same keypair wherever the SK bytes are imported — but the SK must be stored; it cannot be re-derived. |
347
+ | ML-KEM keypair from `fromPassword()` or `fromMnemonic()` (`KeypairEncryptionAdapter`) | ✅ Same keypair — deterministic; no key storage needed, re-derive from password+salt or mnemonic at any time. |
348
+ | `KeypairEncryptionAdapter.fromPublicKey(pk)` (upload-only) | 🔒 PK-only — can wrap DEK for upload; unwrapping requires the matching SK. |
349
+
350
+ #### Multi-key encryption
351
+
352
+ `encryptionAdapters` accepts an **array** of adapters. When more than one is supplied, the SDK wraps the same per-upload DEK under every KEK independently. Any single adapter can decrypt any upload made while it was in the list:
353
+
354
+ ```typescript
355
+ encryptionAdapters: [
356
+ new PasswordEncryptionAdapter({ password, salt }), // primary key
357
+ new MnemonicEncryptionAdapter({ mnemonic: backupPhrase }), // recovery key
358
+ ];
359
+ ```
360
+
361
+ Both adapters can decrypt; neither knows anything about the other's credentials.
362
+
363
+ You can also mix in a post-quantum adapter — the uploader needs only the Public Key:
364
+
365
+ ```typescript
366
+ const { adapter: mlkemAdapter, secretKey } =
367
+ KeypairEncryptionAdapter.generateKeypair("myapp:v1");
368
+ // Store `secretKey` bytes safely — needed to decrypt later.
369
+ encryptionAdapters: [
370
+ new PasswordEncryptionAdapter({ password, salt }), // symmetric fallback
371
+ mlkemAdapter, // post-quantum DEK wrap
372
+ ];
373
+ ```
374
+
375
+ #### Post-quantum (ML-KEM)
376
+
377
+ `KeypairEncryptionAdapter` uses ML-KEM (CRYSTALS-Kyber, NIST FIPS 203) — a post-quantum Key Encapsulation Mechanism — to wrap the per-upload DEK. Unlike the symmetric adapters it uses an asymmetric public/secret keypair:
378
+
379
+ - **`wrapDek()`** requires only the **Public Key** — safe to hand to upload-only parties.
380
+ - **`unwrapDek()`** requires the **Secret Key** — held only by the authorised reader.
381
+
382
+ | Variant | Post-quantum security | PK size | SK size | KEM ciphertext |
383
+ | ----------- | --------------------- | ------- | ------- | -------------- |
384
+ | `mlkem512` | 128-bit | 800 B | 1632 B | 768 B |
385
+ | `mlkem768` | 192-bit (default) | 1184 B | 2400 B | 1088 B |
386
+ | `mlkem1024` | 256-bit | 1568 B | 3168 B | 1568 B |
387
+
388
+ **Usage patterns:**
389
+
390
+ ```typescript
391
+ // `context` is a domain-separation string binding the KEK derivation to your
392
+ // application (and, for the deterministic factories, to this specific use — pick
393
+ // a stable value and keep it consistent for any given adapter's data).
394
+
395
+ // 1 — Fresh random keypair (store the SK):
396
+ const { adapter, publicKey, secretKey } =
397
+ KeypairEncryptionAdapter.generateKeypair("myapp:v1");
398
+
399
+ // 2 — PK-only uploader (no SK needed at upload time):
400
+ const uploader = KeypairEncryptionAdapter.fromPublicKey(publicKey, "myapp:v1");
401
+
402
+ // 3 — Full keypair reader:
403
+ const reader = KeypairEncryptionAdapter.fromKeypair(
404
+ publicKey,
405
+ secretKey,
406
+ "myapp:v1",
407
+ );
408
+
409
+ // 4 — Deterministic keypair from password (same inputs → same keypair, always):
410
+ const adapter = await KeypairEncryptionAdapter.fromPassword(
411
+ password,
412
+ salt,
413
+ "myapp:v1",
414
+ );
415
+
416
+ // 5 — Deterministic keypair from BIP-39 mnemonic:
417
+ const adapter = await KeypairEncryptionAdapter.fromMnemonic(
418
+ { mnemonic },
419
+ "myapp:v1",
420
+ );
421
+ ```
422
+
423
+ #### Post-quantum safety across all adapters
424
+
425
+ Every private upload has two independently protected layers:
426
+
427
+ | Layer | Primitive | PQ security (all adapters) |
428
+ | --------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
429
+ | **Data payload** | XChaCha20-Poly1305, 256-bit key | ✅ 128-bit PQ — Grover's algorithm halves effective key length; NIST SP 800-131A approves 128-bit PQ as sufficient |
430
+ | **Key encapsulation** | Adapter-dependent | Varies — see table below |
431
+
432
+ The **key encapsulation layer** is where adapters differ:
433
+
434
+ | Adapter | Key encapsulation mechanism | PQ safety |
435
+ | --------------------------- | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
436
+ | `PasswordEncryptionAdapter` | scrypt(password) → HKDF-SHA-256 → KEK | ⚠️ Conditional on password entropy. A quantum attacker can brute-force the password; human-chosen passwords cannot reach the 256-bit entropy required for PQ-safe key encapsulation. Use `generatePqsPassword()` for machine-generated input. |
437
+ | `MnemonicEncryptionAdapter` | BIP-39(mnemonic) → HKDF-SHA-256 → KEK | ✅ 128-bit PQ — a 24-word BIP-39 mnemonic is CSPRNG-generated (256 bits of entropy); Grover's halves this to 128-bit. Unconditional and enforced: the adapter rejects mnemonics shorter than 24 words at construction time. |
438
+ | `KeypairEncryptionAdapter` | ML-KEM encapsulate(PK) → HKDF-SHA-256 → KEK | ✅ 192-bit PQ — security rests on the LWE problem; no known quantum speedup exists. Highest level available. |
439
+
440
+ For **"harvest now, decrypt later"** threat models, both `MnemonicEncryptionAdapter` (128-bit PQ, unconditional) and `KeypairEncryptionAdapter` (192-bit PQ, unconditional) provide solid post-quantum guarantees at the key encapsulation level. `PasswordEncryptionAdapter` with a human-chosen password does not — use `generatePqsPassword()` to close that gap.
441
+
442
+ #### Machine-generated PQ-safe passwords
443
+
444
+ If you want cross-device password recoverability **and** full ML-KEM post-quantum security, use `generatePqsPassword()`:
445
+
446
+ ```typescript
447
+ import {
448
+ generatePqsPassword,
449
+ KeypairEncryptionAdapter,
450
+ } from "@dstorage-tech/dstorage-sdk";
451
+
452
+ // 32 random bytes → 43-char base64url string (256 bits of real entropy).
453
+ // Store this string — it is the only secret needed to re-derive the keypair on any device.
454
+ const password = generatePqsPassword();
455
+
456
+ // Derives an ML-KEM keypair via scrypt → 64-byte seed → ML-KEM keygen.
457
+ // Same password + salt + context → same keypair, always, on any device.
458
+ const adapter = await KeypairEncryptionAdapter.fromPassword(
459
+ password,
460
+ "myapp:v1:alice@example.com", // salt — domain separator
461
+ "myapp:v1", // context — binds the KEK derivation to this application
462
+ );
463
+ ```
464
+
465
+ > **Warning — human-chosen passwords are not PQ-safe at the key encapsulation layer.**
466
+ > No human-memorable password or passphrase can reach the 256-bit entropy required to
467
+ > resist a Grover's-algorithm brute-force. Human passwords still provide 128-bit PQ
468
+ > security at the _data_ layer (NIST minimum, sufficient for most applications) but a
469
+ > quantum attacker could recover the password and rederive the KEK. Use
470
+ > `generatePqsPassword()` with `KeypairEncryptionAdapter.fromPassword()`, or
471
+ > `MnemonicEncryptionAdapter` with a 24-word BIP-39 mnemonic (12-word mnemonics are
472
+ > rejected — they give only 64-bit PQ security), if key-encapsulation-level PQ safety
473
+ > is required.
474
+
475
+ ---
476
+
477
+ ### Wire formats
478
+
479
+ #### Off-chain data payload
480
+
481
+ **XChaCha20-Poly1305** (JSON):
482
+
483
+ ```json
484
+ {
485
+ "ciphertext": "<base64 — XChaCha20 output: ciphertext || 16-byte Poly1305 tag>",
486
+ "nonce": "<base64 — 24-byte random nonce>",
487
+ "algorithm": "XChaCha20-Poly1305"
488
+ }
489
+ ```
490
+
491
+ #### On-chain storageId encryption layout
492
+
493
+ **XChaCha20-Poly1305** (JSON) — same wire format as the off-chain payload above, with a distinct associated data (AAD) label for domain separation. The plaintext is the storageId string's UTF-8 bytes (43 bytes for the standard base64url storage ID format), not raw decoded bytes:
494
+
495
+ ```json
496
+ {
497
+ "ciphertext": "<base64 — XChaCha20 output: ciphertext || 16-byte Poly1305 tag>",
498
+ "nonce": "<base64 — 24-byte random nonce>",
499
+ "algorithm": "XChaCha20-Poly1305"
500
+ }
501
+ ```
502
+
503
+ ### Scheme detection on read
504
+
505
+ | `encryptionScheme` value | Behaviour |
506
+ | ------------------------ | --------------------------------------------------------------------------------- |
507
+ | `"xchacha20poly1305-v1"` | Unwrap DEK from `keyEnvelope`, decrypt with XChaCha20-Poly1305 |
508
+ | `""` or absent | No decryption — content is public (see [Public mode](#public-mode-ispublic-true)) |
509
+
510
+ ### Tamper detection
511
+
512
+ **Authenticated Encryption with Associated Data (AEAD)**. The Poly1305 tag covers the ciphertext. Any bit-flip in the on-chain encrypted pointer causes decryption to throw before the SDK ever attempts to fetch from storage.
513
+
514
+ ### Storage ID format
515
+
516
+ The SDK treats `storageId` as an opaque string — the crypto layer encrypts its UTF-8 bytes (see [Wire formats](#wire-formats) above), so there is no length or format requirement at the SDK level. Today's Arweave-family adapters (`ArweaveStorageAdapter`, `ArweaveLocalStorageAdapter`, `ArweaveBundlerStorageAdapter`) all return 43-character base64url strings — 32 random bytes encoded without padding, matching the Arweave transaction ID format — but a storage adapter for a different network (e.g. an IPFS CID or another content-addressed ID) can return any string shape without requiring changes to the encryption layer.
517
+
518
+ ### Key loss and data recovery
519
+
520
+ Encryption keys are derived deterministically from the encryption adapter's inputs — no key material is stored by the SDK. This means:
521
+
522
+ - **The same inputs always produce the same key**: reconnecting with the same adapter and credentials decrypts existing data transparently.
523
+ - **Losing your credentials means permanent data loss.** There is no key escrow, no recovery mechanism, and no way to decrypt data without the original inputs:
524
+ - `PasswordEncryptionAdapter` — forgetting the password or losing the salt string makes data unrecoverable.
525
+ - `MnemonicEncryptionAdapter` / wallet providers — losing the seed phrase or private key has the same effect. Follow standard seed-phrase backup practices.
526
+ - `KeypairEncryptionAdapter` — if created with `generateKeypair()`, losing the SK bytes makes data unrecoverable. If created deterministically via `fromPassword()` or `fromMnemonic()`, the keypair can be re-derived from the original inputs.
527
+
528
+ ## License
529
+
530
+ MIT
@@ -0,0 +1,9 @@
1
+ import {
2
+ KeypairEncryptionAdapter
3
+ } from "./chunk-62RSKZ6T.mjs";
4
+ import "./chunk-O37NHERH.mjs";
5
+ import "./chunk-TSZ37JDY.mjs";
6
+ import "./chunk-UJCSKKID.mjs";
7
+ export {
8
+ KeypairEncryptionAdapter
9
+ };
@@ -0,0 +1 @@
1
+ import{a as r}from"./chunk-UOOF4V2Y.mjs";import"./chunk-PKHRAYFD.mjs";import"./chunk-UBALMYIY.mjs";export{r as KeypairEncryptionAdapter};
@@ -0,0 +1 @@
1
+ import{a as r}from"./chunk-ZEPZ4QDD.mjs";import"./chunk-PKHRAYFD.mjs";import"./chunk-EYF3263P.mjs";export{r as KeypairEncryptionAdapter};
@@ -0,0 +1,9 @@
1
+ import {
2
+ KeypairEncryptionAdapter
3
+ } from "./chunk-QKA2DCR6.mjs";
4
+ import "./chunk-O37NHERH.mjs";
5
+ import "./chunk-BWLEAAFW.mjs";
6
+ import "./chunk-UJCSKKID.mjs";
7
+ export {
8
+ KeypairEncryptionAdapter
9
+ };
@@ -0,0 +1 @@
1
+ import{a as r}from"./chunk-TESUOXRJ.mjs";import"./chunk-PKHRAYFD.mjs";import"./chunk-EYF3263P.mjs";export{r as KeypairEncryptionAdapter};
@@ -0,0 +1,9 @@
1
+ import {
2
+ KeypairEncryptionAdapter
3
+ } from "./chunk-UDYXMII2.mjs";
4
+ import "./chunk-O37NHERH.mjs";
5
+ import "./chunk-BWLEAAFW.mjs";
6
+ import "./chunk-UJCSKKID.mjs";
7
+ export {
8
+ KeypairEncryptionAdapter
9
+ };
@@ -0,0 +1,8 @@
1
+ import {
2
+ KeypairEncryptionAdapter
3
+ } from "./chunk-Z6DUAI5B.mjs";
4
+ import "./chunk-O37NHERH.mjs";
5
+ import "./chunk-BWLEAAFW.mjs";
6
+ export {
7
+ KeypairEncryptionAdapter
8
+ };
@@ -0,0 +1 @@
1
+ import{a as o}from"./chunk-5RW7SGA7.mjs";import"./chunk-PKHRAYFD.mjs";import"./chunk-UBALMYIY.mjs";export{o as MnemonicEncryptionAdapter};
@@ -0,0 +1 @@
1
+ import{a as o}from"./chunk-5MZ7FPPY.mjs";import"./chunk-PKHRAYFD.mjs";import"./chunk-EYF3263P.mjs";export{o as MnemonicEncryptionAdapter};
@@ -0,0 +1,9 @@
1
+ import {
2
+ MnemonicEncryptionAdapter
3
+ } from "./chunk-PLAOCKSS.mjs";
4
+ import "./chunk-O37NHERH.mjs";
5
+ import "./chunk-BWLEAAFW.mjs";
6
+ import "./chunk-UJCSKKID.mjs";
7
+ export {
8
+ MnemonicEncryptionAdapter
9
+ };
@@ -0,0 +1,9 @@
1
+ import {
2
+ MnemonicEncryptionAdapter
3
+ } from "./chunk-L5QD6GHB.mjs";
4
+ import "./chunk-O37NHERH.mjs";
5
+ import "./chunk-TSZ37JDY.mjs";
6
+ import "./chunk-UJCSKKID.mjs";
7
+ export {
8
+ MnemonicEncryptionAdapter
9
+ };
@@ -0,0 +1,8 @@
1
+ import {
2
+ MnemonicEncryptionAdapter
3
+ } from "./chunk-OLGVVM5Y.mjs";
4
+ import "./chunk-O37NHERH.mjs";
5
+ import "./chunk-BWLEAAFW.mjs";
6
+ export {
7
+ MnemonicEncryptionAdapter
8
+ };
@@ -0,0 +1 @@
1
+ import{a as o}from"./chunk-5P4EW2YL.mjs";import"./chunk-PKHRAYFD.mjs";import"./chunk-EYF3263P.mjs";export{o as MnemonicEncryptionAdapter};
@@ -0,0 +1,9 @@
1
+ import {
2
+ MnemonicEncryptionAdapter
3
+ } from "./chunk-6IAQVMSM.mjs";
4
+ import "./chunk-O37NHERH.mjs";
5
+ import "./chunk-BWLEAAFW.mjs";
6
+ import "./chunk-UJCSKKID.mjs";
7
+ export {
8
+ MnemonicEncryptionAdapter
9
+ };
@@ -0,0 +1 @@
1
+ import{a as r,b as o,c as t}from"./chunk-TMLXCRJU.mjs";import"./chunk-PKHRAYFD.mjs";import"./chunk-EYF3263P.mjs";export{r as KDF_PRESETS,o as PasswordEncryptionAdapter,t as generatePqsPassword};
@@ -0,0 +1 @@
1
+ import{a as r,b as o,c as t}from"./chunk-U3MS6KMF.mjs";import"./chunk-PKHRAYFD.mjs";import"./chunk-UBALMYIY.mjs";export{r as KDF_PRESETS,o as PasswordEncryptionAdapter,t as generatePqsPassword};