Quickstart
From nothing to a signature. This assumes you have created an app in the console and deployed a signing group — if you have not, do that first; it takes a few minutes.
1. Install the SDK
npm install @oleary-labs/signet-sdk viem
Client-side zero-knowledge proving additionally needs @noir-lang/noir_js, @aztec/bb.js, and @oleary-labs/signet-circuits. They are optional peer dependencies — skip them if you prove server-side.
2. Authenticate the user
The user signs in with OAuth. Their token never reaches the network: a zero-knowledge proof establishes that they hold a valid credential from an issuer your group trusts, bound to an ephemeral session key.
import { startGoogleOAuth, decodeIdToken, handleOAuthCallback } from "@oleary-labs/signet-sdk/oauth";
import { generateSessionKeypair } from "@oleary-labs/signet-sdk/session";
import { generateJWTProof, getJWTModulusBytes } from "@oleary-labs/signet-sdk/proof";
import { authenticateWithBootstrap } from "@oleary-labs/signet-sdk/bootstrap";
// Kick the user to Google.
await startGoogleOAuth({ clientId: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID! });
// …and back on your callback route:
const jwt = await handleOAuthCallback("/api/auth/token");
const claims = decodeIdToken(jwt);
const keypair = await generateSessionKeypair();
// The slow step — a few seconds, once per sign-in.
const { proof } = await generateJWTProof(jwt, keypair.publicKeyHex);
const modulus = await getJWTModulusBytes(jwt);
await authenticateWithBootstrap(
{ groupId: GROUP_ADDRESS, nodeUrls: NODE_URLS },
proof,
keypair.publicKeyHex,
claims,
modulus,
);Why the proof exists
3. Create the user’s key
Key generation is a distributed protocol: every operator ends up with one share, and no machine anywhere ever holds the whole key.
import { keygen } from "@oleary-labs/signet-sdk/keygen";
const key = await keygen(
{ groupId: GROUP_ADDRESS, nodeUrls: NODE_URLS },
keypair,
claims,
);
console.log(key.ethereumAddress); // the user's address
console.log(key.alreadyExisted); // true on every sign-in after the first4. Sign something
import { signSignRequest } from "@oleary-labs/signet-sdk/request";
const signed = await signSignRequest(keypair, claims, GROUP_ADDRESS, messageHash);
const res = await fetch(`${NODE_URLS[0]}/v1/sign`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ...signed, curve: "frost_secp256k1" }),
});
const { ethereum_signature } = await res.json();You contact one node. It coordinates the round with the rest of the group over their own peer-to-peer mesh, and returns when a quorum has contributed. Every participant independently re-checks your session signature before it takes part — the node you happened to call cannot vouch for you to the others.
5. Constrain what the key may sign
A key with no scope will sign any hash you hand it. For anything an agent or a background job touches, mint a scoped sub-key instead — bound to one chain, one contract, and one message type, enforced by every operator.
Running it all locally
# Terminal 1 — Anvil, contracts, three nodes, a bootstrap group cd signet-protocol && devnet/start.sh # Terminal 2 — the bundler and paymaster cd signet-min-bundler && scripts/devnet-setup.sh && ./bundler --config bundler.toml # Terminal 3 — this platform cd platform/backend && go run ./cmd/server cd platform/web && npm run dev