Docs/Protocol Spec
Reference · v0.1

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:

04
MNEMOS Memory Protocol
Classification, enrichment, semantic search, retrieval — the intelligence layer you own.
03
AES-256-GCM Encryption
Client-side key derivation from wallet signature. Server only ever receives ciphertext.
02
Neon Postgres
Serverless SQL storage. Stores encrypted blobs, AI titles, and metadata.
01
Base
Identity, on-chain receipts, $MNM token. EVM-compatible L2.

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

typescript
// 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

typescript
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

typescript
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);
}
TIP
AES-256-GCM is authenticated encryption — any tampering with the ciphertext causes decryption to throw. This gives both confidentiality and integrity guarantees.

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.

sql
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

text
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

text
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      │                              │
TIP
The server never touches plaintext. Even a full database breach exposes only ciphertext — useless without the wallet private key.

AI enrichment

Memories longer than 20 characters trigger an optional enrichment pass via POST /api/enrich. The enrichment pipeline runs three operations:

01
Title generation
Claude Haiku generates a concise 5-10 word title stored unencrypted alongside ciphertext — visible in vault without decryption.
02
Auto-tagging
Claude extracts entity and topic tags from the memory content.
03
Voyage embedding
512-dimensional vector embedding stored in the memory row for semantic similarity search.
WARNING
Enrichment sends the plaintext to the enrichment API. If you have sensitive memories that should not be processed by an external AI model, disable enrichment in vault settings or skip the enrichment step.

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.

WARNING
Base receipt anchoring ships with Phase 2. Memories written today will be backfilled with receipts when the registry contract deploys on Base Sepolia.