Skip to content
Tenzro
← All tutorials
Tutorial · Execution and bridges

Prove and verify with Plonky3 STARKs

Create Plonky3 STARK proofs for the settlement and identity circuits, verify them on the network, and check the attested commitment from an EVM contract.

Advanced30 min

Tenzro Network uses Plonky3 STARKs, which need no trusted setup and rest on hash functions rather than elliptic curves, so they are quantum-safe. The network uses them in three places: to compress the finality certificates of Tenzro DAG Consensus, and in two application circuits you can call yourself:

  • settlement: proves a statement about a payment's private service result, bound to the request it answers, without revealing the result.
  • identity: proves a claim about an identity's private value, bound to a relying party's challenge, without revealing the value.

In this tutorial you create a proof for each circuit, have the network verify it, and read the resulting commitment from a contract.

Prerequisites

  • The tenzro CLI (see CLI reference).
  • Proofs are created on your own machine. The witness is read from standard input and never leaves the tenzro process: it is not an argument, not a file and not sent to any node. Only the proof bundle goes to the network.
bash
export RPC=https://rpc.tenzro.com

1. List the circuits

bash
tenzro zk circuits --rpc $RPC --format json

Each entry names the circuit, the proof system (plonky3), the field (koala-bear) and the hash (poseidon2). This tutorial uses settlement and identity.

2. Prove a settlement

A proof is bound to a 32-byte binding: the verifier's challenge or the hash of the request it answers, so it cannot be replayed against another one. The 32-byte witness, the private value the statement is about, is read as hex from standard input:

bash
BINDING=0x<32-byte challenge from the verifier>
printf '%s' "<32-byte witness hex>" | tenzro zk prove --circuit-id settlement --binding "$BINDING" \
  > settlement-proof.json

jq '{circuit_id, public_inputs}' settlement-proof.json

The bundle carries the circuit id, the public inputs (the statement's digest and the binding, 32-byte hex each) and one proof leg per proof system. The witness itself stays private.

3. Verify on the network

bash
tenzro zk verify --bundle settlement-proof.json --rpc $RPC --format json

The same check is the open JSON-RPC method tenzro_verifyZkProof, with circuit_id, public_inputs and proofs.

A verified proof yields a commitment: a hash of the circuit id, the proof and the public inputs. Change any public input and you get a different commitment.

4. Check the attestation

A commitment is admitted to the on-chain ZK_VERIFY registry when an AttestZkProof transaction verifies its proof in an executed block. Read whether it is:

bash
tenzro zk attestation --commitment <commitment_hex> --rpc $RPC

The JSON-RPC method is tenzro_getZkAttestation.

5. Use the commitment from a contract

Plonky3 STARKs are too large to verify inside every EVM transaction, so contracts read the attested result instead. The ZK_VERIFY precompile takes a 32-byte commitment and returns 1 if it is in the attested registry and 0 otherwise. Tenzro precompiles run inside ordinary EVM transactions, so a settlement contract can gate a payout on a proof:

solidity
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.24;

contract ProofGatedPayout {
    address public immutable zkVerify; // the ZK_VERIFY precompile address, see /docs/zk

    constructor(address precompile) { zkVerify = precompile; }

    function attested(bytes32 commitment) public view returns (bool) {
        (bool ok, bytes memory out) = zkVerify.staticcall(abi.encodePacked(commitment));
        return ok && out.length == 32 && uint256(bytes32(out)) == 1;
    }

    function release(bytes32 commitment, address payable to, uint256 amount) external {
        require(attested(commitment), "proof not attested");
        to.transfer(amount);
    }
}

6. Prove an identity claim

The identity circuit works the same way: the witness is the private value the claim is about, and the binding is the relying party's challenge.

bash
printf '%s' "<32-byte witness hex>" | tenzro zk prove --circuit-id identity --binding "$BINDING" \
  > identity-proof.json
tenzro zk verify --bundle identity-proof.json --rpc $RPC

The relying party learns that the statement holds for a witness it never sees, bound to the challenge it issued.

What these proofs are not for

Inference results on Tenzro are verified with TEE attestation, not with zero-knowledge proofs. To check that a model ran where and how a provider claims, use attested inference; see TEE.

Next steps