Encrypted image storage on Solana. Images are encrypted client-side with ChaCha20-Poly1305, chunked into 900-byte segments, and stored on-chain via transaction events. Only the wallet that uploaded an image can decrypt it.
Program ID: 13ZhnnA6nfDW2H9g2LsY2dVPLjEnHTW4U51kTKWegENC
- User selects an image file
- Client requests a wallet signature over
"encrypt:{image_id}" - Encryption key derived:
SHA256(signature)(32 bytes) - Image encrypted with ChaCha20-Poly1305 (12-byte random nonce prepended)
- Encrypted data chunked into 900-byte segments
- Chunks uploaded on-chain via
initialize_upload->upload_chunkx N ->finalize_upload - Each chunk is emitted as a
ChunkUploadedevent in the transaction logs
- Fetch transaction signatures for the image's PDA
- Parse
ChunkUploadedevents from transaction logs - Reassemble chunks into the encrypted blob
- Request the same wallet signature, derive the same key
- Decrypt with ChaCha20-Poly1305 -> original image
programs/onchain-encrypted-images/ # Anchor program (Rust)
src/
lib.rs # Instructions: initialize_upload, upload_chunk, finalize_upload
state.rs # ImageUpload account structure
events.rs # UploadInitialized, ChunkUploaded, UploadFinalized
error.rs # Custom error codes
app/ # React frontend
src/
components/ # UploadPanel, GalleryPanel, ImageViewDialog, ImageCard
hooks/ # useImageUpload, useImageDecrypt, useGallery
lib/
crypto.ts # ChaCha20-Poly1305 encrypt/decrypt, key derivation
anchor.ts # Anchor program interaction
tests/
onchain-encrypted-images.test.ts # Localnet tests (13 tests)
devnet-smoke.test.ts # Devnet smoke test (4 tests)
utils/helpers.ts # Shared crypto and PDA helpers
# Install dependencies
bun install
cd app && bun install
# Copy environment config
cp .env.example .env# Build the Solana program
anchor build
# Build the frontend
cd app && bun run build.ts# Localnet tests (starts a local validator)
bun run test
# Devnet smoke test (requires funded wallet and deployed program)
bun run test:devnet
# Type-check
bun run checkSee .env.example:
| Variable | Default | Description |
|---|---|---|
DEVNET_RPC |
https://api.devnet.solana.com |
RPC endpoint for devnet tests |
WALLET_PATH |
~/.config/solana/id.json |
Path to Solana keypair |
| Instruction | Description |
|---|---|
initialize_upload(image_id, total_chunks, content_type) |
Creates an ImageUpload PDA account |
upload_chunk(chunk_index, data) |
Uploads a single chunk (max 900 bytes, must be sequential) |
finalize_upload() |
Marks the upload as complete |
| Limit | Value |
|---|---|
| Max image ID length | 64 characters |
| Max content type length | 32 characters |
| Chunk size | 900 bytes |
| Max chunks per image | 10,000 (~9 MB) |
- Program: Rust + Anchor 0.32
- Frontend: React 19, Tailwind CSS, Radix UI
- Crypto:
@noble/ciphers(ChaCha20-Poly1305),@noble/hashes(SHA256) - Wallet: Solana Wallet Adapter (Phantom)
- Runtime: Bun