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 1 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 that a payment is bound to a specific service commitment and amount, that its nonce follows the previous one, and that the payer's balance covers it, without revealing the balance or the service proof.
  • identity: proves control of a key, commits to a set of capabilities, and shows that a reputation score clears a minimum, without revealing the key, the capabilities or the score.

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).
  • Your own node for proving. A witness contains private values, so create proofs on a node you run (http://127.0.0.1:8545 below) and send only the proof to the public endpoint.
bash
export LOCAL=http://127.0.0.1:8545
export RPC=https://rpc.tenzro.xyz

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

The settlement witness holds the payer's balance, the service proof, the current and previous nonces and the amount. Each value is a field element given as a decimal integer.

bash
tenzro zk prove --rpc $LOCAL --format json \
  --circuit-id settlement \
  --witness '{
    "payer_balance": 500,
    "service_proof": 918273645,
    "nonce": 42,
    "prev_nonce": 41,
    "amount": 120
  }' > settlement-proof.json

jq '{circuit_id, proof_size_bytes, public_inputs: (.public_inputs | length)}' settlement-proof.json
json
{ "circuit_id": "settlement", "proof_size_bytes": ..., "public_inputs": ... }

The public inputs are hash commitments to the service and the settlement plus the amount. The balance, the service proof and the nonces stay private.

3. Verify on the network

Send the proof and its public inputs to the public endpoint:

bash
tenzro zk verify --rpc $RPC --format json \
  --circuit-id settlement \
  --proof "$(jq -r .proof settlement-proof.json)" \
  --inputs "$(jq -c .public_inputs settlement-proof.json)"
json
{
  "valid": true,
  "circuit_id": "settlement",
  "status": "plonky3_verified",
  "commitment_hex": "0x...",
  "newly_attested": true,
  "attestation_model": "quorum"
}

The same call is available as the open JSON-RPC method tenzro_verifyZkProof with circuit_id, proof and public_inputs.

A valid proof yields a commitment: a hash of the circuit ID, the proof and the public inputs. The commitment is admitted to the on-chain registry only after a stake-weighted quorum of validators has each re-run the verifier and co-signed it, and it then stays open to challenge for a fraud window. Change any public input and you get a different commitment.

4. Check the attestation window

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

The record shows when the commitment was attested and when its fraud window closes. Anyone who can show the proof does not verify may file a fraud proof during the window:

bash
tenzro zk file-fraud-proof --commitment <commitment_hex> --rpc $RPC

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 the validator quorum has attested it 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 lets an agent show that it controls a key and meets a reputation threshold while keeping the key, its capability set and its exact score private. The key here is a field element held by the prover for this purpose; it is not a wallet or validator key.

bash
tenzro zk prove --rpc $LOCAL --format json \
  --circuit-id identity \
  --witness '{
    "private_key": 7340281,
    "capabilities": 13,
    "capability_blinding": 99120443,
    "actual_reputation": 87,
    "minimum_reputation": 70
  }' > identity-proof.json

tenzro zk verify --rpc $RPC --format json \
  --circuit-id identity \
  --proof "$(jq -r .proof identity-proof.json)" \
  --inputs "$(jq -c .public_inputs identity-proof.json)"

The public inputs are a commitment to the public key, a blinded commitment to the capabilities and the minimum reputation. A relying party that already knows the key commitment and the capability commitment learns only that the agent clears the threshold.

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