Digital product passport
The EU’s Ecodesign for Sustainable Products Regulation (ESPR) introduces Digital Product Passports: verifiable records of a regulated product’s materials, carbon footprint, recycled content and origin. Regulators, recyclers and consumers need to trust those claims. But the exact numbers behind them are commercially sensitive. A precise carbon figure reveals manufacturing efficiency, origin data reveals suppliers, and inspection records reveal defect rates. The tension is real: prove it, but don’t publish it.
The zero-knowledge idea
Section titled “The zero-knowledge idea”The manufacturer proves “an auditor committed to this product’s measurement, and it meets the threshold” (for example carbon ≤ 50 kg CO₂e, or recycled content ≥ 30 %) without revealing the measurement.
The pattern is the same for every claim:
- An auditor measures the product and commits to
(productId, measurement). - The manufacturer proves, in zero knowledge, that the committed measurement passes the public threshold.
- The proof is tied to one product, because
productIdis inside the committed hash. - Anyone can check the proof. Nobody learns the number.
The demo applies this to carbon (≤), recycled content (≥), manufacturing origin (Merkle membership in an approved-country set) and an ordered chain of inspections. It covers two scenarios: EV batteries with a passport NFT per product, and textiles with one per batch.
What stays private, what’s public
Section titled “What stays private, what’s public”| Input | Visibility | Why |
|---|---|---|
measurement | Secret | The exact carbon figure or recycled percentage: the competitive data |
auditorSecret | Secret | Opens the auditor’s commitment |
productId | Public | Ties the proof to one product or batch |
threshold | Public | The regulatory or labelling limit |
auditorHash | Public | The auditor’s commitment to (productId, measurement) |
isCompliant | Public | Proven equal to the threshold comparison; the validator requires 1 |
How it works
Section titled “How it works”- Register. Products and batches go into a registry. The demo keeps it in a Merkle Patricia Forestry trie hashed with Poseidon, so its root stays circuit-friendly.
- Audit. The auditor measures and publishes a commitment per product and claim.
- Prove. The manufacturer generates one Groth16 proof per claim.
- Mint the passport. A minting policy checks the manufacturer’s signature and the compliance proof, then mints exactly one passport token. The public inputs sit in the output’s inline datum.
- Check. Anyone can read the passport and its public inputs on-chain. A non-compliant product can’t mint one.
auditor ──commit(productId, measurement)──▶ auditorHash (public)manufacturer ──prove(measurement ≤ threshold)──▶ mint DPP token datum: [productId, threshold, auditorHash, isCompliant=1]The circuit
Section titled “The circuit”The DPP demo deliberately uses the lower-level CircuitSpec style instead of annotations. This
sketch is simplified from its ComplianceThresholdCircuit, keeping only the “less than or equal”
variant used for carbon:
import org.zeroj.circuit.CircuitBuilder;import org.zeroj.circuit.CircuitSpec;import org.zeroj.circuit.Signal;import org.zeroj.circuit.SignalBuilder;import org.zeroj.circuit.lib.SignalComparators;import org.zeroj.circuit.lib.SignalPoseidon;import org.zeroj.circuit.lib.poseidon.PoseidonParams;import org.zeroj.circuit.lib.poseidon.PoseidonParamsBLS12_381T3;
public class CarbonThresholdCircuit implements CircuitSpec { private static final PoseidonParams POSEIDON = PoseidonParamsBLS12_381T3.INSTANCE;
@Override public void define(SignalBuilder c) { Signal measurement = c.privateInput("measurement"); // secret: kg CO2e Signal auditorSecret = c.privateInput("auditorSecret"); Signal productId = c.publicInput("productId"); Signal threshold = c.publicInput("threshold"); Signal auditorHash = c.publicInput("auditorHash"); Signal isCompliant = c.publicOutput("isCompliant");
// 1. This is the measurement the auditor committed to, for this product Signal claims = SignalPoseidon.hash(c, POSEIDON, productId, measurement); c.assertEqual(SignalPoseidon.hash(c, POSEIDON, auditorSecret, claims), auditorHash);
// 2. measurement <= threshold (16-bit; the comparator range-checks both sides) c.assertEqual(isCompliant, SignalComparators.lessOrEqual(c, measurement, threshold, 16)); }
public static CircuitBuilder build() { return CircuitBuilder.create("compliance-lte") .publicVar("productId").publicVar("threshold") .publicVar("auditorHash").publicVar("isCompliant") .secretVar("measurement").secretVar("auditorSecret") .defineSignals(new CarbonThresholdCircuit()); }}Each threshold circuit is small, a few hundred constraints. The rest of the flow is the same as any other circuit. Compile to R1CS, compute the witness and prove with Groth16. See the Circuit DSL guide.
On Cardano
Section titled “On Cardano”The demo uses two JuLC scripts built on Groth16BLS12381Lib.verify(...):
DppMintingPolicymints a passport token only if the manufacturer signed the transaction, exactly one token is minted, the compliance proof over the first output’s datum[productId, threshold, auditorHash, isCompliant]verifies, andisCompliant == 1.DppComplianceValidatoris a compliance-gated spending validator: lock ADA, then unlock it with a proof of compliance.
A valid proof is not authorization. The manufacturer-signature check is a real ScriptContext
binding, but a production passport also has to tie auditorHash to an accredited auditor, pin
thresholds to the regulation that applies (not to a value the manufacturer picks) and bind
productId to the token being minted. The DPP state machine and an auditor registry held as a
reference input are covered in the design notes below.
Security considerations
Section titled “Security considerations”- Auditor trust. ZK proves the audited value passes the threshold, not that the audit was honest. Use independent auditors, spot checks and accountability, the same trust model as today’s certification.
- Bind every claim to the product and an attestation. An origin proof that only shows “some country code is in the approved set” proves nothing about this product. Each claim needs the product ID and an attested value inside its constraints, and every public input must appear in a constraint.
- Thresholds and policy belong to the verifier: pin them as script parameters or read them from a registry.
- Linkability of batches. Per-batch passports reveal batch sizes and timing. Decide what you’re willing to publish.
- Trusted setup. The demo uses a single-party development setup; production needs an MPC ceremony (Trusted setup, explained).
Try it
Section titled “Try it”The demo lives in
digital-product-passport.
With Yaci DevKit running (see Run the demos):
./demo.sh dpp --run--run generates the compliance proofs for battery BAT-SN001, mints its passport token on-chain
and prints the registry status. In the UI, try BAT-SN003 (65 kg carbon, 20 % recycled) or textile
batch TEX-B2024-003, which is made outside the approved set. Both come back “NOT COMPLIANT”.
Design notes: Digital product passport — detailed design (multi-party supply chains, lifecycle state machine, comparison with the Cardano Foundation blueprint).
Related
Section titled “Related”- Circuit DSL: the
CircuitSpecstyle used here - Authenticated state: Poseidon MPF/JMT registries (experimental)
- Testing circuits: prove your constraints reject bad witnesses
- Application security