Quick Start
Verify a backend-signed message on Solana in 5 minutes
This guide walks you through verifying a backend-signed message on-chain. By the end, you’ll understand the core Range verification flow.
Prerequisites
Section titled “Prerequisites”- Solana CLI installed
- Anchor installed
- Node.js 18+
- A Solana wallet with devnet SOL
Overview
Section titled “Overview”The verification flow has three steps:
- Initialize Settings - Create a Settings PDA with your trusted signer’s public key
- Backend Signs Message - Your backend creates
{timestamp}_{pubkey}and signs it - Verify On-chain - User submits transaction with the signature and message
Step-by-Step
Section titled “Step-by-Step”-
Clone and Build the Range Program
Terminal window git clone https://github.com/ZKLSOL/range.gitcd rangeanchor build -
Generate TypeScript Client
Range uses Codama to generate type-safe clients from the IDL.
Terminal window # Install dependenciesyarn install# The client is pre-generated in codama-ts-range/# To regenerate after IDL changes:yarn codama -
Initialize Settings
Create a Settings account that stores your trusted signer’s public key:
import { Connection, Keypair } from '@solana/web3.js';import { buildInitializeSettingsInstruction } from './codama-ts-range-custom';const connection = new Connection('https://api.devnet.solana.com');const admin = Keypair.generate(); // Your admin keypairconst rangeSigner = Keypair.generate(); // Backend's signing keypairconst instruction = await buildInitializeSettingsInstruction({admin: admin.publicKey,rangeSigner: rangeSigner.publicKey,windowSize: 60n, // 60 seconds validity window});// Sign and send transaction... -
Backend: Sign a Message
Your backend creates a time-bound message and signs it:
import nacl from 'tweetnacl';function createSignedMessage(userPubkey: string, signerKeypair: Keypair) {const timestamp = Math.floor(Date.now() / 1000);const message = `${timestamp}_${userPubkey}`;const signature = nacl.sign.detached(Buffer.from(message),signerKeypair.secretKey);return {signature: new Uint8Array(signature),message: Buffer.from(message),timestamp,};} -
Verify On-chain
The user includes the backend’s signature in their transaction:
import { buildVerifyRangeInstruction } from './codama-ts-range-custom';// User receives signature and message from backendconst { signature, message } = await fetchFromBackend(userPubkey);const instruction = await buildVerifyRangeInstruction({signer: userPubkey, // Transaction signer (must match message)admin: settingsAdminPubkey, // Admin who created the Settingssignature: signature,message: message,});// Sign and send transaction...If verification succeeds, the program emits a
VerificationSuccessevent.
What Gets Verified
Section titled “What Gets Verified”The on-chain program performs these checks:
| Check | What It Validates | Error If Failed |
|---|---|---|
| Signature | Ed25519 signature against range_signer | CouldntVerifySignature |
| Timestamp | Within window_size of current time | TimestampOutOfWindow |
| Pubkey | Message pubkey matches transaction signer | WrongSigner |
Complete Example
Section titled “Complete Example”See the tests/range.ts file for a complete working example including:
- Settings initialization
- Message signing
- Verification
- Error handling
Next Steps
Section titled “Next Steps”- Client Setup (Codama) - Detailed guide on generating clients
- Architecture - Understanding the full verification flow
- verify_range Reference - Complete instruction documentation