Hey Stacks community ![]()
I’m Xenitron, from Skullcoin Labs building sustainable GameFi on Stacks. We’ve been shipping “Encrypted NFTs” in production for puzzle/game use cases (Seed Phrase Hunt, Find2Earn content unlocking). Today it works via a hybrid model (on-chain = permissions, off-chain = encrypted payload), but the ecosystem lacks a shared standard.
This post proposes a small, non-consensus step that could unlock a lot of composability: a trait + minimal on-chain commitments/events standard so wallets/marketplaces/indexers can support “private layers” consistently.
TL;DR
Encrypted NFTs (eNFTs) introduce a new NFT primitive on Stacks: SIP-009 NFTs with a public layer + a commitment-based private layer (encrypted payloads).
Today, projects that attempt “locked content” do it in incompatible ways → wallets/markets/indexers can’t support it consistently.
Proposal: a non-consensus standard trait that exposes:
-
payload commitment (hash + minimal metadata that binds token-id → private payload)
-
owner-gated key envelope (so the rightful owner can decrypt client-side)
-
optional key rotation / versioning
-
standard events for indexers and better UX
No hard fork required. Fully compatible with SIP-009 NFTs.
Why Encrypted NFTs?
Typical NFT metadata is public. But lots of real apps need incomplete information:
-
game/puzzle clues (ARG style)
-
coordinates / “treasure maps”
-
access passes with hidden content
-
sealed collectibles (reveal only to owner)
You can store ciphertext anywhere, but without a standard:
-
wallets don’t know how to show “Locked content”
-
markets can’t index or preview the encrypted layer
-
devs can’t compose these NFTs across apps
How we do it today (Hybrid)
Our current pattern on Stacks is:
-
On-chain: SIP-009 NFT ownership + commitments (hashes / indexes / events) that bind token-id → locked payload.
-
Off-chain: encrypted payload (image/text/coords) stored in a database or content storage; revealed only after verifying ownership and (optionally) a signed challenge.
This works, but it’s fragmented and easy to implement insecurely if each team invents their own access control pattern.
Proposal (Non-consensus): eNFT Trait + Commitments
Define a trait (name bikesheddable) that every “Encrypted NFT” contract can implement, enabling standard UX.
Minimal required API (conceptual)
- get-payload-commitment(token-id) → returns:
-
commitment (hash binding the payload)
-
cipher-suite (string identifier)
-
optional payload-uri (ciphertext location)
-
optional mime/type
-
version
- get-key-envelope(token-id) → returns owner-gated:
-
envelope (opaque bytes; encryption envelope or retrieval descriptor)
-
envelope-hash
-
created-at
- Optional: get-key-envelope-version(token-id) or key rotation events.
Standard events
-
encrypted_payload_set(token-id, commitment, cipher-suite, payload-uri?)
-
key_envelope_rotated(token-id, envelope-hash, version)
Ownership gating requirement
get-key-envelope MUST fail unless tx-sender is the current token owner.
What this enables (immediately)
-
Wallets can display “
Locked content” consistently. -
Markets can index “has private layer” and show reveal actions to owners.
-
Apps can reuse each other’s eNFTs without custom integrations.
-
Security improves because patterns become reviewable and repeatable.
Security notes (important)
-
A public chain can’t keep plaintext secret. The goal is interoperability around ciphertext + owner-gated key retrieval / client-side decryption.
-
Commitment fields make payload integrity verifiable (prevents silent server-side swaps).
-
A standard owner-gated signature challenge pattern is recommended to avoid common access control bugs (IDOR/replay).
Non-goals (for this SIP)
-
“True on-chain secrecy” (not possible on a public chain by definition).
-
Mandatory ZK verification or heavy cryptographic precompiles.
-
Forcing one encryption method.
We want a small, adoptable spec that works now.
Future work (separate track, optional)
If the community is interested, a separate consensus-level SIP could explore adding additional cryptography primitives to Clarity/VM to enable more advanced flows (e.g., on-chain verification of encryption-consistency proofs, etc.). That is not required for the trait standard, and should be treated as a longer-term R&D topic.
Why this helps developer adoption:
Today, building “locked content” NFTs on Stacks requires each team to invent their own access-control flows, storage conventions, and indexing patterns—making integrations slow and security reviews harder. A shared Encrypted NFT trait reduces that complexity to a known, reusable primitive, enabling faster wallet/market support and easier composability between projects. Lower integration friction + clearer best practices tends to attract more builders, because developers can ship private-layer use cases without reinventing the entire stack every time.
Open questions / Feedback requested
-
Trait shape: what’s the cleanest minimal interface for wallets/markets?
-
Commitment semantics: should we recommend sha256(ciphertext) by default?
-
Event schemas: what fields do indexers need for good UX?
-
Key rotation: do we standardize it v1 or leave as optional?
