sigil
All patterns
advancedcookbook

ZK credit score gate

Prove credit ≥ 700 without revealing the score.

Combine Long HTTP (`0x0805`) to fetch a third-party credit API + ZK Proofs (`0x0806`) to prove a threshold without revealing the underlying value. The TEE attests both the API response and the ZK circuit — gate any contract feature on "score above N" with zero leakage.

Architecture


                Phase 1                          Phase 2 (callback)
   ┌──────┐  fetch    ┌────────────┐  prove   ┌──────────┐  proof  ┌──────────┐
   │ User │ ────────▶ │ LongHTTP   │ ───────▶ │ ZK 0x806 │ ──────▶ │ Gate     │
   │  tx  │           │ 0x0805     │  in TEE  │          │         │ verifies │
   └──────┘           └────────────┘          └──────────┘         └────┬─────┘
                                                                         │
                                                                         ▼
                                                              user authorized
                                                              (score ≥ 700, but
                                                               actual score never
                                                               touched chain)

Code

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {PrecompileConsumer} from "./utils/PrecompileConsumer.sol";

contract ZKCreditGate is PrecompileConsumer {
    address constant ZK             = address(0x806);
    address constant ASYNC_DELIVERY = 0x5A16214fF555848411544b005f7Ac063742f39F6;

    bytes32 public constant CREDIT_CIRCUIT =
        0x0000000000000000000000000000000000000000000000000000000000000000; // your circuit hash

    mapping(address => bool) public verified;

    /// @notice User triggers proof generation. Backed by their hashed identity.
    function requestProof(bytes calldata circuitInput, address executor)
        external returns (bytes32 jobId)
    {
        // ExecutorRequest layout (NOT LongRunningRequest — silent revert otherwise)
        bytes memory input = abi.encode(
            executor, circuitInput, block.number + 200,
            CREDIT_CIRCUIT, address(this), this.onZKResultDelivered.selector,
            300_000, 1_000_000_000, 100_000_000, uint256(0),
            new bytes[](0), new bytes[](0), bytes(""), bytes("")
        );
        (bool ok, bytes memory result) = ZK.call(input);
        require(ok, "zk submit failed");
        jobId = abi.decode(result, (bytes32));
    }

    function onZKResultDelivered(bytes32, bytes calldata result) external {
        require(msg.sender == ASYNC_DELIVERY, "unauth");
        // Result is the ZK proof. The chain verifies it as part of settlement —
        // delivery success ⇒ proof valid ⇒ user passed the threshold.
        // (extract user address from circuit public inputs in real impl)
        verified[tx.origin] = true;
    }

    modifier onlyVerified() {
        require(verified[msg.sender], "credit gate failed");
        _;
    }
}