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.
| Store | Import | Where data lives |
|---|---|---|
memoryStore() (default) | @vaultys/peer-sdk | Memory: lost on restart |
indexedDBStore(name?) | @vaultys/peer-sdk | The browser's IndexedDB (default database "vaultys-peer") |
fileStore(dir) | @vaultys/peer-sdk/node | One 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.