Skip to main content

Storage

A peer persists two things: its contacts and its outbox (messages not yet acknowledged). Both go through a Store, a minimal async key-value interface. The identity's secret is never stored.

StoreImportWhere data lives
memoryStore() (default)@vaultys/peer-sdkMemory: lost on restart
indexedDBStore(name?)@vaultys/peer-sdkThe browser's IndexedDB (default database "vaultys-peer")
fileStore(dir)@vaultys/peer-sdk/nodeOne file per key in a directory
import { createPeer, indexedDBStore } from "@vaultys/peer-sdk";

const peer = await createPeer({ identity, transport, store: indexedDBStore("my-app") });

Keys are namespaced by the peer's DID (<did>/contacts/…, <did>/outbox/…), so several identities can share one store.

Writing your own​

interface Store {
get(key: string): Promise<Uint8Array | undefined>;
set(key: string, value: Uint8Array): Promise<void>;
delete(key: string): Promise<void>;
keys(prefix: string): Promise<string[]>; // all keys starting with prefix
}

Values are opaque bytes. For example, a store over a remote key-value service:

const remoteStore = (api: KvClient): Store => ({
get: (key) => api.get(key),
set: (key, value) => api.put(key, value),
delete: (key) => api.delete(key),
keys: (prefix) => api.list({ prefix }),
});

Encrypting at rest​

Contacts and queued messages are stored as they are. To encrypt them, wrap any store:

import { crypto as vcrypto } from "@vaultys/id";

function encryptedStore(inner: Store, key: Uint8Array): Store {
const NONCE = 24;
return {
async get(k) {
const sealed = await inner.get(k);
if (!sealed) return undefined;
const plain = vcrypto.secretbox.open(sealed.subarray(NONCE), sealed.subarray(0, NONCE), key);
if (!plain) throw new Error(`cannot decrypt ${k}`);
return plain;
},
async set(k, value) {
const nonce = vcrypto.randomBytes(NONCE);
const box = vcrypto.secretbox(value, nonce, key);
const sealed = new Uint8Array(NONCE + box.length);
sealed.set(nonce);
sealed.set(box, NONCE);
await inner.set(k, sealed);
},
delete: (k) => inner.delete(k),
keys: (prefix) => inner.keys(prefix),
};
}

Derive key (32 bytes) from something the user controls, such as a passkey's PRF output or a passphrase, never from data in the same store.