# Objekt.sh > Decentralized media storage, encryption, and pay-to-reveal for ENS names. ## Static Site Deployment Deploy a directory as a static website to `tmp.objekt.sh`: ```bash objekt deploy ./dist -w my-wallet ``` For permanent hosting, deploy to IPFS and set the ENS contenthash: ```bash # Deploy to IPFS objekt deploy ./dist -w my-wallet --storage ipfs # Set contenthash on ENS objekt ens contenthash set 1a35e1.eth ipfs://Qm... ``` ### Contenthash Read the current contenthash for an ENS name: ```bash objekt ens contenthash get 1a35e1.eth ``` ## Storage Tiers All uploads default to CDN. Paid tiers use [x402](https://x402.org) with USDC on Base — no accounts or API keys needed. | Tier | Durability | Cost | Payment | | --------- | ----------------- | ---------- | ------------ | | `cdn` | 90-day edge cache | Free | None | | `ipfs` | 12-month pin | $0.20/MB | USDC on Base | | `arweave` | Permanent | \~$0.09/MB | USDC on Base | ### Usage ```bash # Free CDN (default) objekt put file.png -w my-wallet # IPFS (12-month pin) objekt put file.png -w my-wallet --storage ipfs # Arweave (permanent) objekt put file.png -w my-wallet --storage arweave ``` ### Estimate Cost Check the price before uploading: ```bash objekt put file.png -w my-wallet --storage arweave --estimate ``` ### Live Pricing ```bash objekt pricing ``` Arweave pricing is dynamic based on current AR/USD rates. ## Upload & Download ### General File Upload ```bash objekt put ./image.png -w my-wallet --storage ipfs ``` Returns a URI, permalink, and payment receipt (for paid tiers). ### ENS Media Upload an avatar or header for an ENS name you own: ```bash # Avatar (max 512KB — JPEG, PNG, WebP) objekt ens avatar upload 1a35e1.eth -f avatar.png -w my-wallet # Header (max 1MB — JPEG, PNG, WebP) objekt ens header upload 1a35e1.eth -f header.jpg -w my-wallet ``` ### Download ```bash # By key objekt get proposals/0x1234/media/abc123 # ENS avatar objekt ens avatar get 1a35e1.eth ``` ### Encrypted Downloads If the content is encrypted, provide your wallet or a view key to decrypt: ```bash objekt get -w my-wallet objekt get --view-key objekt_vk_... ``` The CLI auto-detects encrypted envelopes (CBOR tag prefix) and decrypts transparently. ## Deposit & Purchase ### Deposit a View Key ```bash objekt reveal deposit 1a35e1.eth phone \ --view-key objekt_vk_abc123... \ --price 5.00 \ --content-uri ipfs://Qm... \ --ttl 7d \ -w my-wallet ``` | Option | Description | Default | | --------------- | --------------------------------------- | -------- | | `--view-key` | The view key to deposit | Required | | `--price` | Price in USD | Required | | `--content-uri` | Where the encrypted content lives | Required | | `--ttl` | Time to live (`30m`, `2h`, `1d`, `1w`) | `1d` | | `-w` | OWS wallet name (must own the ENS name) | Required | The CLI signs a `RevealDeposit` EIP-712 message. The server verifies the signature and checks ENS ownership (for `.eth` names) or address match (for `0x` addresses). ### List Available Keys ```bash objekt reveal list 1a35e1.eth ``` Returns all deposited keys with prices, content URIs, and expiry times. View keys are **never** included in list responses. ### Purchase a View Key ```bash objekt reveal buy 1a35e1.eth phone -w buyer-wallet ``` The CLI wraps the request with [x402](https://x402.org) payment. USDC is paid on Base directly to the content owner's address. On success, the view key is returned. ### Remove a Key ```bash objekt reveal remove 1a35e1.eth phone -w my-wallet ``` The current ENS owner (or address owner) can remove any deposited key. This allows cleanup after ENS transfers. ### TTL Entries auto-expire after the TTL. Supported formats: | Format | Duration | | ------ | ---------- | | `30m` | 30 minutes | | `2h` | 2 hours | | `1d` | 1 day | | `1w` | 1 week | Default is `1d`. Expired entries are automatically cleaned up by KV. ## Envelope Metadata Objekt encrypted envelopes are self-describing. Anyone who encounters a file on IPFS, Arweave, or ENS can inspect it without decrypting. ### Inspect an Envelope ```ts import { inspectEnvelope, isEncrypted } from "@objekt.sh/ecies"; if (isEncrypted(data)) { const info = inspectEnvelope(data); console.log(info.mime); // "image/png" console.log(info.access); // "reveal.objekt.sh/1a35e1.eth/phone" console.log(info.recipients); // [{ curve: 1, caip10: "eip155:1:0xabc..." }, ...] } ``` ### What's Public vs Private | Field | Visibility | Purpose | | ------------------- | ------------------ | --------------------------------------------------------------------------------- | | CBOR tag ("objekt") | Public | Format detection — 9-byte prefix check | | `mime` | Public | Original content type | | `access` | Public | Where to buy a view key (x402 URL) | | `r[].id` (CAIP-10) | Public | Who can decrypt (chain + address) | | `r[].pub` | Public | Recipient public key (needed for decryption matching) | | `r[].w` | Public but useless | Wrapped AES key — encrypted per recipient, can't be unwrapped without private key | | `ct` | Public but useless | Encrypted content — can't be decrypted without the AES key | The metadata is intentionally public. The security comes from the AES-256-GCM encryption of the content and the ECIES wrapping of the AES key per recipient. ### Discovery Flow When a client encounters an Objekt encrypted file: :::steps #### Detect Check the 9-byte prefix `[0xDB, 0x00, 0x00, 0x6F, 0x62, 0x6A, 0x65, 0x6B, 0x74]`. #### Inspect Parse the CBOR envelope to read metadata: MIME type, recipients (CAIP-10 addresses), and access URL. #### Try to decrypt If the client has a matching keypair (wallet-derived or view key), decrypt directly. #### Buy access If no keypair matches but `access` is present, fetch the reveal URL to see the price and pay via x402. #### Decrypt with view key After purchasing, use the returned view key to decrypt. ::: ### Embedding in ENS The `access` URL can be published as an ENS text record (e.g., `sh.objekt.reveal`) so wallets and dApps can discover paid content associated with an ENS name without fetching the encrypted file first. ## Reveal — Pay-to-Decrypt `reveal.objekt.sh` is a key escrow service that lets content owners sell access to encrypted content. Buyers pay USDC on Base via [x402](https://x402.org), and the view key is released. ### How It Works ``` Owner Reveal Service Buyer │ │ │ │ 1. encrypt content + view key │ │ │ 2. upload to IPFS/Arweave │ │ │ 3. deposit view key + price ────►│ │ │ │◄──── 4. pay USDC (x402) ────│ │ │───── 5. return view key ────►│ │ │ │ │ │ 6. decrypt content │ ``` ### Key Properties * **Owner sets the price** — quality signal for inbound contact * **Payment goes to the owner** — x402 `payTo` is the owner's address, not the protocol * **Content lives independently** — on IPFS or Arweave, persists even if the reveal service goes down * **Self-describing envelopes** — the encrypted file contains an `access` URL pointing to the reveal endpoint * **Dual namespace** — works with ENS names (`1a35e1.eth/phone`) or wallet addresses (`0xabc.../email`) * **TTL** — entries expire after a configurable duration (default 1 day) ### Endpoints | Endpoint | Description | | ----------------------------------- | ---------------------------------------------- | | `reveal.objekt.sh/1a35e1.eth` | List available keys and prices | | `reveal.objekt.sh/1a35e1.eth/phone` | Purchase a specific view key (x402) | | `reveal.objekt.sh/attestation` | TEE attestation proof (when deployed on Phala) | ## Trust Model The reveal service is a key escrow. Understanding its trust properties is important. ### Current: Encrypted-at-Rest Escrow (v1) The service encrypts view keys at rest using AES-256-GCM with a service secret. During a purchase, the key is decrypted in memory (\~1ms) and returned to the buyer. :::warning The service operator **can** read view keys. This is the same trust model as Gumroad, Stripe, or any payment-gated content service. ::: **Mitigations:** * View keys are encrypted at rest (KV breach alone doesn't leak keys) * Key exists in plaintext memory only during the response (\~1ms) * `sha256(viewKey)` commitment hash lets buyers verify they got the correct key * The service is separate from the storage gateway (different trust boundary) ### Future: Hardware TEE (v2) The service architecture is portable. The same code runs on Cloudflare Workers (v1) or inside a [Phala Cloud](https://phala.com) TEE (v2). When deployed on Phala: * **Intel TDX** hardware isolation — the operator **cannot** read view keys * **Remote attestation** — buyers cryptographically verify the TEE is genuine and running the expected code * **Sealed storage** — view keys are encrypted by the TEE, only decryptable inside the enclave The `GET /attestation` endpoint returns the TDX attestation proof when running on Phala, or `{ tee: false }` on CF Workers. ### Future: Merkle-Gated Access (idea) Merkle trees could gate *who* can purchase from the reveal service. The owner deposits a Merkle root of allowed addresses alongside the view key. Buyers submit a membership proof with their payment — the service verifies the proof before releasing the key. This separates access control (who can buy) from confidentiality (whether the processor can read the secret). In v1, the operator still sees the key. In v2 (TEE), Merkle verification happens inside the enclave — giving both allowlist gating and operator blindness. ### Rejected Approaches We extensively researched alternatives before settling on this architecture: #### Single-Proxy Umbral PRE [Umbral](https://github.com/nucypher/rust-umbral) proxy re-encryption was tested and works in CF Workers (443KB WASM, \~5ms). However, **with a single proxy, buyer + proxy collusion recovers the owner's master private key:** ``` proxy reconstructs f(0) = a/d from kfrags buyer computes d from their private key therefore a = f(0) * d = owner's secret key ``` This is **worse** than simple escrow — it compromises all of the owner's encrypted content, not just the escrowed item. The Rust WASM implementation has never been audited. #### Ferveo DKG (BLS12-381) Same NuCypher team. Single-node is pointless (degenerates to regular encryption). Multi-node on our own infrastructure is security theater — we hold all key shares. Also GPL-3.0 (hard blocker) and unaudited. #### TACo (Threshold Network) Production-ready threshold decryption, but no Base chain conditions on mainnet (Ethereum + Polygon only). Incompatible with CF Workers (ethers v5 dependency). \~$4.4K/year. 3-12 second decrypt latency. #### Lit Protocol V1 (Naga) sunsetting April 1, 2026. V3 (Chipotle) in transition, not production-stable. Trust model shifting from threshold to single-TEE. #### Cloudflare Workers TEE Does not exist. CF Workers use V8 isolate memory isolation only. No SGX, no SEV, no remote attestation. ## CAIP-10 Chains Objekt uses [CAIP-2](https://chainagnostic.org/CAIPs/caip-2) namespaces for chain identification and [CAIP-10](https://chainagnostic.org/CAIPs/caip-10) for account addresses. Each namespace maps to an elliptic curve used for encryption key derivation. ### Supported Namespaces | Namespace | CAIP-2 | Curve | Key Size | | --------- | ---------- | --------- | --------------------- | | EIP155 | `eip155:*` | secp256k1 | 33 bytes (compressed) | | BIP122 | `bip122:*` | secp256k1 | 33 bytes (compressed) | | Cosmos | `cosmos:*` | secp256k1 | 33 bytes (compressed) | | Tron | `tron:*` | secp256k1 | 33 bytes (compressed) | | Spark | `spark:*` | secp256k1 | 33 bytes (compressed) | | Filecoin | `fil:*` | secp256k1 | 33 bytes (compressed) | | Solana | `solana:*` | X25519 | 32 bytes | | TON | `ton:*` | X25519 | 32 bytes | | Sui | `sui:*` | X25519 | 32 bytes | ### CAIP-10 Address Format ``` :: ``` #### Examples | Chain | CAIP-10 | | ---------------- | -------------------------------------------------------------------------------------- | | Ethereum mainnet | `eip155:1:0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045` | | Base | `eip155:8453:0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045` | | Bitcoin mainnet | `bip122:000000000019d6689c085ae165831e93:bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4` | | Solana mainnet | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU` | ### Key Derivation Encryption keypairs are derived deterministically from a wallet signature: 1. Sign a fixed message: `"objekt.sh encryption key v1"` 2. Hash the signature with SHA-256 to get a 32-byte seed 3. Use the seed as a private key for the namespace's curve This means a single OWS wallet can derive encryption keys for every supported chain. ```bash # Show all derived encryption public keys objekt wallet encryption-key my-wallet ``` ### In the Envelope Recipients in an encrypted envelope are identified by their CAIP-10 address (optional `id` field) and their public key (always present). This allows clients to determine which chains can decrypt a given file without needing external metadata. ## CLI Reference ### Wallet Management ```bash objekt wallet create # Create a new wallet objekt wallet import --private-key 0x # Import from private key objekt wallet list # List wallets objekt wallet encryption-key # Show encryption public keys per chain ``` ### ENS Media ```bash objekt ens avatar upload -f -w objekt ens avatar get objekt ens header upload -f -w objekt ens header get objekt ens contenthash get objekt ens contenthash set ``` ### General Upload/Download ```bash objekt put -w [options] objekt get [options] ``` #### `put` Options | Option | Description | Default | | -------------- | ------------------------------------ | -------- | | `--storage` | `cdn`, `ipfs`, or `arweave` | `ipfs` | | `--encrypt` | Encrypt for self | `false` | | `--encryptFor` | Recipient public keys or ENS names | — | | `--view-key` | Generate a shareable view key | `false` | | `--estimate` | Show cost estimate without uploading | — | | `-w` | OWS wallet name | Required | | `--testnet` | Use Base Sepolia | `false` | #### `get` Options | Option | Description | | ------------ | --------------------------------------------- | | `-w` | OWS wallet (for decrypting encrypted content) | | `--view-key` | View key string (`objekt_vk_...`) | | `--output` | Save to file path | ### Deploy ```bash objekt deploy -w [--storage ipfs] ``` ### Pricing ```bash objekt pricing ``` ### Reveal ```bash objekt reveal deposit [options] # Deposit view key for sale objekt reveal buy -w # Purchase a view key objekt reveal list # List available keys objekt reveal remove -w # Remove a deposited key ``` #### `reveal deposit` Options | Option | Description | Default | | --------------- | -------------------------------------- | -------- | | `--view-key` | View key to deposit | Required | | `--price` | Price in USD (e.g. `5.00`) | Required | | `--content-uri` | Content URI (`ar://...`, `ipfs://...`) | Required | | `--ttl` | Time to live (`30m`, `2h`, `1d`, `1w`) | `1d` | | `-w` | OWS wallet (must own the namespace) | Required | ### Global Options | Option | Description | | ----------- | --------------------------------------- | | `--network` | `mainnet` or `sepolia` | | `--testnet` | Use testnet (Base Sepolia for payments) | ## EIP-712 Types All signatures use the same domain: ```ts domain: { name: "Objekt", version: "1" } ``` ### Upload (Storage Gateway) ```ts primaryType: "Upload" types: { Upload: [ { name: "upload", type: "string" }, // Upload type / key { name: "expiry", type: "string" }, // Timestamp ms { name: "name", type: "string" }, // ENS name or address { name: "hash", type: "string" }, // SHA-256 of file bytes ] } ``` ### RevealDeposit ```ts primaryType: "RevealDeposit" types: { RevealDeposit: [ { name: "action", type: "string" }, // "deposit" { name: "ensName", type: "string" }, // Namespace (ENS name or 0x address) { name: "keyName", type: "string" }, // Key name (e.g. "phone") { name: "commitment", type: "string" }, // sha256(viewKey) { name: "price", type: "string" }, // USD price (e.g. "5.00") { name: "expiry", type: "string" }, // Timestamp ms ] } ``` ### RevealRemove ```ts primaryType: "RevealRemove" types: { RevealRemove: [ { name: "action", type: "string" }, // "remove" { name: "ensName", type: "string" }, // Namespace { name: "keyName", type: "string" }, // Key name { name: "expiry", type: "string" }, // Timestamp ms ] } ``` ### Verification Server-side verification uses viem's `verifyTypedData`: ```ts import { verifyTypedData } from "viem/actions"; const valid = await verifyTypedData(client, { ...typedDataParameters, address: unverifiedAddress, signature: sig, message: { ... }, }); ``` After signature verification, the server checks that the recovered address owns the ENS name (via ensjs `getOwner`) or matches the `0x` namespace directly. ## Installation Install the CLI globally: :::code-group ```bash [pnpm] pnpm add -g @objekt.sh/cli ``` ```bash [npm] npm install -g @objekt.sh/cli ``` ::: ### Create a Wallet Objekt uses [Open Wallet Standard](https://openwallet.sh) (OWS) for key management. Keys are encrypted locally in `~/.ows/`. ```bash objekt wallet create my-wallet ``` Or import an existing private key: ```bash objekt wallet import my-wallet --private-key 0x... ``` ### Verify ```bash objekt wallet list ``` You're ready to upload, encrypt, and sell access to content. ## Quick Start ### Upload an Avatar ```bash objekt ens avatar upload 1a35e1.eth -f avatar.png -w my-wallet ``` ### Upload with Encryption Encrypt a file so only you and specific recipients can read it: ```bash objekt put secret.pdf -w my-wallet --encrypt --view-key --storage ipfs ``` This outputs a **view key** (`objekt_vk_...`) and a content URI. Anyone with the view key can decrypt. ### Sell Access Deposit the view key to the reveal service with a price: ```bash objekt reveal deposit 1a35e1.eth phone \ --view-key objekt_vk_abc123... \ --price 5.00 \ --content-uri ipfs://Qm... \ --ttl 7d \ -w my-wallet ``` Buyers pay USDC on Base via [x402](https://x402.org) to get the view key: ```bash objekt reveal buy 1a35e1.eth phone -w buyer-wallet ``` ### How It Works 1. **Sign** — CLI signs requests with EIP-712 typed data via your OWS wallet 2. **Pay** — For paid tiers (IPFS, Arweave), x402 wraps fetch with USDC payment on Base 3. **Store** — Server validates signature, verifies ENS ownership, stores content 4. **Encrypt** — Client-side ECIES encrypts content for multiple recipients across chains 5. **Reveal** — View keys are escrowed, buyers pay to access them ## ENS Contenthash ### `objekt ens contenthash` — Read & Write Manage the contenthash record on an ENS name. This is how ENS-native websites resolve via IPFS or Arweave. #### Get Contenthash ```bash objekt ens contenthash get 1a35e1.eth ``` #### Set Contenthash ```bash objekt ens contenthash set 1a35e1.eth ipfs://Qm... -w my-wallet ``` Setting a contenthash requires an on-chain transaction — the CLI will prompt for confirmation. ## ENS Media ### `ens.objekt.sh` — Avatars & Headers | Method | Path | Description | | --------------- | -------------------------- | ----------- | | `PUT /{name}` | Upload avatar for ENS name | | | `GET /{name}` | Get avatar for ENS name | | | `PUT /{name}/h` | Upload header for ENS name | | | `GET /{name}/h` | Get header for ENS name | | #### Upload Avatar ```bash objekt ens avatar upload 1a35e1.eth -f avatar.png -w my-wallet ``` #### Upload Header ```bash objekt ens header upload 1a35e1.eth -f header.png -w my-wallet ``` #### Supported Formats | Type | Formats | Max Size | | ------ | --------------- | -------- | | Avatar | JPEG, PNG, WebP | 512KB | | Header | JPEG, PNG, WebP | 1MB | ## Envelope Format Objekt encrypted envelopes use [CBOR](https://cbor.io) (RFC 8949) with a registered tag for instant detection. ### Detection Every envelope starts with a 9-byte CBOR tag prefix: ``` 0xDB 0x00 0x00 0x6F 0x62 0x6A 0x65 0x6B 0x74 ^tag(8-byte) ^---------- "objekt" ----------^ ``` This encodes CBOR tag `122,511,826,820,980` — the ASCII bytes of "objekt" as a 48-bit integer. Any standard CBOR decoder reads this as `Tag(122511826820980, )`. Detection is a 9-byte prefix check — no CBOR decoding needed: ```ts import { isEncrypted } from "@objekt.sh/ecies"; if (isEncrypted(data)) { // It's an Objekt encrypted envelope } ``` ### Structure ```sh Tag(122511826820980) { v: 2, // Version mime: "image/png", // Original content MIME type access: "reveal.objekt.sh/1a35e1.eth/phone", // x402 reveal URL (optional) r: [ // Recipient stanzas { c: 1, // CurveId (1 = secp256k1, 2 = X25519) pub: Uint8Array(33), // Recipient compressed public key epk: Uint8Array(33), // Ephemeral public key (ECDH) w: Uint8Array(60), // Wrapped AES key (nonce + ciphertext + GCM tag) id: "eip155:1:0xabc...", // CAIP-10 address (optional) }, ], ct: Uint8Array, // Content ciphertext (nonce + encrypted + GCM tag) } ``` ### Fields | Field | Type | Description | | --------- | --------- | ---------------------------------------------------------------- | | `v` | `number` | Envelope version (currently `2`) | | `mime` | `string` | Original MIME type of the plaintext content | | `access` | `string?` | URL where a view key can be purchased via x402 | | `r` | `array` | Recipient stanzas — one per authorized decryptor | | `r[].c` | `number` | Curve ID: `1` = secp256k1, `2` = X25519 | | `r[].pub` | `bytes` | Recipient's compressed public key | | `r[].epk` | `bytes` | Ephemeral public key used in ECDH key agreement | | `r[].w` | `bytes` | Wrapped AES-256 key (12B nonce + 32B ciphertext + 16B GCM tag) | | `r[].id` | `string?` | Recipient's CAIP-10 address | | `ct` | `bytes` | AES-256-GCM encrypted content (12B nonce + ciphertext + 16B tag) | ### Why CBOR? * **Self-describing** — parseable decades later without external schema * **Binary-native** — `Uint8Array` values encode directly (no base64 overhead) * **Extensible** — add fields without breaking old parsers * **IPFS-native** — CBOR is the encoding used by IPLD, the data model behind IPFS * **Compact** — smaller than JSON, smaller than the previous hand-rolled binary format * **Tagged** — CBOR tags are specifically designed for format identification ## Encryption :::warning Experimental — under heavy R\&D. Do not use for sensitive workloads. ::: Objekt provides end-to-end, client-side encryption via the `@objekt.sh/ecies` package. Content is encrypted before it leaves your machine — the server never sees plaintext. ### Key Features * **Multi-recipient** — Encrypt once, wrap the key for multiple recipients * **Multi-curve** — secp256k1 (EVM, Bitcoin, Cosmos) and X25519 (Solana, TON, Sui) * **View keys** — Generate shareable decryption keys without sharing wallets * **CBOR envelope** — Self-describing binary format with CAIP-10 recipient addressing * **Access metadata** — Embedded x402 reveal URLs for pay-to-decrypt ### Supported Chains | Namespace | Curve | Chains | | --------- | --------- | -------------------------------------- | | EIP155 | secp256k1 | Ethereum, Base, Polygon, Arbitrum, ... | | BIP122 | secp256k1 | Bitcoin | | Cosmos | secp256k1 | Cosmos Hub, Osmosis, ... | | Tron | secp256k1 | Tron | | Solana | X25519 | Solana | | TON | X25519 | TON | | Sui | X25519 | Sui | ### How It Works ``` Plaintext + nonce → [AES-256-GCM] → Content ciphertext Recipient 1 pubkey + AES key → [ECIES secp256k1] → Wrapped key #1 Recipient 2 pubkey + AES key → [ECIES X25519] → Wrapped key #2 View key pubkey + AES key → [ECIES secp256k1] → Wrapped key #3 CBOR envelope = Tag("objekt") + version + MIME + recipients + ciphertext ``` A random AES-256 key encrypts the content once. That key is then wrapped individually for each recipient using curve-appropriate ECIES (ECDH + HKDF-SHA256 + AES-256-GCM). Any recipient can independently decrypt. ## Multi-Recipient Encryption Objekt encrypts content once and wraps the AES key individually for each recipient. Recipients decrypt independently — no coordination needed. ### Encrypt for Multiple Recipients ```bash objekt put secret.pdf -w my-wallet \ --encrypt \ --encryptFor 0x02abc...def \ --encryptFor 1a35e1.eth ``` Recipients can be specified as: * **Hex public key** — `0x02...` (33 bytes secp256k1) or `0x...` (32 bytes X25519) * **ENS name** — resolves the `sh.objekt.encpubkey` text record ### Cross-Chain Recipients A single file can be encrypted for recipients on different chains simultaneously: ``` Recipient 1: eip155:1:0xabc... (Ethereum, secp256k1) Recipient 2: solana:5eykt:7xKX... (Solana, X25519) Recipient 3: bip122:000...000:bc1q... (Bitcoin, secp256k1) ``` The CLI derives chain-specific encryption keypairs from your wallet signature: ```bash # Show your encryption public keys per chain objekt wallet encryption-key my-wallet ``` ### CAIP-10 Addressing Recipients are identified by [CAIP-10](https://chainagnostic.org/CAIPs/caip-10) addresses in the envelope: ``` :: ``` Examples: * `eip155:1:0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045` (Ethereum mainnet) * `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp:7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU` (Solana) * `bip122:000000000019d6689c085ae165831e93:bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4` (Bitcoin) This is embedded in the `id` field of each recipient stanza, making the envelope self-describing across all chains. ### How Decryption Works The decryptor provides their keypairs. The envelope is scanned for a matching `(curve, publicKey)` pair: ```ts import { decryptEnvelope } from "@objekt.sh/ecies"; const { plaintext, mime, access } = decryptEnvelope(envelope, keypairs); ``` If no keypair matches but an `access` URL is present, the decryptor can purchase a view key from the reveal service. ## View Keys View keys let anyone decrypt content without needing a wallet. They're shareable, single-purpose decryption keys. ### How They Work A view key is a random secp256k1 keypair. The public key is added as a recipient during encryption. The private key (the "view key") is shared with whoever should have access. ``` objekt_vk_a1b2c3d4e5f6... ← 32-byte private key, hex-encoded with prefix ``` ### Generate a View Key ```bash objekt put secret.txt -w my-wallet --encrypt --view-key --storage ipfs ``` Output: ```json { "uri": "ipfs://Qm...", "permalink": "https://ipfs.objekt.sh/Qm...", "viewKey": "objekt_vk_a1b2c3d4e5f6..." } ``` ### Decrypt with a View Key ```bash objekt get --view-key objekt_vk_a1b2c3d4e5f6... ``` ### Programmatic Usage ```ts import { generateViewKey, parseViewKey, encryptForRecipients, decryptEnvelope } from "@objekt.sh/ecies"; // Generate const { viewKey, recipient, keypair } = generateViewKey(); // Add as recipient during encryption const envelope = encryptForRecipients(plaintext, [ { pubKey: ownerKey.publicKey, curve: ownerKey.curve }, recipient, // view key recipient ], { mime: "text/plain" }); // Decrypt with view key const parsed = parseViewKey(viewKey); const { plaintext: decrypted } = decryptEnvelope(envelope, [parsed]); ``` ### View Keys + Reveal View keys are the bridge between encryption and the reveal service. The flow: :::steps #### Encrypt with a view key ```bash objekt put secret.txt -w my-wallet --encrypt --view-key --storage ipfs ``` #### Deposit the view key for sale ```bash objekt reveal deposit 1a35e1.eth phone \ --view-key objekt_vk_... \ --price 5.00 \ --content-uri ipfs://Qm... \ -w my-wallet ``` #### Buyer purchases access ```bash objekt reveal buy 1a35e1.eth phone -w buyer-wallet # → Returns the view key after USDC payment ``` #### Buyer decrypts ```bash objekt get ipfs://Qm... --view-key objekt_vk_... ``` ::: ### Commitment Hash When depositing a view key, the reveal service computes `sha256(viewKey)` as a commitment hash. This can be published as an ENS text record so buyers can verify they received the correct key after purchase.