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.
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:
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:
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.
{
"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.
{
"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.
{
"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.
{
"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.
{
"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().
{
"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.
{
"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.
→ { "wallet": "PubKeyBase58" }
← { "nonce": "0x6f4c…", "expiresAt": "<ISO-8601>" }POST /verify
Verify the wallet signature and confirm NFT ownership against live on-chain state.
→ {
"wallet": "PubKeyBase58",
"mint": "PubKeyBase58",
"signature": "0x…",
"nonce": "0x6f4c…"
}
← { "token": "vt_…", "expiresAt": "<ISO-8601>" }POST /release
Exchange a verification token for the registered key.
→ { "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.
