Skip to content

Encrypt a file

Store private files on Filecoin while keeping control of who can read them. Client-side encryption lets storage providers hold and serve your data without access to its contents.

Your application encrypts each file before upload. Filecoin stores the encrypted bytes, and providers can prove they hold those bytes without decrypting them. A reader downloads the encrypted file and uses the appropriate key to restore its contents. This combines verifiable storage with confidentiality: providers can demonstrate that they are storing your data while the keys remain under your application’s control.

Keysmith manages key derivation, recovery, and sharing. It derives file keys from a signature made by the owner’s wallet. Public recovery metadata travels with each encrypted file, allowing the owner to regenerate its key using a compatible wallet rather than maintain a separate secret-key backup for every file. Your application can share access to individual files or groups of files without sharing the owner’s wallet credentials.

This guide uses Keysmith to derive keys, Filecoin Encryption Envelope to encrypt and decrypt files, and the Synapse SDK to upload and retrieve the encrypted data.

File contents are encrypted; public envelope metadata remains visible. Recovery requires a compatible owner wallet and access to the encrypted files.

Encrypted bytes may be publicly retrievable; reading their contents requires the decryption key.

This example uses Node.js 24 and a dedicated owner account on Calibration. You’ll need test FIL for transaction fees and test USDFC for storage; the SDK setup guide links to both faucets. An upload session key cannot derive the owner’s encryption keys.

Add @filoz/keysmith and filecoin-encryption-envelope alongside your existing SDK dependencies. The setup below includes the tested versions and a required security override.

Encrypt the file in your application before calling synapse.storage.upload(). Synapse uploads the bytes you provide.

The complete example includes SDK initialization. When adapting it to an existing application, reuse your configured Synapse client and owner account. Key derivation and encryption belong before the upload; decryption belongs after the download.

The example buffers a small file, its encrypted bytes, and the decrypted result in memory. For larger files, use the streaming encryption API.

Set up a test project and account

The setup commands use a POSIX shell, available on macOS, Linux, and Windows through WSL.

Create an ESM project and install the packages. The override selects a security-fixed version of Zod, a validation dependency:

mkdir filecoin-encryption
cd filecoin-encryption
npm init -y
npm pkg set type=module
npm pkg set overrides.zod=4.6.6
npm install @filoz/synapse-sdk@2.0.2 @filoz/keysmith@0.1.0 filecoin-encryption-envelope@0.1.0 viem@2.55.19
printf '.env\nnode_modules/\n' > .gitignore

Create the test account and save its private key locally:

node --input-type=module <<'JS'
import { writeFileSync } from 'node:fs'
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'

const key = generatePrivateKey()
writeFileSync('.env', `PRIVATE_KEY=${key}\n`, { flag: 'wx', mode: 0o600 })
console.log(privateKeyToAccount(key).address)
JS

Fund the printed address with test FIL and test USDFC using the faucets above. Keep .env local: anyone holding its private key can use the account and derive its encryption keys. The command refuses to overwrite an existing .env file.

Create a sample file large enough to meet the storage minimum:

node --input-type=module -e "import { writeFileSync } from 'node:fs'; writeFileSync('sample.txt', 'A sample file for Filecoin encryption.\n'.repeat(32))"

Save the following as encrypt-file.ts. Keysmith creates a keyspace for the encryption keys and uses the documents scope within it. A fresh salt derives a unique key for this file.

Keep the generated recovery labels in the envelope and record the keyspace identifier and commitment in the data set metadata. The example does both. No encryption keys are written on-chain.

encrypt-file.ts
import { function deepStrictEqual<T>(actual: unknown, expected: T, message?: Error | assert.AssertMessageFunction): asserts actual is T (+1 overload)
Tests for deep equality between the `actual` and `expected` parameters. "Deep" equality means that the enumerable "own" properties of child objects are recursively evaluated also by the following rules.
@sincev1.2.0
deepStrictEqual
} from 'node:assert'
import {
function readFile(path: PathLike | FileHandle, options?: ({
    encoding?: null | undefined;
    flag?: OpenMode | undefined;
} & EventEmitter<T extends EventMap<T> = any>.Abortable) | null): Promise<NonSharedBuffer> (+2 overloads)
Asynchronously reads the entire contents of a file. If no encoding is specified (using `options.encoding`), the data is returned as a `Buffer` object. Otherwise, the data will be a string. If `options` is a string, then it specifies the encoding. When the `path` is a directory, the behavior of `fsPromises.readFile()` is platform-specific. On macOS, Linux, and Windows, the promise will be rejected with an error. On FreeBSD, a representation of the directory's contents will be returned. An example of reading a `package.json` file located in the same directory of the running code: ```js import { readFile } from 'node:fs/promises'; try { const filePath = new URL('./package.json', import.meta.url); const contents = await readFile(filePath, { encoding: 'utf8' }); console.log(contents); } catch (err) { console.error(err.message); } ``` It is possible to abort an ongoing `readFile` using an `AbortSignal`. If a request is aborted the promise returned is rejected with an `AbortError`: ```js import { readFile } from 'node:fs/promises'; try { const controller = new AbortController(); const { signal } = controller; const promise = readFile(fileName, { signal }); // Abort the request before the promise settles. controller.abort(); await promise; } catch (err) { // When a request is aborted - err is an AbortError console.error(err); } ``` Aborting an ongoing request does not abort individual operating system requests but rather the internal buffering `fs.readFile` performs. Any specified `FileHandle` has to support reading.
@sincev10.0.0@parampath filename or `FileHandle`@returnFulfills with the contents of the file.
readFile
,
function writeFile(file: PathLike | FileHandle, data: string | NodeJS.ArrayBufferView | Iterable<string | NodeJS.ArrayBufferView> | AsyncIterable<string | NodeJS.ArrayBufferView>, options?: (ObjectEncodingOptions & {
    mode?: Mode | undefined;
    flag?: OpenMode | undefined;
    flush?: boolean | undefined;
} & EventEmitter<T extends EventMap<T> = any>.Abortable) | BufferEncoding | null): Promise<void>
Asynchronously writes data to a file, replacing the file if it already exists. `data` can be a string, a buffer, an [AsyncIterable](https://tc39.github.io/ecma262/#sec-asynciterable-interface), or an [Iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#The_iterable_protocol) object. The `encoding` option is ignored if `data` is a buffer. If `options` is a string, then it specifies the encoding. The `mode` option only affects the newly created file. See `fs.open()` for more details. Any specified `FileHandle` has to support writing. It is unsafe to use `fsPromises.writeFile()` multiple times on the same file without waiting for the promise to be settled. Similarly to `fsPromises.readFile` \- `fsPromises.writeFile` is a convenience method that performs multiple `write` calls internally to write the buffer passed to it. For performance sensitive code consider using `fs.createWriteStream()` or `filehandle.createWriteStream()`. It is possible to use an `AbortSignal` to cancel an `fsPromises.writeFile()`. Cancelation is "best effort", and some amount of data is likely still to be written. ```js import { writeFile } from 'node:fs/promises'; import { Buffer } from 'node:buffer'; try { const controller = new AbortController(); const { signal } = controller; const data = new Uint8Array(Buffer.from('Hello Node.js')); const promise = writeFile('message.txt', data, { signal }); // Abort the request before the promise settles. controller.abort(); await promise; } catch (err) { // When a request is aborted - err is an AbortError console.error(err); } ``` Aborting an ongoing request does not abort individual operating system requests but rather the internal buffering `fs.writeFile` performs.
@sincev10.0.0@paramfile filename or `FileHandle`@returnFulfills with `undefined` upon success.
writeFile
} from 'node:fs/promises'
import * as import KeysmithKeysmith from '@filoz/keysmith' import { class SynapseSynapse, const calibration: FilecoinChaincalibration } from '@filoz/synapse-sdk' import * as import feefee from 'filecoin-encryption-envelope' import { function privateKeyToAccount(privateKey: Hex, options?: PrivateKeyToAccountOptions): PrivateKeyAccount
@descriptionCreates an Account from a private key.@returnsA Private Key Account.
privateKeyToAccount
} from 'viem/accounts'
const const privateKey: string | undefinedprivateKey = var process: NodeJS.Processprocess.NodeJS.Process.env: NodeJS.ProcessEnv
The `process.env` property returns an object containing the user environment. See [`environ(7)`](http://man7.org/linux/man-pages/man7/environ.7.html). An example of this object looks like: ```json { "TERM": "xterm-256color", "SHELL": "/usr/local/bin/bash", "USER": "maciej", "PATH": "~/.bin/:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin", "PWD": "/Users/maciej", "EDITOR": "vim", "SHLVL": "1", "HOME": "/Users/maciej", "LOGNAME": "maciej", "_": "/usr/local/bin/node" } ``` It is possible to modify this object, but such modifications will not be reflected outside the Node.js process, or (unless explicitly requested) to other `Worker` threads. In other words, the following example would not work: ```bash node -e 'process.env.foo = "bar"' &#x26;&#x26; echo $foo ``` While the following will: ```js import { env } from 'node:process'; env.foo = 'bar'; console.log(env.foo); ``` Assigning a property on `process.env` will implicitly convert the value to a string. **This behavior is deprecated.** Future versions of Node.js may throw an error when the value is not a string, number, or boolean. ```js import { env } from 'node:process'; env.test = null; console.log(env.test); // => 'null' env.test = undefined; console.log(env.test); // => 'undefined' ``` Use `delete` to delete a property from `process.env`. ```js import { env } from 'node:process'; env.TEST = 1; delete env.TEST; console.log(env.TEST); // => undefined ``` On Windows operating systems, environment variables are case-insensitive. ```js import { env } from 'node:process'; env.TEST = 1; console.log(env.test); // => 1 ``` Unless explicitly specified when creating a `Worker` instance, each `Worker` thread has its own copy of `process.env`, based on its parent thread's `process.env`, or whatever was specified as the `env` option to the `Worker` constructor. Changes to `process.env` will not be visible across `Worker` threads, and only the main thread can make changes that are visible to the operating system or to native add-ons. On Windows, a copy of `process.env` on a `Worker` instance operates in a case-sensitive manner unlike the main thread.
@sincev0.1.27
env
.string | undefinedPRIVATE_KEY
if (!const privateKey: string | undefinedprivateKey || !/^0x[0-9a-fA-F]{64}$/.RegExp.test(string: string): boolean
Returns a Boolean value that indicates whether or not a pattern exists in a searched string.
@paramstring String on which to perform the search.
test
(const privateKey: stringprivateKey)) {
throw new
var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error
('Set PRIVATE_KEY to your dedicated test account private key')
} const
const account: {
    address: Address;
    nonceManager?: NonceManager | undefined;
    sign: (parameters: {
        hash: Hash;
    }) => Promise<Hex>;
    signAuthorization: (parameters: AuthorizationRequest) => Promise<SignAuthorizationReturnType>;
    signMessage: ({ message }: {
        message: SignableMessage;
    }) => Promise<Hex>;
    signTransaction: <serializer extends SerializeTransactionFn<TransactionSerializable> = SerializeTransactionFn<TransactionSerializable>, transaction extends Parameters<serializer>[0] = Parameters<serializer>[0]>(transaction: transaction, options?: {
        serializer?: serializer | undefined;
    } | undefined) => Promise<Hex>;
    signTypedData: <const typedData extends TypedData | Record<string, unknown>, primaryType extends keyof typedData | "EIP712Domain" = keyof typedData>(parameters: TypedDataDefinition<typedData, primaryType>) => Promise<Hex>;
    publicKey: Hex;
    source: "privateKey";
    type: "local";
}
account
= function privateKeyToAccount(privateKey: Hex, options?: PrivateKeyToAccountOptions): PrivateKeyAccount
@descriptionCreates an Account from a private key.@returnsA Private Key Account.
privateKeyToAccount
(const privateKey: stringprivateKey as `0x${string}`)
const const synapse: Synapsesynapse = class SynapseSynapse.Synapse.create(options: SynapseOptions): Synapsecreate({ SynapseOptions.account: `0x${string}` | Accountaccount, SynapseOptions.chain?: FilecoinChain | undefinedchain: const calibration: FilecoinChaincalibration, SynapseOptions.source: string | nullsource: 'encryption-quickstart', }) const const original: Uint8Array<ArrayBuffer>original = new
var Uint8Array: Uint8ArrayConstructor
new (elements: Iterable<number>) => Uint8Array<ArrayBuffer> (+6 overloads)
Uint8Array
(await
function readFile(path: PathLike | FileHandle, options?: ({
    encoding?: null | undefined;
    flag?: OpenMode | undefined;
} & EventEmitter<T extends EventMap<T> = any>.Abortable) | null): Promise<NonSharedBuffer> (+2 overloads)
Asynchronously reads the entire contents of a file. If no encoding is specified (using `options.encoding`), the data is returned as a `Buffer` object. Otherwise, the data will be a string. If `options` is a string, then it specifies the encoding. When the `path` is a directory, the behavior of `fsPromises.readFile()` is platform-specific. On macOS, Linux, and Windows, the promise will be rejected with an error. On FreeBSD, a representation of the directory's contents will be returned. An example of reading a `package.json` file located in the same directory of the running code: ```js import { readFile } from 'node:fs/promises'; try { const filePath = new URL('./package.json', import.meta.url); const contents = await readFile(filePath, { encoding: 'utf8' }); console.log(contents); } catch (err) { console.error(err.message); } ``` It is possible to abort an ongoing `readFile` using an `AbortSignal`. If a request is aborted the promise returned is rejected with an `AbortError`: ```js import { readFile } from 'node:fs/promises'; try { const controller = new AbortController(); const { signal } = controller; const promise = readFile(fileName, { signal }); // Abort the request before the promise settles. controller.abort(); await promise; } catch (err) { // When a request is aborted - err is an AbortError console.error(err); } ``` Aborting an ongoing request does not abort individual operating system requests but rather the internal buffering `fs.readFile` performs. Any specified `FileHandle` has to support reading.
@sincev10.0.0@parampath filename or `FileHandle`@returnFulfills with the contents of the file.
readFile
('sample.txt'))
// 1. Derive the encryption key and public envelope metadata. const
const ref: {
    owner: `0x${string}`;
    keyspace: `0x${string}`;
}
ref
= { owner: `0x${string}`owner:
const account: {
    address: Address;
    nonceManager?: NonceManager | undefined;
    sign: (parameters: {
        hash: Hash;
    }) => Promise<Hex>;
    signAuthorization: (parameters: AuthorizationRequest) => Promise<SignAuthorizationReturnType>;
    signMessage: ({ message }: {
        message: SignableMessage;
    }) => Promise<Hex>;
    signTransaction: <serializer extends SerializeTransactionFn<TransactionSerializable> = SerializeTransactionFn<TransactionSerializable>, transaction extends Parameters<serializer>[0] = Parameters<serializer>[0]>(transaction: transaction, options?: {
        serializer?: serializer | undefined;
    } | undefined) => Promise<Hex>;
    signTypedData: <const typedData extends TypedData | Record<string, unknown>, primaryType extends keyof typedData | "EIP712Domain" = keyof typedData>(parameters: TypedDataDefinition<typedData, primaryType>) => Promise<Hex>;
    publicKey: Hex;
    source: "privateKey";
    type: "local";
}
account
.address: `0x${string}`address, keyspace: `0x${string}`keyspace: import KeysmithKeysmith.
function newKeyspace(): Hex
export newKeyspace
newKeyspace
() }
const { KeyspaceKeys.kk: Uint8Array<ArrayBufferLike>kk: const keyspaceKey: Uint8Array<ArrayBufferLike>keyspaceKey, const commitment: stringcommitment, const descriptor: Keysmith.GrantDescriptordescriptor } = await import KeysmithKeysmith.
function keyspaceKeys(signer: Keysmith.TypedDataSigner, ref: Keysmith.KeyspaceRef, options?: Keysmith.KeyspaceKeysOptions): Promise<Keysmith.KeyspaceKeys>
export keyspaceKeys
keyspaceKeys
(
const account: {
    address: Address;
    nonceManager?: NonceManager | undefined;
    sign: (parameters: {
        hash: Hash;
    }) => Promise<Hex>;
    signAuthorization: (parameters: AuthorizationRequest) => Promise<SignAuthorizationReturnType>;
    signMessage: ({ message }: {
        message: SignableMessage;
    }) => Promise<Hex>;
    signTransaction: <serializer extends SerializeTransactionFn<TransactionSerializable> = SerializeTransactionFn<TransactionSerializable>, transaction extends Parameters<serializer>[0] = Parameters<serializer>[0]>(transaction: transaction, options?: {
        serializer?: serializer | undefined;
    } | undefined) => Promise<Hex>;
    signTypedData: <const typedData extends TypedData | Record<string, unknown>, primaryType extends keyof typedData | "EIP712Domain" = keyof typedData>(parameters: TypedDataDefinition<typedData, primaryType>) => Promise<Hex>;
    publicKey: Hex;
    source: "privateKey";
    type: "local";
}
account
,
const ref: {
    owner: `0x${string}`;
    keyspace: `0x${string}`;
}
ref
)
const const target: Keysmith.WriteTargettarget = import KeysmithKeysmith.
function writeTarget(grant: Keysmith.GrantDescriptor, nodeKey: Uint8Array, scope?: string): Keysmith.WriteTarget
export writeTarget
writeTarget
(const descriptor: Keysmith.GrantDescriptordescriptor, const keyspaceKey: Uint8Array<ArrayBufferLike>keyspaceKey, 'documents')
const const salt: `0x${string}`salt = import KeysmithKeysmith.
function newSalt(): Hex
export newSalt
newSalt
()
const const key: Uint8Array<ArrayBufferLike>key = import KeysmithKeysmith.
function pieceKey(node: Uint8Array, salt: Hex): Uint8Array
export pieceKey
pieceKey
(const target: Keysmith.WriteTargettarget.WriteTarget.key: Uint8Array<ArrayBufferLike>key, const salt: `0x${string}`salt)
const const appMetadata: Keysmith.PieceMetadataappMetadata = import KeysmithKeysmith.
function pieceMetadata(ref: Keysmith.KeyspaceRef, options: {
    salt: Hex;
    scope?: string;
}): Keysmith.PieceMetadata
export pieceMetadata
pieceMetadata
(const target: Keysmith.WriteTargettarget.WriteTarget.ref: Keysmith.KeyspaceRefref, { salt: `0x${string}`salt, scope?: string | undefinedscope: const target: Keysmith.WriteTargettarget.WriteTarget.scope?: string | undefinedscope })
// 2. Finish encryption before preparing storage or uploading bytes. const const encrypted: Uint8Array<ArrayBufferLike>encrypted = await function through(bytes: Uint8Array, transform: ReadableWritablePair<Uint8Array, Uint8Array>): Promise<Uint8Array>through(const original: Uint8Array<ArrayBuffer>original, import feefee.
function encrypt(options: fee.ChunkedEncryptOptions): ReadableWritablePair<Uint8Array, Uint8Array>
export encrypt
encrypt
({
ChunkedEncryptOptions.cek: Uint8Array<ArrayBufferLike>cek: const key: Uint8Array<ArrayBufferLike>key, ChunkedEncryptOptions.appMetadata?: fee.AppMetadata | undefinedappMetadata, ChunkedEncryptOptions.contentLength?: number | undefinedcontentLength: const original: Uint8Array<ArrayBuffer>original.Uint8Array<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>.byteLength: number
The length in bytes of the array.
byteLength
,
})) const
const metadata: {
    "foc/ks": `0x${string}`;
    "foc/kc": string;
}
metadata
= {
[import KeysmithKeysmith.
const KEYSPACE_ID_KEY: "foc/ks"
export KEYSPACE_ID_KEY
KEYSPACE_ID_KEY
]:
const ref: {
    owner: `0x${string}`;
    keyspace: `0x${string}`;
}
ref
.keyspace: `0x${string}`keyspace,
[import KeysmithKeysmith.
const COMMITMENT_KEY: "foc/kc"
export COMMITMENT_KEY
COMMITMENT_KEY
]: const commitment: stringcommitment,
} const const contexts: StorageContext[]contexts = await const synapse: Synapsesynapse.Synapse.storage: StorageManagerstorage.StorageManager.createContexts(options?: CreateContextsOptions): Promise<StorageContext[]>createContexts({ BaseContextOptions.metadata?: Record<string, string> | undefinedmetadata }) const const preparation: PrepareResultpreparation = await const synapse: Synapsesynapse.Synapse.storage: StorageManagerstorage.StorageManager.prepare(options: PrepareOptions): Promise<PrepareResult>prepare({ PrepareOptions.context?: StorageContext | StorageContext[] | undefinedcontext: const contexts: StorageContext[]contexts, PrepareOptions.pieceSizes: readonly bigint[]pieceSizes: [
var BigInt: BigIntConstructor
(value: bigint | boolean | number | string) => bigint
BigInt
(const encrypted: Uint8Array<ArrayBufferLike>encrypted.Uint8Array<ArrayBufferLike>.byteLength: number
The length in bytes of the array.
byteLength
)],
}) if (const preparation: PrepareResultpreparation.
PrepareResult.transaction: {
    depositAmount: bigint;
    includesApproval: boolean;
    execute: (options?: {
        onHash?: (hash: Hash) => void;
    }) => Promise<{
        hash: Hash;
        receipt: import("viem").TransactionReceipt | null;
    }>;
} | null
transaction
) await const preparation: PrepareResultpreparation.
PrepareResult.transaction: {
    depositAmount: bigint;
    includesApproval: boolean;
    execute: (options?: {
        onHash?: (hash: Hash) => void;
    }) => Promise<{
        hash: Hash;
        receipt: import("viem").TransactionReceipt | null;
    }>;
}
transaction
.
execute: (options?: {
    onHash?: (hash: Hash) => void;
}) => Promise<{
    hash: Hash;
    receipt: import("viem").TransactionReceipt | null;
}>
execute
()
// 3. Upload with the default two copies and check the result. const const result: UploadResultresult = await const synapse: Synapsesynapse.Synapse.storage: StorageManagerstorage.StorageManager.upload(data: UploadPieceStreamingData, options?: StorageManagerUploadOptions): Promise<UploadResult>upload(const encrypted: Uint8Array<ArrayBufferLike>encrypted, { BaseContextOptions.metadata?: Record<string, string> | undefinedmetadata }) var console: Consoleconsole.Console.log(...data: any[]): void
The **`console.log()`** static method outputs a message to the console. [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)
log
('PieceCID:', const result: UploadResultresult.UploadResult.pieceCid: PieceCIDpieceCid.CID<unknown, 85, 4113, 1>.toString(base?: MultibaseEncoder<string>): string
Returns a string representation of an object.
toString
())
var console: Consoleconsole.Console.log(...data: any[]): void
The **`console.log()`** static method outputs a message to the console. [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)
log
('Data sets:', const result: UploadResultresult.UploadResult.copies: CopyResult[]copies.Array<CopyResult>.map<string>(callbackfn: (value: CopyResult, index: number, array: CopyResult[]) => string, thisArg?: any): string[]
Calls a defined callback function on each element of an array, and returns an array that contains the results.
@paramcallbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.@paramthisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
map
(copy: CopyResultcopy => copy: CopyResultcopy.CopyResult.dataSetId: bigintdataSetId.BigInt.toString(radix?: number): string
Returns a string representation of an object.
@paramradix Specifies a radix for converting numeric values to strings.
toString
()))
if (!const result: UploadResultresult.UploadResult.complete: booleancomplete) { throw new
var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error
(`Incomplete upload: stored ${const result: UploadResultresult.UploadResult.copies: CopyResult[]copies.Array<CopyResult>.length: number
Gets or sets the length of the array. This is a number one higher than the highest index in the array.
length
} of ${const result: UploadResultresult.UploadResult.requestedCopies: numberrequestedCopies} requested copies`)
} // 4. Download, fully decrypt, and compare before saving the restored file. const const downloaded: Uint8Array<ArrayBufferLike>downloaded = await const synapse: Synapsesynapse.Synapse.storage: StorageManagerstorage.StorageManager.download(options: StorageManagerDownloadOptions): Promise<Uint8Array>download({ pieceCid: string | PieceCIDpieceCid: const result: UploadResultresult.UploadResult.pieceCid: PieceCIDpieceCid }) const const restored: Uint8Array<ArrayBufferLike>restored = await function through(bytes: Uint8Array, transform: ReadableWritablePair<Uint8Array, Uint8Array>): Promise<Uint8Array>through(const downloaded: Uint8Array<ArrayBufferLike>downloaded, import feefee.
function decrypt(key: Uint8Array | fee.KeyResolver): ReadableWritablePair<Uint8Array, Uint8Array>
export decrypt
decrypt
(import KeysmithKeysmith.
function keyResolver(node: Uint8Array, holding?: Keysmith.Holding | Keysmith.GrantDescriptor): (info: {
    appMetadata?: Readonly<Record<string, unknown>>;
}) => Uint8Array
export keyResolver
keyResolver
(const keyspaceKey: Uint8Array<ArrayBufferLike>keyspaceKey, const descriptor: Keysmith.GrantDescriptordescriptor)))
deepStrictEqual<Uint8Array<ArrayBuffer>>(actual: unknown, expected: Uint8Array<ArrayBuffer>, message?: Error | assert.AssertMessageFunction): asserts actual is Uint8Array<ArrayBuffer> (+1 overload)
Tests for deep equality between the `actual` and `expected` parameters. "Deep" equality means that the enumerable "own" properties of child objects are recursively evaluated also by the following rules.
@sincev1.2.0
deepStrictEqual
(const restored: Uint8Array<ArrayBufferLike>restored, const original: Uint8Array<ArrayBuffer>original)
await
function writeFile(file: PathLike | FileHandle, data: string | NodeJS.ArrayBufferView | Iterable<string | NodeJS.ArrayBufferView> | AsyncIterable<string | NodeJS.ArrayBufferView>, options?: (ObjectEncodingOptions & {
    mode?: Mode | undefined;
    flag?: OpenMode | undefined;
    flush?: boolean | undefined;
} & EventEmitter<T extends EventMap<T> = any>.Abortable) | BufferEncoding | null): Promise<void>
Asynchronously writes data to a file, replacing the file if it already exists. `data` can be a string, a buffer, an [AsyncIterable](https://tc39.github.io/ecma262/#sec-asynciterable-interface), or an [Iterable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#The_iterable_protocol) object. The `encoding` option is ignored if `data` is a buffer. If `options` is a string, then it specifies the encoding. The `mode` option only affects the newly created file. See `fs.open()` for more details. Any specified `FileHandle` has to support writing. It is unsafe to use `fsPromises.writeFile()` multiple times on the same file without waiting for the promise to be settled. Similarly to `fsPromises.readFile` \- `fsPromises.writeFile` is a convenience method that performs multiple `write` calls internally to write the buffer passed to it. For performance sensitive code consider using `fs.createWriteStream()` or `filehandle.createWriteStream()`. It is possible to use an `AbortSignal` to cancel an `fsPromises.writeFile()`. Cancelation is "best effort", and some amount of data is likely still to be written. ```js import { writeFile } from 'node:fs/promises'; import { Buffer } from 'node:buffer'; try { const controller = new AbortController(); const { signal } = controller; const data = new Uint8Array(Buffer.from('Hello Node.js')); const promise = writeFile('message.txt', data, { signal }); // Abort the request before the promise settles. controller.abort(); await promise; } catch (err) { // When a request is aborted - err is an AbortError console.error(err); } ``` Aborting an ongoing request does not abort individual operating system requests but rather the internal buffering `fs.writeFile` performs.
@sincev10.0.0@paramfile filename or `FileHandle`@returnFulfills with `undefined` upon success.
writeFile
('restored.txt', const restored: Uint8Array<ArrayBuffer>restored)
var console: Consoleconsole.Console.log(...data: any[]): void
The **`console.log()`** static method outputs a message to the console. [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)
log
(`Verified: ${const restored: Uint8Array<ArrayBuffer>restored.Uint8Array<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>.byteLength: number
The length in bytes of the array.
byteLength
} bytes match the original file`)
async function function through(bytes: Uint8Array, transform: ReadableWritablePair<Uint8Array, Uint8Array>): Promise<Uint8Array>through( bytes: Uint8Array<ArrayBufferLike>bytes: interface Uint8Array<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>
A typed array of 8-bit unsigned integer values. The contents are initialized to 0. If the requested number of bytes could not be allocated an exception is raised.
Uint8Array
,
transform: ReadableWritablePair<Uint8Array<ArrayBufferLike>, Uint8Array<ArrayBufferLike>>transform: interface ReadableWritablePair<R = any, W = any>ReadableWritablePair<interface Uint8Array<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>
A typed array of 8-bit unsigned integer values. The contents are initialized to 0. If the requested number of bytes could not be allocated an exception is raised.
Uint8Array
, interface Uint8Array<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>
A typed array of 8-bit unsigned integer values. The contents are initialized to 0. If the requested number of bytes could not be allocated an exception is raised.
Uint8Array
>,
): interface Promise<T>
Represents the completion of an asynchronous operation
Promise
<interface Uint8Array<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>
A typed array of 8-bit unsigned integer values. The contents are initialized to 0. If the requested number of bytes could not be allocated an exception is raised.
Uint8Array
> {
const const source: ReadableStream<Uint8Array<ArrayBufferLike>>source = new var ReadableStream: new <Uint8Array<ArrayBufferLike>>(underlyingSource: UnderlyingDefaultSource<Uint8Array<ArrayBufferLike>>, strategy?: QueuingStrategy<Uint8Array<ArrayBufferLike>> | undefined) => ReadableStream<Uint8Array<ArrayBufferLike>> (+2 overloads)ReadableStream<interface Uint8Array<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>
A typed array of 8-bit unsigned integer values. The contents are initialized to 0. If the requested number of bytes could not be allocated an exception is raised.
Uint8Array
>({
UnderlyingDefaultSource<Uint8Array<ArrayBufferLike>>.start?: ((controller: ReadableStreamDefaultController<Uint8Array<ArrayBufferLike>>) => any) | undefinedstart(controller: ReadableStreamDefaultController<Uint8Array<ArrayBufferLike>>controller) { controller: ReadableStreamDefaultController<Uint8Array<ArrayBufferLike>>controller.ReadableStreamDefaultController<Uint8Array<ArrayBufferLike>>.enqueue(chunk: Uint8Array<ArrayBufferLike>): void
The **`enqueue()`** method of the ReadableStreamDefaultController interface enqueues a given chunk in the associated stream. [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/enqueue)
enqueue
(bytes: Uint8Array<ArrayBufferLike>bytes)
controller: ReadableStreamDefaultController<Uint8Array<ArrayBufferLike>>controller.ReadableStreamDefaultController<Uint8Array<ArrayBufferLike>>.close(): void
The **`close()`** method of the ReadableStreamDefaultController interface closes the associated stream. [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/close)
close
()
}, }) const const chunks: Uint8Array<ArrayBufferLike>[]chunks: interface Uint8Array<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>
A typed array of 8-bit unsigned integer values. The contents are initialized to 0. If the requested number of bytes could not be allocated an exception is raised.
Uint8Array
[] = []
let let length: numberlength = 0 const const reader: ReadableStreamDefaultReader<Uint8Array<ArrayBufferLike>>reader = const source: ReadableStream<Uint8Array<ArrayBufferLike>>source.ReadableStream<Uint8Array<ArrayBufferLike>>.pipeThrough<Uint8Array<ArrayBufferLike>>(transform: ReadableWritablePair<Uint8Array<ArrayBufferLike>, Uint8Array<ArrayBufferLike>>, options?: StreamPipeOptions): ReadableStream<Uint8Array<ArrayBufferLike>>
The **`pipeThrough()`** method of the ReadableStream interface provides a chainable way of piping the current stream through a transform stream or any other writable/readable pair. [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/pipeThrough)
pipeThrough
(transform: ReadableWritablePair<Uint8Array<ArrayBufferLike>, Uint8Array<ArrayBufferLike>>transform).ReadableStream<Uint8Array<ArrayBufferLike>>.getReader(): ReadableStreamDefaultReader<Uint8Array<ArrayBufferLike>> (+2 overloads)
The **`getReader()`** method of the ReadableStream interface creates a reader and locks the stream to it. While the stream is locked, no other reader can be acquired until this one is released. [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/getReader)
getReader
()
try { while (true) { const { const done: booleandone, const value: Uint8Array<ArrayBufferLike> | undefinedvalue } = await const reader: ReadableStreamDefaultReader<Uint8Array<ArrayBufferLike>>reader.ReadableStreamDefaultReader<Uint8Array<ArrayBufferLike>>.read(): Promise<ReadableStreamReadResult<Uint8Array<ArrayBufferLike>>>
The **`read()`** method of the ReadableStreamDefaultReader interface returns a Promise providing access to the next chunk in the stream's internal queue. [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader/read)
read
()
if (const done: booleandone) break const chunks: Uint8Array<ArrayBufferLike>[]chunks.Array<Uint8Array<ArrayBufferLike>>.push(...items: Uint8Array<ArrayBufferLike>[]): number
Appends new elements to the end of an array, and returns the new length of the array.
@paramitems New elements to add to the array.
push
(const value: Uint8Array<ArrayBufferLike>value)
let length: numberlength += const value: Uint8Array<ArrayBufferLike>value.Uint8Array<ArrayBufferLike>.byteLength: number
The length in bytes of the array.
byteLength
} } finally { const reader: ReadableStreamDefaultReader<Uint8Array<ArrayBufferLike>>reader.ReadableStreamDefaultReader<Uint8Array<ArrayBufferLike>>.releaseLock(): void
The **`releaseLock()`** method of the ReadableStreamDefaultReader interface releases the reader's lock on the stream. [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader/releaseLock)
releaseLock
()
} const const output: Uint8Array<ArrayBuffer>output = new
var Uint8Array: Uint8ArrayConstructor
new (length: number) => Uint8Array<ArrayBuffer> (+6 overloads)
Uint8Array
(let length: numberlength)
let let offset: numberoffset = 0 for (const const chunk: Uint8Array<ArrayBufferLike>chunk of const chunks: Uint8Array<ArrayBufferLike>[]chunks) { const output: Uint8Array<ArrayBuffer>output.Uint8Array<ArrayBuffer>.set(array: ArrayLike<number>, offset?: number): void
Sets a value or an array of values.
@paramarray A typed or untyped array of values to set.@paramoffset The index in the current array at which the values are to be written.
set
(const chunk: Uint8Array<ArrayBufferLike>chunk, let offset: numberoffset)
let offset: numberoffset += const chunk: Uint8Array<ArrayBufferLike>chunk.Uint8Array<ArrayBufferLike>.byteLength: number
The length in bytes of the array.
byteLength
} return const output: Uint8Array<ArrayBuffer>output }

Storage preparation uses the encrypted size, including the envelope and authentication overhead. It may submit a funding and approval transaction from the test account. Uploading creates paid storage copies on Calibration.

The helper consumes the entire stream before returning bytes. If encryption or decryption fails, it throws; an incomplete restored file is never saved.

node --env-file=.env encrypt-file.ts

Success prints a PieceCID, the replica data set IDs, and a verification message. restored.txt matches sample.txt byte for byte. Keep the PieceCID and data set IDs to retrieve this upload later.

Each run creates a new keyspace and new data sets. In an application, read and reuse existing data set metadata when adding files to that keyspace. Generate a fresh salt and piece key for every encryption attempt.

  • Funding or approval fails: check the test account’s FIL and USDFC balances using the payment guide.
  • Only some copies succeed: those copies are already stored and billed. Inspect the upload result before retrying.
  • Decryption fails: check that the key and envelope belong to the same keyspace. Modified or truncated encrypted data also fails authentication; do not use partial output.

For recovery, sharing, and storage permissions, see Keys and sharing.