@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
text/typescript
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)
}
}