Documentation

How CryptoProto encrypts, stores, and gates content.

Overview

CryptoProto is a protocol for encrypted, NFT-gated content ownership.

The internet's ownership model is a polite fiction: you buy access to something, and somewhere a server holds the right to take it away. CryptoProto removes that server from the loop. Content is encrypted client-side with AES-256-GCM, the ciphertext is pinned to Arweave forever, and the decryption key is gated by a Solana NFT.

Every viewer proves ownership the same way: sign a fresh challenge with the wallet that holds the NFT. The escrow verifies the signature, confirms ownership against live on-chain state, and releases the key. No accounts, no subscriptions, no platforms in the path.

Architecture

Seven composable steps, two flows. The author chain runs once when content is published; the viewer chain runs every time someone unlocks it.

encryptuploadmintregisterAUTHORchallengeverifydecryptVIEWERon-chain state

The author flow ends at register — the key is now held by the escrow, bound to the mint address. The viewer flow begins independently any time someone with the NFT wants to read the content.

Getting started

Install

CryptoProto runs as an MCP server inside any MCP-compatible runtime (Claude Desktop, Cursor, Claude Code). See /tools for the per-runtime config.

First encryption (author)

From an agent prompt:

AUTHOR FLOW
// Plaintext → NFT-gated permanence
encrypt(file) → ciphertext, key
upload(ciphertext) → arweave txId
mint(metadata, owner) → mint address
register(key, mint) → keyId

Viewer flow

When a holder wants to access the content, the agent performs three calls — typically against the wallet the user is signed in with:

VIEWER FLOW
challenge(wallet) → nonce
verify(wallet, mint, signature, nonce) → token
decrypt(ciphertext, token) → plaintext

Tools

Seven MCP tools. Names are stable; arguments may evolve before 1.0.

encrypt

encrypt(file: Buffer, key?: Uint8Array) → { ciphertext, key, nonce }Params
file
Plaintext content to encrypt.
key
Optional 32-byte AES key. Generated when omitted.

Returns. Ciphertext bytes, the encryption key used, and the GCM nonce.

EXAMPLE
{
  "ciphertext": "0x4a7f1c8e3b…",
  "key":        "0xb1e2f0a4d9…",
  "nonce":      "0x9f3a72c0b6…"
}

upload

upload(ciphertext: Buffer) → { txId, url }Params
ciphertext
The encrypted bytes to pin to Arweave.

Returns. Arweave transaction id and the gateway URL.

EXAMPLE
{
  "txId": "T9k…ZQ",
  "url":  "https://arweave.net/T9k…ZQ"
}

mint

mint(metadata: NFTMetadata, owner: PublicKey) → { mint, sig }Params
metadata
Name, image, attributes, and the cipher hash.
owner
Solana wallet that receives the minted NFT.

Returns. The mint address and the transaction signature.

EXAMPLE
{
  "mint": "Cp7…aR",
  "sig":  "5Yz…Ax"
}

register

register(key: Uint8Array, mint: PublicKey) → { keyId }Params
key
AES key produced by encrypt().
mint
NFT mint address that gates this key.

Returns. An opaque key id for later release calls.

EXAMPLE
{
  "keyId": "k_01HQX…"
}

challenge

challenge(wallet: PublicKey) → { nonce, expiresAt }Params
wallet
Solana public key the holder will sign with.

Returns. A single-use nonce and an expiration timestamp.

EXAMPLE
{
  "nonce":     "0x6f4c…",
  "expiresAt": "2026-04-27T17:30:00Z"
}

verify

verify(wallet, mint, signature, nonce) → { token }Params
wallet
Public key claiming ownership.
mint
NFT mint address being claimed.
signature
Signature of the issued nonce.
nonce
The nonce returned from challenge().

Returns. A short-lived bearer token usable with decrypt().

EXAMPLE
{
  "token": "vt_…"
}

decrypt

decrypt(ciphertext: Buffer, token: string) → BufferParams
ciphertext
Encrypted bytes fetched from Arweave.
token
Verification token from verify().

Returns. The original plaintext bytes.

EXAMPLE
{
  "plaintext": "<original file bytes>"
}

Escrow API

The key escrow runs as a Cloudflare Worker. Most callers use the MCP tools above; the raw HTTP surface is documented here for non-MCP integrations.

POST /challenge

Issue a single-use nonce for a wallet.

REQUEST · RESPONSE
→ { "wallet": "PubKeyBase58" }
← { "nonce": "0x6f4c…", "expiresAt": "<ISO-8601>" }

POST /verify

Verify the wallet signature and confirm NFT ownership against live on-chain state.

REQUEST · RESPONSE
→ {
    "wallet":    "PubKeyBase58",
    "mint":      "PubKeyBase58",
    "signature": "0x…",
    "nonce":     "0x6f4c…"
  }
← { "token": "vt_…", "expiresAt": "<ISO-8601>" }

POST /release

Exchange a verification token for the registered key.

REQUEST · RESPONSE
→ { "token": "vt_…", "keyId": "k_01HQX…" }
← { "key": "0xb1e2f0a4d9…", "nonce": "0x9f3a72c0b6…" }

All endpoints return 200 on success, 401 for invalid signatures or expired nonces, and 404 for unknown mints.

Security

  • AES-256-GCM. Authenticated encryption. Tampering with the ciphertext fails the auth check before a single byte is decoded.
  • Nonce policy. 96-bit random nonces. Never reused for a given key. Stored alongside the ciphertext.
  • Key custody. The escrow holds keys encrypted at rest. Keys are released only after a signature + on-chain ownership proof.
  • Threat model. Stolen ciphertext is useless without the NFT. Stolen NFT is useless without the wallet. Compromised escrow cannot mint or transfer NFTs. Replays are blocked by nonce expiry. Network MITM sees only ciphertext and signatures.

FAQ

What if I lose my wallet?

Access follows the NFT. If the wallet is lost, access is lost — same as losing the only key to a safe deposit box. Use a hardware wallet for content you can't afford to lose.

Can the encrypted file be deleted?

No. Arweave's incentive model is pay-once-store-forever. Content uploaded to the network is replicated and persists past any single node.

Why Solana and not Ethereum?

Verification needs to be fast and cheap to feel like a UI primitive, not a transaction. Solana's confirmation times and fees fit the access pattern.

Is the escrow trustless?

Trust-minimised. The escrow can refuse to release keys, but it cannot mint NFTs, transfer ownership, or read your plaintext. Multi-party escrow is on the roadmap.

How much does this cost?

Storage is paid once (Arweave bundle fee, scales with file size). Minting is one Solana transaction. Verification is free at the protocol level.