UNPKG

@cryptkeeperzk/rlnjs

Version:

Client library for generating and using RLN ZK proofs, is a forked repo from main Rate-Limiting-Nullifier/rlnjs to make it work with Cryptkeeper Browser Extension using `@cryptkeeperzk/snarkjs` and `@cryptkeeperzk/ffjavascript`

191 lines (174 loc) 5.13 kB
import { MerkleProof } from '@zk-kit/incremental-merkle-tree' import { groth16 } from '@cryptkeeperzk/snarkjs' // Types import { StrBigInt, VerificationKey, Proof } from './types' import { calculateExternalNullifier } from './common' /** * Public signals of the SNARK proof. */ export type RLNPublicSignals = { x: StrBigInt; externalNullifier: StrBigInt; y: StrBigInt; root: StrBigInt; nullifier: StrBigInt; } /** * SNARK proof that contains both proof and public signals. * Can be verified directly by a SNARK verifier. */ export type RLNSNARKProof = { proof: Proof; publicSignals: RLNPublicSignals; } /** * RLN full proof that contains both SNARK proof and other information. * The proof is valid iff the epoch and rlnIdentifier match externalNullifier, * and the snarkProof is valid. */ export type RLNFullProof = { snarkProof: RLNSNARKProof; epoch: bigint; rlnIdentifier: bigint; } /** * RLN witness that contains all the inputs needed for proof generation. */ export type RLNWitness = { identitySecret: bigint; userMessageLimit: bigint; messageId: bigint; // Ignore `no-explicit-any` because the type of `identity_path_elements` in zk-kit is `any[]` pathElements: any[]; // eslint-disable-line @typescript-eslint/no-explicit-any identityPathIndex: number[]; x: string | bigint; externalNullifier: bigint; } /** * Wrapper of RLN circuit for proof generation. */ export class RLNProver { constructor( readonly wasmFilePath: string | Uint8Array, readonly finalZkeyPath: string | Uint8Array, ) { } /** * Generates a RLN full proof. * @param args The parameters for creating the proof. * @returns The full SnarkJS proof. */ public async generateProof(args: { rlnIdentifier: bigint; identitySecret: bigint; userMessageLimit: bigint; messageId: bigint; merkleProof: MerkleProof; x: bigint; epoch: bigint; }): Promise<RLNFullProof> { const witness: RLNWitness = { identitySecret: args.identitySecret, userMessageLimit: args.userMessageLimit, messageId: args.messageId, pathElements: args.merkleProof.siblings, identityPathIndex: args.merkleProof.pathIndices, x: args.x, externalNullifier: calculateExternalNullifier(args.epoch, args.rlnIdentifier), } const { proof, publicSignals } = await groth16.fullProve( witness, this.wasmFilePath, this.finalZkeyPath, null, ) const snarkProof: RLNSNARKProof = { proof, publicSignals: { y: publicSignals[0], root: publicSignals[1], nullifier: publicSignals[2], x: publicSignals[3], externalNullifier: publicSignals[4], }, } return { snarkProof, epoch: args.epoch, rlnIdentifier: args.rlnIdentifier, } } } /** * Wrapper of RLN circuit for verification. */ export class RLNVerifier { constructor(readonly verificationKey: VerificationKey) { } /** * Verifies a RLN full proof. * @param rlnIdentifier unique identifier for a RLN app. * @param fullProof The SnarkJS full proof. * @returns True if the proof is valid, false otherwise. * @throws Error if the proof is using different parameters. */ public async verifyProof(rlnIdentifier: bigint, rlnRullProof: RLNFullProof): Promise<boolean> { const expectedExternalNullifier = calculateExternalNullifier( BigInt(rlnRullProof.epoch), rlnIdentifier, ) const actualExternalNullifier = rlnRullProof.snarkProof.publicSignals.externalNullifier if (expectedExternalNullifier !== BigInt(actualExternalNullifier)) { throw new Error( `External nullifier does not match: expectedExternalNullifier=${expectedExternalNullifier}, ` + `actualExternalNullifier=${actualExternalNullifier}, epoch=${rlnRullProof.epoch}, ` + `this.rlnIdentifier=${rlnIdentifier}`, ) } const { proof, publicSignals } = rlnRullProof.snarkProof return groth16.verify( this.verificationKey, [ publicSignals.y, publicSignals.root, publicSignals.nullifier, publicSignals.x, publicSignals.externalNullifier, ], proof, ) } } type SNARKProof = { proof: Proof; publicSignals: StrBigInt[]; } /** * Wrapper of Withdraw circuit for proof generation. */ export class WithdrawProver { constructor( readonly wasmFilePath: string | Uint8Array, readonly finalZkeyPath: string | Uint8Array, ) { } async generateProof(args: { identitySecret: bigint; address: bigint }): Promise<SNARKProof> { return (await groth16.fullProve( args, this.wasmFilePath, this.finalZkeyPath, null, )) as SNARKProof } } /** * Wrapper of Withdraw circuit for verification. Since verifier is deployed * on-chain, this class mainly used for testing. */ export class WithdrawVerifier { constructor(readonly verificationKey: VerificationKey) { } async verifyProof(proof: SNARKProof): Promise<boolean> { return groth16.verify(this.verificationKey, proof.publicSignals, proof.proof) } }