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
Two paths
The browser is the zero-install path. Go to /create or /view, connect a Solana wallet, and you are done — no install, no SDK, no account. See Use it in the browser.
For agents and automation, CryptoProto also 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
Use it in the browser
CryptoProto runs end-to-end in the browser at /create and /view. No install, no SDK, no account. A Solana wallet — Phantom, Solflare, or Backpack — is the only requirement.
Devnet beta
The flows run on Solana devnet today. Use test SOL only. Grab it free from the faucet and switch your wallet to devnet. Mainnet support lands next. The flows are identical.
Create
Pick a file — image, video, or PDF, up to 100 MB. The steps run in order:
encrypt with AES-256-GCM in the browser tab
upload ciphertext to Arweave // free under 100 KB; larger funds storage in SOL
sign #1 → mint the NFT + pay a flat 0.01 SOL service fee
sign #2 → escrow the key, bound to the mint
Two signatures total. The plaintext and the key never leave the tab. The key goes only to the escrow, bound to the mint.
View
Paste a link, or connect the wallet that holds the NFT. The page proves ownership by signing a single-use challenge — no transaction, no fee — fetches the key from escrow, decrypts in the browser, and renders the content. Sell or transfer the NFT and the new owner unlocks automatically. You no longer can.
One warning. If the tab closes between minting and key escrow, the key is gone and that NFT can never be unlocked. The UI blocks and warns before letting this happen.
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. The full OpenAPI spec ships at /openapi.json.
POST /register
Escrow a key for a mint. The signature proves the caller holds the registering wallet.
→ {
"nftMint": "<base58>",
"pubkey": "<base58>",
"keyHex": "<hex>",
"signature": "<hex>"
}
← { "success": true }signature is Ed25519 over the UTF-8 string register:<nftMint>, hex-encoded.
GET /challenge
Issue a single-use nonce for a wallet. Valid 60 seconds. Rate limit 30/min/IP.
→ GET /challenge?pubkey=<base58>
← { "nonce": "<hex string>", "expiresAt": <epoch ms> }GET /key
Release the escrowed key. The escrow verifies the signature and confirms live on-chain ownership before responding. Rate limit 10/min/IP.
→ GET /key?nftMint=<>&pubkey=<>&nonce=<>&signature=<>
← { "key": "<hex>" }signature is Ed25519 over the UTF-8 nonce string exactly as returned — do not hex-decode it — hex-encoded.
Any deviation returns AUTH_ERROR.
Errors
Errors return JSON with a message and a stable code.
{ "error": "<message>", "code": "<CODE>" }Codes: BAD_REQUEST, AUTH_ERROR, OWNERSHIP_ERROR, KEY_NOT_FOUND, RATE_LIMIT.
On-chain manifest
The NFT metadata JSON, at the token's URI, carries everything a viewer needs under properties.cryptoproto.
{
"name": "…",
"description": "…",
"properties": {
"content_type": "image/png",
"cryptoproto": {
"arweave_tx": "<ciphertext data-item id>",
"iv": "<base64, 12 bytes>",
"tag": "<base64, 16-byte GCM auth tag>",
"algo": "AES256GCM",
"nft_mint": "<mint or empty>"
}
}
}One interop trap: the ciphertext on Arweave excludes the GCM tag; the tag travels separately in the manifest and is re-appended before decryption.
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
Is this live?
Yes — as a devnet beta. The browser flows run on Solana devnet today. Use test SOL only. Mainnet support lands next; the flows are identical.
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; free under 100 KB). Minting is one Solana transaction plus a flat 0.01 SOL service fee per mint. Verification is free at the protocol level.
