Phase 1 ยท local end-to-end encrypted round trip working

Your repos in the cloud.
Encrypted to your wallet.

A local MCP server & CLI that pushes your repos and project files to cheap object storage โ€” with end-to-end encryption rooted in your Solana wallet. The hosted backend only ever sees ciphertext. Solana handles auth, billing, and tamper-evidence; never your keys.

AES-256-GCM ยท HKDF-SHA256 ยท ed25519 ยท content-addressed chunks ยท Merkle-verified restore

vault โ€” push / pull
$ vault init my-repo
Created vault "my-repo"
  vaultId: feaa0db244e9d07b4f401c92a329e1f9
  wallet:  BJc5hVBMxFFuqdzmwUEeTtWnGURq2UXB6wD8EV2bHVDR

$ vault push <vaultId> ./my-repo -m "first snapshot"
Pushed snapshot snap-0001-f4e5e181107c
  merkleRoot: f4e5e181107c26a4d6ac990306f9595aโ€ฆ
  files: 6  chunks: 6 (new: 6)  uploaded: 4.1 KB ciphertext

$ vault pull <vaultId> ./restore
โœ“ Restored snap-0001 โ€” every chunk hash & Merkle root verified
โœ“ byte-identical
How it works

Envelope encryption, anchored to a signature

No invented crypto โ€” just established primitives composed correctly. Your wallet signature derives the key; a random per-vault key encrypts the data; the server gets nothing it can open.

1

Sign once, derive a key

Your wallet signs a fixed canonical message. KEK = HKDF-SHA256(signature, salt=vaultId, info="kek") โ€” recomputable on any device that holds the wallet, on no device that doesn't.

2

Wrap a random data key

A random per-vault DEK encrypts your files. It's stored only as wrappedDEK = AES-256-GCM(KEK, DEK). The server may hold the wrapped DEK โ€” it's useless without the wallet.

3

Chunk, address, encrypt

Files are split into chunks, content-addressed by SHA-256(plaintext) for per-vault dedup, then each chunk is encrypted with the DEK and a fresh nonce. Only ciphertext is uploaded.

4

Encrypt the manifest too

The file tree โ†’ chunk-hash map is itself encrypted with the DEK. The backend never learns your filenames, sizes, or directory structure.

5

Merkle-verify every restore

pull recomputes the snapshot's Merkle root and re-checks every chunk's SHA-256 before writing a single byte. GCM auth tags catch any tampering. Phase 2 anchors that 32-byte root on-chain.

Zero-knowledge invariant

The server stores ciphertext. Full stop.

A complete backend compromise yields ciphertext and a wrapped key that opens nothing. Here's exactly what stays on your device โ€” and what the storage backend can and can't see.

on your device never transmitted

  • Plaintext files and the data key (DEK)
  • The wallet signature and derived KEK
  • Filenames, sizes, directory structure
  • Chunking, hashing, encryption โ€” all local

on the backend ciphertext only

  • Encrypted chunks โ€” AES-256-GCM(DEK, โ€ฆ)
  • Encrypted snapshot manifest (tree hidden)
  • A wrapped DEK โ€” opens nothing alone
  • Opaque snapshot ids + Merkle roots
Built for

Cheap, portable, verifiable

No egress fees

S3-compatible storage pointed at Cloudflare R2 by default. Pull from anywhere without surprise bandwidth bills.

Wallet = identity

Your Solana keypair is the root of trust. Open any vault on a new machine just by signing โ€” no password vault to lose.

Tamper-evident

Content-addressed chunks + a Merkle root over the whole tree. A flipped byte in storage is caught before it ever touches disk.

Per-vault dedup

Identical chunks upload once โ€” but never across users. We refuse convergent dedup that would leak who holds the same file.

Runs as MCP or CLI

Drive it from your editor's agent over the Model Context Protocol, or straight from the terminal. Same five operations.

On-chain anchor (Phase 2)

Each push writes its 32-byte Merkle root on-chain, making snapshots independently auditable and rollback-evident.

Quick start

Round-trips offline in under a minute

With no R2 credentials set, storage falls back to a local filesystem mock โ€” so the full encrypted round trip works with zero cloud setup. Point it at R2 when you're ready.

bash
# clone & install
$ git clone https://github.com/yksanjo/vaultmcp && cd vaultmcp
$ npm install && npm run build

# sign in, create a vault, push, pull
$ vault login
$ vault init my-repo
$ vault push <vaultId> ./my-repo -m "first snapshot"
$ vault pull <vaultId> ./restore   # byte-identical, verified

# point at Cloudflare R2 (optional โ€” no egress fees)
$ export VAULTMCP_R2_ENDPOINT=https://<acct>.r2.cloudflarestorage.com
$ export VAULTMCP_R2_BUCKET=my-vault  VAULTMCP_R2_ACCESS_KEY=โ€ฆ  VAULTMCP_R2_SECRET_KEY=โ€ฆ

Five tools, mirrored as CLI commands

vault_initcreate a vault, derive keys, write local config
vault_pushsnapshot + upload, returns id + Merkle root
vault_pullrestore latest or by id, integrity-verified
vault_listlist vaults / snapshots
vault_statuswallet, vaults, last snapshot, quota
vault loginsign-in with Solana โ†’ session