[CEREBRO]Cerebro Logo

DOCUMENTATION

API reference and integration guide for the Cerebro ZK prover

OVERVIEW

Cerebro is a zero-knowledge proving service for JWT verification. It accepts a standard JWT and its RSA public key modulus, then generates a Groth16 proof that the token is valid — without revealing the token's contents on-chain.

The proof and public signals can be submitted to Starknet, where a Cairo smart contract verifies them using Garaga's BN254 pairing engine.

CIRCUIT
circom (jwt_verify)
PROVER
rapidsnark / snarkjs
VERIFIER
Garaga Groth16 (Cairo)

API_ENDPOINT

POST https://api.cerebro.cavos.xyz/prove

The prover accepts JSON payloads up to 1MB. All communication is over HTTPS with CORS enabled.

REQUEST_FORMAT

Send a POST request with the following JSON body:

json
{
  "jwt": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOi...",
  "modulus": "2148399...large decimal or hex string..."
}
jwtREQUIRED

A complete RS256-signed JWT string with three dot-separated parts (header.payload.signature). Must contain sub, iss, aud, nonce, and exp claims. Must not be expired.

modulusREQUIRED

The RSA public key modulus (n) from the JWKS endpoint of the JWT issuer. Accepts decimal string or hex format.

RESPONSE_FORMAT

On success, the API returns:

json
{
  "success": true,
  "proof": {
    "pi_a": ["12345...", "67890...", "1"],
    "pi_b": [["12345...", "67890..."], ["12345...", "67890..."], ["1", "0"]],
    "pi_c": ["12345...", "67890...", "1"],
    "protocol": "groth16",
    "curve": "bn128"
  },
  "publicSignals": [
    "pubkey_hash",
    "sub_hash",
    "nonce_hash",
    "iss_hash",
    "aud_hash",
    "exp"
  ],
  "claims": {
    "pubkey_hash": "1234567890...",
    "sub_hash": "9876543210...",
    "nonce_hash": "1111111111...",
    "iss_hash": "2222222222...",
    "aud_hash": "3333333333...",
    "exp": "1716000000"
  },
  "prover": "rapidsnark",
  "provingTimeSeconds": 4.2
}

PUBLIC_SIGNALS

[0] pubkey_hashPoseidon hash of the RSA modulus chunks
[1] sub_hashHash of the JWT 'sub' claim (user ID)
[2] nonce_hashHash of the 'nonce' claim (session binding)
[3] iss_hashHash of the 'iss' claim (token issuer)
[4] aud_hashHash of the 'aud' claim (client ID)
[5] expJWT expiration timestamp (plaintext)

ERROR_RESPONSES

400Missing 'jwt'JWT field not provided in request body
400Missing 'modulus'Modulus field not provided
400Invalid JWT structureJWT does not have 3 dot-separated parts
400JWT expiredToken exp claim is in the past
400Missing required claim: XJWT payload missing sub/iss/aud/nonce/exp
400JWT too largeSigned message exceeds circuit max length (960 bytes)
500Internal prover errorWitness generation or proof computation failed

CIRCUIT_DETAILS

The jwt_verify.circom circuit performs the following operations inside the ZK proof:

  • >RSA signature verification (2048-bit, 17 x 121-bit limbs)
  • >SHA-256 hash check of the signed JWT message
  • >Base64url decoding of the JWT payload
  • >Extraction and hashing of sub, iss, aud, nonce claims
  • >Plaintext output of exp for on-chain expiration checks
MAX_MSG_LEN
1024 bytes
MAX_PAYLOAD
768 bytes
RSA_LIMBS
17 x 121 bits
CONSTRAINTS
~2.5M

SMART_CONTRACTS

On-chain verification uses a Garaga-generated Groth16 verifier contract deployed on Starknet. The contract exposes a single function:

cairo
fn verify_groth16_proof_bn254(
    full_proof_with_hints: Span<felt252>
) -> Result<Span<u256>, felt252>

DEPLOYED_ADDRESSES

STARKNET_SEPOLIA
0x03cb5a1c747cbdf80e6051f8c2a68bbbbf40b4bcfd8dc4efe3f84fd15dae77dd.
STARKNET_MAINNET
0x02bd46d2be8ef3a804da2b38cfcfecdc04cf16f0f791d36780a6026f9c71fcb1

INTEGRATION_GUIDE

1. FETCH_JWKS

Retrieve the RSA public key from the OIDC provider's JWKS endpoint and extract the modulus.

typescript
// Fetch Google's JWKS and extract the modulus
const jwks = await fetch(
  "https://www.googleapis.com/oauth2/v3/certs"
).then(r => r.json());

const key = jwks.keys.find(k => k.kid === jwtHeader.kid);
const modulus = BigInt(
  "0x" + Buffer.from(key.n, "base64url").toString("hex")
).toString();

2. GENERATE_PROOF

typescript
const response = await fetch("https://api.cerebro.cavos.xyz/prove", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ jwt: idToken, modulus }),
});

const { proof, publicSignals, claims } = await response.json();

3. VERIFY_ON_CHAIN

Serialize the proof using Garaga's calldata format and call the verifier contract:

typescript
// Garaga serialization order:
// [pi_a.x, pi_a.y, pi_b[0][1], pi_b[0][0],
//  pi_b[1][1], pi_b[1][0], pi_c.x, pi_c.y]
// Then append public signals

const tx = await account.execute({
  contractAddress: VERIFIER_ADDRESS,
  entrypoint: "verify_groth16_proof_bn254",
  calldata: fullProofWithHints,
});

4. CURL_EXAMPLE

bash
curl -X POST https://api.cerebro.cavos.xyz/prove \
  -H "Content-Type: application/json" \
  -d '{
    "jwt": "eyJhbGciOiJSUzI1NiIs...",
    "modulus": "21387645123490812039..."
  }'
CEREBRO_DOCUMENTATION // v1.0