Protocol Spec
The technical architecture of the MNEMOS L6 memory layer — encryption, storage, and on-chain anchoring.
Overview
MNEMOS implements an L6 memory protocol on top of Base. The stack has four independently verifiable layers:
Encryption layer
All vault memories are encrypted client-side using the browser's native WebCrypto API before any data is transmitted. No external encryption service, no network calls.
Key derivation
// lib/crypto.ts — actual implementation
export const SIGN_MESSAGE =
"MNEMOS_ENCRYPTION_KEY_v1\n\nSigning this message derives your personal " +
"encryption key.\nIt does not create a transaction or cost any gas.";
export async function deriveKeyFromWallet(
signMessage: (opts: { message: string }) => Promise<string>
): Promise<CryptoKey> {
// 1. Deterministic ECDSA signature
const signature = await signMessage({ message: SIGN_MESSAGE });
// 2. Hex signature → bytes
const hex = signature.startsWith("0x") ? signature.slice(2) : signature;
const sigBytes = new Uint8Array(hex.match(/.{2}/g)!.map(b => parseInt(b, 16)));
// 3. SHA-256(signature) → 32-byte key material
const keyMaterial = await crypto.subtle.digest("SHA-256", sigBytes);
// 4. Import as non-extractable AES-256-GCM key
return crypto.subtle.importKey(
"raw", keyMaterial,
{ name: "AES-GCM", length: 256 },
false, // non-extractable
["encrypt", "decrypt"]
);
}Encryption
export async function encryptContent(plaintext: string, key: CryptoKey): Promise<string> {
const iv = crypto.getRandomValues(new Uint8Array(12)); // 96-bit random IV
const encoded = new TextEncoder().encode(plaintext);
const ciphertextBuf = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, encoded);
return `${toB64url(iv.buffer)}.${toB64url(ciphertextBuf)}`;
// Output: base64url(iv[12]) + "." + base64url(ciphertext)
}Decryption
export async function decryptContent(ciphertext: string, key: CryptoKey): Promise<string> {
const dot = ciphertext.indexOf(".");
if (dot === -1) throw new Error("Invalid ciphertext: missing IV separator");
const iv = fromB64url(ciphertext.slice(0, dot));
const ct = fromB64url(ciphertext.slice(dot + 1));
const plaintextBuf = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, ct);
return new TextDecoder().decode(plaintextBuf);
}Storage layer — Neon Postgres
MNEMOS uses Neon serverless Postgres as the primary storage backend. The schema stores encrypted blobs alongside unencrypted metadata used for display and filtering.
CREATE TABLE memories (
id TEXT PRIMARY KEY, -- "mem_" + nanoid(8)
wallet_address TEXT NOT NULL, -- owning wallet (checksummed)
ciphertext TEXT, -- AES-256-GCM blob (null for api-plain)
note TEXT, -- plaintext for api-plain source
category TEXT NOT NULL DEFAULT 'context',
tags TEXT[] NOT NULL DEFAULT '{}',
source TEXT NOT NULL DEFAULT 'vault',
title TEXT, -- AI-generated, stored unencrypted
embedding vector(512), -- Voyage AI embedding for semantic search
vault_id TEXT, -- shared vault FK (null = personal)
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
deleted_at TIMESTAMPTZ -- soft delete
);
CREATE INDEX idx_memories_wallet ON memories(wallet_address);
CREATE INDEX idx_memories_category ON memories(wallet_address, category);
CREATE INDEX idx_memories_tags ON memories USING GIN(tags);
CREATE INDEX idx_memories_embedding ON memories USING ivfflat(embedding vector_cosine_ops);Write flow
Browser API Server Neon DB
│ │ │
│ 1. Derive AES key from sig │ │
│ 2. Encrypt plaintext → blob │ │
│ 3. Call AI enrichment (optional) │ │
│ 4. POST /api/memory │ │
│ { ciphertext, wallet, ... │ │
│ signature, timestamp } │ │
│ ─────────────────────────────────▶│ │
│ │ 5. Verify wallet signature │
│ │ 6. INSERT into memories │
│ │ ─────────────────────────────▶│
│ 7. Return { id, createdAt } │ 200 OK │
│ ◀─────────────────────────────── │ │Read flow
Browser API Server Neon DB │ │ │ │ 1. GET /api/memory │ │ │ Authorization: Bearer key │ │ │ ─────────────────────────────────▶│ │ │ │ 2. Validate API key │ │ │ 3. SELECT ciphertext, meta │ │ │ ─────────────────────────────▶│ │ 4. Return encrypted rows │ rows returned │ │ ◀─────────────────────────────── │ │ │ │ │ │ 5. Derive AES key from sig │ │ │ 6. Decrypt each row locally │ │ │ 7. Render plaintext in vault │ │
AI enrichment
Memories longer than 20 characters trigger an optional enrichment pass via POST /api/enrich. The enrichment pipeline runs three operations:
Base receipt anchoring
Phase 2 of the protocol introduces on-chain write receipts. A SHA-256 commitment of the ciphertext is submitted to the MNEMOS registry contract on Base, providing a tamper-evident timestamp and ownership proof without revealing content.