Get started

A draw you can check.

Run one provably fair draw in four steps, with no account or API key. The server commits first, you choose the inputs, and your browser checks the result.

JavaScript is optional. Read each open request and replay instruction below; they include the complete manual path.

Guided commit-reveal integration

01 · Commit

Make the promise first.

A public fingerprint is made before the draw settings are chosen. Save the returned record; it makes later changes easy to spot.

Commitment ID
Created by POST /api/commit
Server hash
Published before client input
Expires
Reveal within the returned TTL
No account or API key needed
Developer example: create the commitment
curl -X POST "https://api.provable.io/api/commit"
const response = await fetch("https://api.provable.io/api/commit", {
  method: "POST"
});
const commitment = await response.json();

// Save this complete JSON record before choosing clientSeed, min, or max.
console.log(JSON.stringify(commitment, null, 2));
02 · Confirm inputs

Choose what everyone can see.

Your client seed is simply a phrase you choose. It appears in the proof alongside the inclusive range.

Count: 1 result. This walkthrough intentionally makes one draw.

Create a commitment first, then review these values.

03 · Reveal once

Check, then reveal.

Review the request before sending it. A successful reveal uses this commitment once and cannot be repeated.

For the manual request, replace COMMIT_ID with the saved commitment’s commitId, and use your agreed clientSeed, min, and max. Send it before expiresAt. Save the entire response; a second successful reveal is not possible.

Review the locked request

Client seed: summer-draw-24 · Count: 1 result · Range: 1–100 inclusive

This uses the commitment once. Do not reveal until everyone accepts these visible values. An unused expired commitment must be restarted.

Complete the first two stages to continue
Drawn — not yet verified—

One result from the locked inclusive range.

Developer example: reveal the result once
curl -X POST "https://api.provable.io/api/reveal" \
  -H "Content-Type: application/json" \
  -d '{"commitId":"COMMIT_ID","clientSeed":"summer-draw-24","endpoint":"ints","params":{"count":1,"min":1,"max":100}}'
const response = await fetch("https://api.provable.io/api/reveal", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    commitId: "COMMIT_ID",
    clientSeed: "summer-draw-24",
    endpoint: "ints",
    params: { count: 1, min: 1, max: 100 }
  })
});
const proof = await response.json();
console.log(JSON.stringify(proof, null, 2));
Open the complete proof JSON returned by /api/reveal
complete proof JSON
After /api/reveal, save the complete JSON response here. Use its exact values below; do not shorten or reconstruct the proof.
04 · Verify

Now check it yourself.

Your browser checks the original commitment and recalculates the number. A server lookup does not replace these local checks.

Drawn — not yet verified—

One result from the locked inclusive range.

1. Commitment integrityWaitingHash the revealed server seed and compare it with the originally saved server hash.
2. Independent numeric replayWaitingReplay the canonical HMAC byte stream and integer mapping locally.
Reveal a result first
Open the exact proof JSON used for verification
complete proof JSON
After /api/reveal, save the complete JSON response here. Use its exact values below; do not shorten or reconstruct the proof.
Replay this exact proof in Node.js

Save the complete commitment response as commitment.json before choosing inputs, and the full reveal response as proof.json. Save the script below as replay-proof.mjs, set its three locked inputs to the values you recorded before reveal, then run:

npm install @provableio/provable-core
node replay-proof.mjs
import assert from "node:assert/strict";
import crypto from "node:crypto";
import { readFileSync } from "node:fs";
import { Provable } from "@provableio/provable-core";

const commitment = JSON.parse(readFileSync("commitment.json", "utf8"));
const proof = JSON.parse(readFileSync("proof.json", "utf8"));

// Insert the inputs you independently recorded before reveal.
const lockedClientSeed = "summer-draw-24";
const lockedMin = 1;
const lockedMax = 100;

assert.equal(proof.commitId, commitment.commitId);
assert.equal(proof.serverHash, commitment.serverHash);
assert.equal(proof.endpoint, "ints");
assert.equal(Number(proof.count), 1);
assert.equal(proof.clientSeed, lockedClientSeed);
assert.equal(proof.min, lockedMin);
assert.equal(proof.max, lockedMax);
assert.equal(
  crypto.createHash("sha256").update(proof.serverSeed).digest("hex"),
  commitment.serverHash
);
const replayed = Provable(() => {})({
  serverSeed: proof.serverSeed,
  clientSeed: proof.clientSeed,
  cursor: proof.cursor,
  nonce: proof.nonce
}).ints(1, proof.max - proof.min + 1, proof.min);
assert.deepEqual(replayed, proof.outcome);

Optional record lookups

Lookup confirms server records; it is not a local check.

Verification is complete only when both local checks pass.