Skip to content
Writing

Verifiable NFT Composition from IPFS Token URIs

10 min readKai Aldag

A RISC Zero zkVM app that proves an NFT's off-chain IPFS metadata matches its on-chain tokenURI, then composes verified NFTs into new ones — our Best ZKVM Application winner at ZKHack Montreal.


At ZKHack Montreal, this project won the Best ZKVM Application bounty from RISC0. The idea we explored is simple to describe and surprisingly powerful: what if an NFT’s tokenURI could become a trustless input to new applications, not just a pointer that frontends render?

Most NFT metadata lives off-chain. On-chain, the contract usually publishes a URI such as:

ipfs://Qm...

That URI is an IPFS content identifier. It commits to bytes, but smart contracts generally cannot afford to fetch the bytes, parse JSON, validate the schema, and run application logic over it. As a result, anything interesting that depends on metadata tends to move into trusted servers, indexers, or frontends.

We built a different path: a RISC Zero zkVM application that proves a supplied metadata object is exactly the content committed to by an on-chain IPFS token URI, then composes those verified objects into new NFT state.

In our demo, the NFTs are soccer player cards. Each player NFT publishes only an IPFS CID on-chain. The off-chain metadata contains player attributes like jersey number, tier, overall rating, speed, shooting, passing, dribbling, defense, physicality, and goal tending. The system proves that a claimed Player object really hashes back to the CID published by the ERC-721 contract. Once that fact is proven, the player metadata can be used inside another proof to build higher-level assets, such as a team NFT.

The important part is the trust model: the downstream application does not need to trust the caller, an API, an indexer, or a metadata server. It trusts the on-chain tokenURI and a zkVM proof.

Why This Matters

NFT metadata is a huge amount of latent application state. It can encode game stats, attributes, inventory, generative traits, identity claims, membership properties, or arbitrary JSON. But because that data usually sits behind IPFS URIs, most contracts treat it as opaque.

That limits composability. You can transfer the NFT, check ownership, and maybe read a URI, but you cannot easily say:

  • “Build a team from these verified player cards.”
  • “Upgrade this NFT only if its existing metadata has a qualifying trait.”
  • “Create a derivative asset whose attributes are computed from several source NFTs.”
  • “Migrate metadata to a new format without trusting a centralized migration service.”
  • “Prove that an edited IPFS object preserves selected fields from the original object.”

RISC Zero changes the boundary. The expensive and awkward work happens inside the zkVM: decoding data, recomputing CIDs, checking application rules, and producing new outputs. Ethereum only verifies a succinct proof and consumes the verified journal.

The Core Mechanism

The project has four main pieces:

  1. A shared Rust metadata and CID library.
  2. A player-verification zkVM guest.
  3. A team-composition zkVM guest.
  4. Solidity contracts that publish and verify the resulting commitments.

Here is the high-level split between the chain, the publisher, and the zkVM:

The sequence looks like this when someone builds a derived NFT from existing IPFS-backed NFTs:

And at the zkVM-program level, the two proving steps are:

1. Recomputing IPFS CIDs in Rust

The common Rust crate defines a serializable Player metadata type and a ComputeCid trait. Given any serializable object, the code serializes it to JSON bytes and feeds those bytes through the same UnixFS-style CID construction used by IPFS.

That gives us two useful forms:

  • The raw CID bytes, including the multihash prefix.
  • The formatted URI string, ipfs://<cid>.

This is the bridge between ordinary NFT metadata and verifiable computation. A prover can present a JSON object to the zkVM, and the zkVM can independently derive the CID that object should have.

2. Reading the Real On-Chain Token URI

The player verification guest uses RISC Zero Steel to make authenticated Ethereum view calls inside the proof. It reads:

  • ownerOf(tokenId)
  • tokenURI(tokenId)

from the Player ERC-721 contract on Sepolia.

The guest then recomputes the expected IPFS URI from the supplied Player metadata and asserts that it exactly matches the on-chain tokenURI.

If the assertion passes, the proof journal commits to the Ethereum state commitment and the owner. That means a verifier learns: at the proven chain state, this player object is the content committed to by this token’s published URI, and this address owned the token.

3. Composing Verified NFTs

The next step is composition. A team should not be built from arbitrary JSON that merely looks like player metadata. It should be built from player objects that have already been proven to correspond to real player NFTs.

RISC Zero proof composition gives us that structure. The team-building guest can accept player verification receipts as assumptions, verify those receipts inside the zkVM, and then run higher-level application logic over the verified player data.

In the soccer demo, that means composing player NFTs into a team. The same pattern generalizes to any rule system:

  • Combine multiple source NFTs into one derived NFT.
  • Enforce ownership or approval checks.
  • Preserve selected metadata fields.
  • Compute new attributes from old attributes.
  • Produce a new IPFS CID for the derived object.

Instead of treating metadata as a frontend convention, the application can treat it as proven input.

4. Verifying the Result On-Chain

On-chain, the contracts use RISC Zero’s Groth16 verifier interface. The generated image IDs bind each proof to a specific zkVM program:

  • VERIFY_CID_ID identifies the player CID verification guest.
  • MAKE_TEAM_ID identifies the team composition guest.

The Player contract stores compact CID commitments as bytes32 values and reconstructs CIDv0 URIs by prepending the multihash prefix and Base58 encoding the result. That keeps on-chain storage small while still exposing standard ipfs://... token URIs.

The Team contract is the receiving side for the composed proof. It checks player approvals, verifies the RISC Zero seal against the team-building image ID, and only accepts the submitted team URI when the proof’s journal matches the expected public output.

What We Built

The repository contains an end-to-end prototype of verifiable IPFS-backed NFT composition:

  • common defines the player metadata schema and CID derivation logic.
  • methods-player contains the zkVM guest that proves a player object matches the on-chain IPFS tokenURI.
  • methods-team contains the zkVM guest for composing verified player proofs into a team-building proof.
  • apps contains a publisher CLI that preflights Ethereum calls with Steel, generates player proofs, passes them as assumptions into the team proof, and prepares the proof output for on-chain verification.
  • contracts contains the ERC-721 player and team contracts, RISC Zero image ID integration, and CIDv0 URI formatting logic.

The demo domain is intentionally concrete. Soccer cards make the value easy to see: a player card has metadata-rich stats, and a team is a meaningful composition of multiple players. But the actual primitive is broader than sports or games.

We proved that an NFT’s off-chain metadata can become a verifiable input to another application, as long as the original token publishes an IPFS CID on-chain.

Trustlessly Modifying IPFS URIs

One way to describe this project is “trustless IPFS URI modification.”

That does not mean changing the contents behind an existing CID. IPFS CIDs are content-addressed, so changing the bytes necessarily creates a new CID. Instead, the system lets anyone prove a valid transformation:

old on-chain tokenURI -> verified old metadata -> application rule -> new metadata -> new IPFS CID

The proof can show that the new URI was derived correctly from the old one according to a known program.

That unlocks a useful design pattern for NFT applications:

  1. Read the authoritative source URI from an existing on-chain NFT.
  2. Provide the corresponding IPFS content to the zkVM.
  3. Recompute the CID and prove it matches the source URI.
  4. Apply deterministic transformation rules.
  5. Compute the new CID.
  6. Publish or accept the new URI on-chain only if the proof verifies.

No trusted backend has to certify the transformation. No indexer has to be treated as an oracle. No contract has to parse arbitrary JSON or implement IPFS hashing in Solidity. The zkVM handles the heavy computation, and the chain verifies the result.

Where This Can Go

This pattern is useful anywhere IPFS metadata is already the source of truth but is too expensive or inconvenient for smart contracts to inspect directly.

Games can use it to craft items, combine characters, update stats, or enforce progression rules based on existing NFT metadata. NFT collections can use it for trustless migrations, where holders prove that a new metadata object faithfully preserves traits from an old collection while adding new fields. Creator tools can use it to generate derivative assets with provenance that is verifiable on-chain. DAOs and membership systems can use it to prove properties of off-chain metadata without exposing every detail in contract storage.

The broader point is that IPFS URIs do not have to be dead strings. With a zkVM, they can become composable commitments.

The Takeaway

The project turns a common NFT pattern into a programmable primitive. An ERC-721 publishes an IPFS CID. A prover supplies the content. The zkVM recomputes the CID, proves it matches the on-chain URI, runs application logic over the verified content, and outputs a new commitment. Ethereum verifies the proof and can safely accept the result.

That is why this was a strong fit for the RISC0 bounty at ZKHack Montreal. It uses the zkVM for exactly the kind of work that is natural off-chain but hard on-chain: IPFS hashing, JSON-shaped application logic, authenticated chain reads, and proof composition.

The result is a system for composing NFTs in verifiably correct ways from the only thing the original NFT had to publish on-chain: its IPFS token URI.

Written by

Kai Aldag

I build and write about crypto, cryptography, and distributed systems. Egg Tech is my one-person company — the home for what I ship and the notes I keep along the way.

Related writing