Add verifiable randomness to your app
The SDK wraps contract calls, canonical specs, Merkle roots, drand verification, TAP-02 circuit replay and proof checks behind a viem-first API.
What is in the SDK
- createTapeRNG: a viem publicClient/walletClient wrapper for publish, finalize and proof.
- buildDraw: deterministic canonical JSON, participant Merkle root, prizeSpecHash and specHash.
- createDrawAndReveal: one transaction for create + reveal, with a future drand quicknet round and a fixed revealBlock.
- getProof / verifyProof: recompute the pre-seed, circuit output, final seed, Fisher-Yates winners and prize slots.
- TAP-02 interpreter: decode/encode/load/beat/pack/unpack/evalNetlist/stepNetlist, a strict port of the official reference.py.
- drand: multi-endpoint fetchBeacon, offline BLS12-381 verifyBeacon, and sha256(signature) validation.
- CLI: verify proof.json with exit code 0/1 for CI or third-party replay.
Create a client
chainId 1952 uses the testnet MockCPU with real drand; on mainnet 196 set circuitMode to tapeout and use the real Keccak IP Core.
import { createTapeRNG } from "@taperng/sdk";
import { createPublicClient, http } from "viem";
const publicClient = createPublicClient({
transport: http("https://testrpc.xlayer.tech/terigon"),
});
// For writes: createWalletClient({ account, chain, transport: custom(window.ethereum) })
const taperng = createTapeRNG({
publicClient,
walletClient, // optional for read-only
addresses: { lottery, tapeRng },
chainId: 1952,
beaconMode: "drand", // or "none"
circuitMode: "mock", // "tapeout" on mainnet
});Create and reveal a draw
participantCount and prizes are exactly the form inputs; the SDK generates Participant 001…N, the canonical spec and the flattened prize slots.
const draw = await taperng.createDrawAndReveal({
name: "Annual Party Lottery",
participantCount: 100,
prizes: [
{ name: "50 CNY Red Packet", quantity: 3 },
{ name: "100 CNY Red Packet", quantity: 2 },
{ name: "Xiaomi Laptop", quantity: 1 },
{ name: "iPhone 17", quantity: 1 },
],
locale: "en",
});
console.log(draw.drawId, draw.built.participantsRoot);
console.log(draw.built.specHash, draw.revealBlock, draw.beaconRound);Finalize and verify
finalizeDraw reads the draw's beaconRound, fetches the 48-byte signature from drand and submits it. verifyProof is a pure replay of the proof JSON.
await taperng.finalizeDraw(draw.drawId);
const proof = await taperng.getProof(draw.drawId);
const result = taperng.verifyProof(proof);
if (!result.ok) {
throw new Error("proof failed");
}
console.log(result.checks);Independent verification
cd taperng/packages/sdk
./node_modules/.bin/tsx src/cli.ts verify proof.jsonReplay the TapeOut circuit offline
The proof carries the netlist. In mainnet tapeout mode the SDK loads/evals the circuit locally and compares the output to circuitMix. Testnet MockCPU skips TAP-02 replay explicitly.
import { load, evalNetlist } from "@taperng/sdk";
// TAP-02: NAND / LATCH / REF, LSB-first packing
const circuit = load(netlist, nIn, nOut);
const output = evalNetlist(circuit, input);Verify the beacon without RPC
verifyBeacon uses @noble/curves for BLS12-381; the message is sha256(uint64_be(round)) and randomness must equal sha256(signature).
import { fetchBeacon, verifyBeacon } from "@taperng/sdk";
const beacon = await fetchBeacon("latest");
const { ok, randomness } = verifyBeacon(beacon);
// randomness === sha256(beacon.signature)What the proof verifies
- Context hash: chainId, lottery, participantsRoot, counts, prizeSpecHash and specHash.
- Organizer commitment: seed, context, organizer, chainId and TapeRNG address.
- Pre-seed: requestId, blockHash, beaconRandomness, CPU/circuit/netlistHash and every fallback flag.
- TapeOut circuit output and circuitMix; mainnet tapeout mode replays the netlist locally.
- finalSeed and the partial Fisher-Yates winners / prizeSlots.
- sha256(drand signature) equals beaconRandomness.
Security assumptions and limits
- The contracts and the vendored BLS library are unaudited; this is a hackathon demo, not a production security claim.
- TapeOut is not deployed on testnet, so the public demo uses MockCPU; only mainnet/fork uses the real Keccak IP Core.
- Organizer + X Layer sequencer collusion remains a residual risk; a fixed revealBlock and drand reduce it but do not eliminate it.
- drand relies on the League of Entropy threshold assumption; blockhash is only an additional/fallback source.
- Prizes are metadata only; there is no on-chain escrow, claim or settlement.
- The proof embeds participants, prizes and the netlist; privacy use cases need an extra disclosure/hashing strategy.
How the demo relates to the SDK
The home form demonstrates the same flow: buildDraw → createDrawAndReveal → finalizeDraw → proof. The web app keeps an equivalent derivation for inline verification without a backend; third-party apps should reuse packages/sdk directly.