fix: switching to @cryptkeeperzk/rlnjs and @cryptkeeperzk/semaphore (#690)

* fix: switching to @cryptkeeperzk/rlnjs and @cryptkeeperzk/semaphore

* fix: adding a new line in pnpm-workspace.yaml

* fix: upgrading @cryptkeeperzk/rlnjs and @cryptkeeperzk/semaphore packages

* fix: e2e tests

---------

Co-authored-by: isk <0xisk>
Co-authored-by: 0xmad <0xmad@users.noreply.github.com>
This commit is contained in:
0xisk
2023-08-18 18:47:46 +02:00
committed by GitHub
parent 6f72719f43
commit e80d9db21d
90 changed files with 290 additions and 9667 deletions

View File

@@ -83,7 +83,7 @@ jobs:
if: runner.os == 'Linux'
run: pnpm run build
env:
METAMASK_EXTENSION_ID: "fhohjionocjbacbejikmmnnloidaalnj"
METAMASK_EXTENSION_ID: "apabigcacjahmioldedonehjbfipdbkf"
- name: Build extension (Windows)
if: runner.os == 'Windows'

View File

@@ -81,7 +81,7 @@ jobs:
if: runner.os == 'Linux'
run: pnpm run build
env:
METAMASK_EXTENSION_ID: "fhohjionocjbacbejikmmnnloidaalnj"
METAMASK_EXTENSION_ID: "apabigcacjahmioldedonehjbfipdbkf"
- name: Build extension (Windows)
if: runner.os == 'Windows'

View File

@@ -88,8 +88,8 @@
},
"dependencies": {
"@cryptkeeperzk/providers": "workspace:^",
"@cryptkeeperzk/rln-proof": "workspace:^",
"@cryptkeeperzk/semaphore-proof": "workspace:^",
"@cryptkeeperzk/rlnjs": "^3.1.10",
"@cryptkeeperzk/semaphore-proof": "^3.10.3",
"@cryptkeeperzk/types": "workspace:^",
"@cryptkeeperzk/zk": "workspace:^",
"@emotion/styled": "^11.11.0",

View File

@@ -1,5 +1,5 @@
import { RPCAction } from "@cryptkeeperzk/providers";
import { RLNSNARKProof } from "@cryptkeeperzk/rln-proof";
import { RLNSNARKProof } from "@cryptkeeperzk/rlnjs";
import { generateProof } from "@cryptkeeperzk/semaphore-proof";
import { getMerkleProof } from "@cryptkeeperzk/zk";
import { omit } from "lodash";

View File

@@ -17,7 +17,7 @@
"dependencies": {
"@cryptkeeperzk/providers": "workspace:^",
"@cryptkeeperzk/types": "workspace:^",
"@semaphore-protocol/identity": "^3.10.1",
"@cryptkeeperzk/semaphore-identity": "^3.10.3",
"bigint-conversion": "^2.4.1",
"ethers": "^6.6.3",
"react": "^18.2.0",

View File

@@ -1,6 +1,6 @@
/* eslint-disable no-console */
import { cryptkeeperConnect, type CryptKeeperInjectedProvider } from "@cryptkeeperzk/providers";
import { Identity } from "@semaphore-protocol/identity";
import { Identity } from "@cryptkeeperzk/semaphore-identity";
import { bigintToHex } from "bigint-conversion";
import { encodeBytes32String } from "ethers";
import { useState, useEffect, useCallback } from "react";

View File

@@ -32,8 +32,8 @@
},
"dependencies": {
"@cryptkeeperzk/zk": "workspace:^",
"@semaphore-protocol/group": "^3.10.1",
"@semaphore-protocol/identity": "^3.10.1",
"@cryptkeeperzk/semaphore-group": "^3.10.3",
"@cryptkeeperzk/semaphore-identity": "^3.10.3",
"@zk-kit/incremental-merkle-tree": "^1.1.0",
"bigint-conversion": "^2.4.1",
"cors": "^2.8.5",

View File

@@ -1,6 +1,6 @@
import { BigNumberish } from "@cryptkeeperzk/semaphore-group";
import { Identity } from "@cryptkeeperzk/semaphore-identity";
import { generateMerkleProof } from "@cryptkeeperzk/zk";
import { BigNumberish } from "@semaphore-protocol/group";
import { Identity } from "@semaphore-protocol/identity";
import { MerkleProof } from "@zk-kit/incremental-merkle-tree";
import { bigintToHex, hexToBigint } from "bigint-conversion";
import cors from "cors";

View File

@@ -1,35 +0,0 @@
module.exports = {
extends: ['airbnb-typescript/base', "plugin:@typescript-eslint/recommended", "plugin:import/recommended", "plugin:import/typescript"],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
root: true,
parserOptions: {
project: './tsconfig.json',
},
rules: {
semi: ["warn", "never"],
"import/extensions": ["warn", "never"],
"@typescript-eslint/semi": ["warn", "never"],
"indent": ["warn", 2]
},
settings: {
"import/parsers": {
"@typescript-eslint/parser": [".ts", ".tsx"]
},
"import/resolver": {
typescript: {
alwaysTryTypes: true,
},
node: {
extensions: [".js", ".jsx", ".ts", ".tsx"],
},
},
"import/extensions": [
".js",
".jsx",
".ts",
".tsx"
]
}
};

View File

@@ -1,3 +0,0 @@
node_modules/
coverage
zkeyFiles

View File

@@ -1,7 +0,0 @@
Copyright 2021-2023 Ethereum Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,332 +0,0 @@
# Rate Limiting Nullifier Javascript / Typescript Library
## Contents
- [Rate Limiting Nullifier Javascript / Typescript Library](#rate-limiting-nullifier-javascript-typescript-library)
* [Contents](#contents)
* [Description](#description)
* [Install](#install)
+ [Build circuits and get the parameter files](#build-circuits-and-get-the-parameter-files)
- [With script (Recommended)](#with-script-recommended)
- [Manually clone and build the circuits](#manually-clone-and-build-the-circuits)
* [Usage](#usage)
+ [Initializing an RLN instance](#initializing-an-rln-instance)
+ [Accessing Identity and Identity Commitment](#accessing-identity-and-identity-commitment)
+ [Registering](#registering)
+ [Generating a proof](#generating-a-proof)
+ [Withdrawing](#withdrawing)
+ [Verifying a proof](#verifying-a-proof)
+ [Saving a proof](#saving-a-proof)
+ [Slashing a user](#slashing-a-user)
* [Example](#example)
* [Tests](#tests)
* [Bugs, Questions & Features](#bugs-questions-features)
* [License](#license)
## Description
RLN (Rate-Limiting Nullifier) is a zk-gadget/protocol that enables spam prevention in anonymous environments.
The core of RLN is in the [circuit logic](https://github.com/Rate-Limiting-Nullifier/circom-rln), documentation [here](https://rate-limiting-nullifier.github.io/rln-docs/protocol_spec.html#technical-side-of-rln). RLNjs provides easy management of the registry and proof creation.
[`RLN`](./src/rln.ts) class is the core of RLNjs. It allows user to generate proofs, verify proofs, and detect spams. Also, user can register to RLN, withdraw, and slash spammers.
| _Tests Ran on an M2 Macbook_ | Time |
| ---------------------------- | ------ |
| RLN Proof | ~800ms |
| RLN Proof Verification | ~130ms |
| Withdraw Proof | ~260ms |
| Withdraw Proof Verification | ~145ms |
## Install
Install rlnjs with npm:
```bash
npm install rlnjs
```
### Build circuits and get the parameter files
Circuit parameter files `circuit.wasm`, `final.zkey`, and `verification_key.json` are needed when instantiating a [RLN](src/rln.ts) instance. You can choose to build the circuits with script or manually.
#### With script (Recommended)
Run the script [scripts/build-zkeys.sh](scripts/build-zkeys.sh) and it will build the circuits for you.
```bash
./scripts/build-zkeys.sh
```
In the project root, you should can see the zkey files in the `./zkeyFiles` folder.
```bash
$ tree ./zkeyFiles
zkeyFiles
├── rln
│ ├── circuit.wasm
│ ├── final.zkey
│ └── verification_key.json
└── withdraw
├── circuit.wasm
├── final.zkey
└── verification_key.json
```
#### Manually clone and build the circuits
> Circom needs to be installed, please see this [link](https://docs.circom.io/getting-started/installation/) for installation instructions.
```bash
git clone https://github.com/Rate-Limiting-Nullifier/circom-rln.git &&
cd circom-rln
```
> Make sure the depth of the merkle tree are the same in both rlnjs and rln-circuits, otherwise verification will fail. You need to rebuild the circuits every time the circuit is changed.
```bash
npm i &&
npm run build
```
The previous step should have produced the following files:
```bash
$ tree zkeyFiles
zkeyFiles
├── rln
│ ├── circuit.config.toml
│ ├── circuit.wasm
│ ├── final.zkey
│ └── verification_key.json
└── withdraw
├── circuit.config.toml
├── circuit.wasm
├── final.zkey
└── verification_key.json
```
## Usage
### Initializing an RLN instance
#### Create RLN instance with the contract registry
The following snippet creates RLN instance with default settings.
```typescript
const rlnIdentifier = BigInt(5566)
const contractAddress = "0x..."
const contractAtBlock = 12345678
const provider = new ethers.JsonRpcProvider(url)
const signer = await provider.getSigner(0)
// Create an RLN instance with the contract registry.
// ethers provider and the contract address are both required then.
const rln = RLN.createWithContractRegistry({
/* These parameters are required */
rlnIdentifier, // The unique id representing your application
provider, // ethers.js provider
contractAddress, // RLN contract address
/* These parameters are optional */
contractAtBlock, // The block number at which the RLN contract was deployed. If not given, default is 0
signer, // ethers.js signer. If not given, users won't be able to execute write operations to the RLN contract
// ... See all optional parameters in RLN constructor in src/rln.ts
})
```
Custom options can be passed to the RLN instance. Note that default tree depth is `20`. If you're using a tree depth other than `20`, you need to the circuit parameters when creating the RLN instance. See [RLN constructor](src/rln.ts) for all options.
```typescript
import path from "path"
import { ethers } from "ethers"
import { Identity } from '@semaphore-protocol/identity'
import { RLN, IRLNRegsitry, ContractRLNRegistry } from "rlnjs"
// Assume you have built `rln.circom` and `withdraw.circom` and have placed them under the folder ./zkeyFiles/rln
// and ./zkeyFiles/withdraw respectively.
/* rln circuit parameters */
const rlnZkeyFilesDir = path.join("zkeyFiles", "rln");
// zkeyFiles/rln/verification_key.json
const rlnVerificationKey = JSON.parse(
fs.readFileSync(path.join(rlnZkeyFilesDir, "verification_key.json"), "utf-8")
)
// zkeyFiles/rln/circuit.wasm
const rlnWasmFilePath = path.join(rlnZkeyFilesDir, "circuit.wasm")
// zkeyFiles/rln/final.zkey
const rlnFinalZkeyPath = path.join(rlnZkeyFilesDir, "final.zkey")
/* withdraw circuit parameters */
const withdrawZkeyFilesDir = path.join("zkeyFiles", "withdraw")
// zkeyFiles/withdraw/circuit.wasm
const withdrawWasmFilePath = path.join(withdrawZkeyFilesDir, "circuit.wasm")
// zkeyFiles/withdraw/final.zkey
const withdrawFinalZkeyPath = path.join(withdrawZkeyFilesDir, "final.zkey")
const rlnIdentifier = BigInt(5566)
const treeDepth = 16
const provider = new ethers.JsonRpcProvider(url)
const contractAddress = "0x..."
const signer = await provider.getSigner(0)
const identity = new Identity("1234")
// Create an RLN instance with the contract registry.
// ethers provider and the contract address are both required then.
const rln = RLN.createWithContractRegistry({
/* These parameters are required */
rlnIdentifier, // The unique id representing your application
provider, // ethers.js provider
contractAddress, // RLN contract address
/* These parameters are optional */
contractAtBlock, // The block number at which the RLN contract was deployed. If not given, default is 0
identity, // the semaphore identity. If not given, a new identity is created
signer, // ethers.js signer. If not given, users won't be able to execute write operations to the RLN contract
treeDepth, // The depth of the merkle tree. Default is 20
wasmFilePath: rlnWasmFilePath, // The path to the rln circuit wasm file. If not given, `createProof` will not work
finalZkeyPath: rlnFinalZkeyPath, // The path to the rln circuit final zkey file. If not given, `createProof` will not work
verificationKey: rlnVerificationKey, // The rln circuit verification key. If not given, `verifyProof` will not work
withdrawWasmFilePath, // The path to the withdraw circuit wasm file. If not given, `withdraw` will not work
withdrawFinalZkeyPath, // The path to the withdraw circuit final zkey file. If not given, `withdraw` will not work
// ... See all optional parameters in RLN constructor in src/rln.ts
})
```
#### Create RLN instance with other types of registries
You can also initialize an RLN instance with the constructor, but you need to provide a registry. This is particularly useful if you want to use a custom registry instead of the contract registry. For testing, you could use `MemoryRLNRegistry` and let different RLN instances use the same registry.
```typescript
const registry: IRLNRegistry = new MemoryRLNRegistry(rlnIdentifier, treeDepth)
const rln1 = new RLN({rlnIdentifier, registry, treeDepth})
const rln2 = new RLN({rlnIdentifier, registry, treeDepth})
// ...Do something with rln1 and rln2
```
#### Prover-only and Verifier-only modes
RLN instance must at least be initialized with either `wasmFilePath` and `finalZkeyPath`, or `verificationKey`. If you only provide `wasmFilePath` and `finalZkeyPath`, you can only generate proofs. You will get an error if you try to verify a proof. If you only provide `verificationKey`, you can only verify proofs. You will get an error if you try to generate a proof. If you provide both, you can both generate and verify proofs.
```typescript
const rlnProverOnly = new RLN({
rlnIdentifier,
registry,
wasmFilePath: rlnWasmFilePath,
finalZkeyPath: rlnFinalZkeyPath,
// Missing `verificationKey`
withdrawWasmFilePath,
withdrawFinalZkeyPath,
})
const rlnVerifierOnly = new RLN({
rlnIdentifier,
registry,
// Missing `wasmFilePath` and `finalZkeyPath`
verificationKey: rlnVerificationKey,
withdrawWasmFilePath,
withdrawFinalZkeyPath,
})
```
### Accessing Identity and Identity Commitment
When an RLN instance is initialized without `identity` given, it creates an `Identity` for you. You can access identity and its commitment using `rln.identity` and `rln.identityCommitment` respectively.
```typescript
// Example of accessing the generated identity commitment
const identity = rln.identity
const identityCommitment = rln.identityCommitment
```
### Registering
```typescript
const messageLimit = BigInt(1);
// This registers the identity commitment to the registry
// If you're using ContractRLNRegistry, you will send a transaction to the RLN contract, sending tokens, and get registered.
await rln.register(messageLimit);
console.log(await rln.isRegistered()) // true
```
### Generating a proof
```typescript
const epoch = BigInt(123)
const message = "Hello World"
const proof = await rln.createProof(epoch, message);
```
You can generate a proof for an epoch and a message by calling `rln.createProof()`. For the same epoch, you can only generate up to `messageLimit` proofs, each of them with a unique `messageId` within the range `[0, messageLimit-1]`. Message id is not required here because after registering, there is a message id counter inside to avoid reaching the rate limit.
> Note that the built-in [MemoryMessageIDCounter](./src/message-id-counter.ts) is not persistent. If you stop the application and restart it in the same epoch, you might risk spamming. If you want to persist the message id counter, you can implement your own message id counter by implementing the [IMessageIDCounter](./src/message-id-counter.ts) interface and set it with `rln.setMessageIDCounter()`.
### Withdrawing
```typescript
// This withdraws the identity commitment from the registry.
// If you're using ContractRLNRegistry, you will send a transaction to the RLN contract, and get the tokens back.
await rln.withdraw();
// after withdrawing, you still need to wait for the freezePeriod in order to release the withdrawal
console.log(await rln.isRegistered()) // true
// If you're using ContractRLNRegistry, after `freezePeriod` (i.e. `freezePeriod + 1` blocks), you can release the withdrawal and successfully get the funds back
await rln.releaseWithdrawal();
console.log(await rln.isRegistered()) // false
```
### Verifying a proof
```typescript
const proofResult = await rln.verifyProof(epoch, message, proof) // true or false
```
A proof can be invalid in the following conditions:
- Proof mismatches epoch, message, or rlnIdentifier
- The snark proof itself is invalid
### Saving a proof
User should save all proofs they receive to detect spams. You can save a proof by calling `rln.saveProof()`. The return value is an object indicating the status of the proof.
```typescript
const result = await rln.saveProof(proof)
// status can be VALID, DUPLICATE, BREACH.
// - VALID means the proof is successfully added to the cache
// - DUPLICATE means the proof is already saved before
// - BREACH means the added proof breaches the rate limit, in which case the `secret` is recovered and is accessible by `result.secret`
const status = result.status
// if status is "breach", you can get the secret by
const secret = result.secret
```
> 1. `verifyProof(epoch, message, proof)` and `saveProof(proof)` are different. `verifyProof` not only verifies the snark proof but ensure the proof matches `epoch` and `message`, while `saveProof()` does not verify the snark proof at all. `saveProof()` checks if the proof will spam and adds the proof to cache for future spam detection. If one wants to make sure the proof is for `epoch` and `message` and also detect spams, they should call both `verifyProof` and `saveProof`.
> 2. `saveProof` is not persistent. If you restart the application, you might fail to detect some spams. If you want to persist the proof cache, you can implement your own proof cache by implementing the [ICache](./src/cache.ts) interface and set it in the constructor.
### Slashing a user
```typescript
const slashReceiver = "0x0000000000000000000000000000000000001234"
await rln.slash(secret, receiver) // user using the secret gets slashed and the funds go to the receiver
```
If receiver is not given, the funds will go to the signer.
```typescript
await rln.slash(secret) // funds go to the signer
```
## Example
Please see the examples [here](./examples/). We have examples for [NodeJS](./examples/node/) and [browser](./examples/browser/).
## Tests
```bash
npm test
```
## Bugs, Questions & Features
If you find any bugs, have any questions, or would like to propose new features, feel free to open an [issue](https://github.com/Rate-Limiting-Nullifier/RLNjs/issues/new/).
## License
RLNjs is released under the [MIT license](https://opensource.org/licenses/MIT).

View File

@@ -1,45 +0,0 @@
# Example: use RLNjs in browser
The example go through the following steps:
1. Deploy the necessary contracts: verifier (the mock proof verifier since it's not yet generated), ERC20 token, and the RLN contract
2. Create a RLN instance `rln`, and demonstrate how to `register`, `createProof`, `verifyProof`, `withdraw`, and `releaseWithdrawal`.
3. Create a RLN instance `rlnAnother` and demonstrate how to slash a spammer. `rlnAnother` simply `register` and send more proofs than they should. `rln` can detect the spam by examine the output from `saveProof`. If the status of the output is BREACH, the secret is leaked. `rln` can use the recovered secret to slash the spammer by calling `slash`.
To install and run the example, follow the steps below.
1. Install the project
```bash
$ npm install
```
2. Run a local testing RPC
Here we use a hardhat node. If the RPC is not listening `http://localhost:8545`, you need to change the `url` in [config.ts](./src/configs.ts).
In a new terminal, run:
```bash
$ npx hardhat node
```
3. Run the web server
```bash
$ npm run test
...
Available on:
http://127.0.0.1:8080
http://192.168.50.66:8080
Hit CTRL-C to stop the server
```
4. Open the web page at `http://localhost:8080`. You should see the following output in the browser console.
```
Connecting to endpoint at http://localhost:8545
Deploying contracts...
...
Successfully breached rlnAnother's secret=xxx
Successfully slashed rlnAnother
```

View File

@@ -1,14 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Webpage</title>
</head>
<body>
<h1>Hello World!</h1>
<!-- Include your JavaScript file here -->
<script type="module" src="index.mjs"></script>
</body>
</html>

View File

@@ -1,26 +0,0 @@
{
"name": "example-browser",
"version": "1.0.0",
"description": "",
"main": "dist/index.mjs",
"module": "dist/index.mjs",
"scripts": {
"build": "rollup --config rollup.config.mjs",
"test": "npm run build && mkdir -p public && cp dist/index.mjs index.html public && cd public && npx http-server",
"clean": "rm -rf dist"
},
"author": "",
"license": "ISC",
"dependencies": {
"@rollup/plugin-commonjs": "^24.1.0",
"@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-node-resolve": "^15.0.2",
"@rollup/plugin-replace": "^5.0.2",
"rollup": "^3.20.2",
"rollup-plugin-cleaner": "^1.0.0",
"rollup-plugin-polyfill-node": "^0.12.0",
"rollup-plugin-typescript2": "^0.34.1",
"rollup-plugin-visualizer": "^5.9.0",
"rlnjs": "^3.1.5"
}
}

View File

@@ -1,65 +0,0 @@
/* eslint-disable import/no-extraneous-dependencies */
import typescript from 'rollup-plugin-typescript2'
import { nodeResolve } from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import json from '@rollup/plugin-json'
import nodePolyfills from 'rollup-plugin-polyfill-node'
import replace from '@rollup/plugin-replace'
import { visualizer } from 'rollup-plugin-visualizer'
import cleaner from 'rollup-plugin-cleaner'
import * as fs from 'fs'
const input = 'src/index.ts'
const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf-8'))
const banner = `/**
* @module ${pkg.name}
* @version ${pkg.version}
* @file ${pkg.description}
* @copyright Ethereum Foundation 2022
* @license ${pkg.license}
* @see [Github]{@link ${pkg.homepage}}
*/`
const typescriptPlugin = typescript({
tsconfig: 'tsconfig.json',
useTsconfigDeclarationDir: true,
})
const visualizerPlugin = visualizer({
emitFile: true,
filename: 'stats.html',
template: 'sunburst',
})
const browserPlugins = [
typescriptPlugin,
replace({
// Replace `process.browser` with `true` to avoid `process is not defined` error
// This is because ffjavascript and snarkjs use `process.browser` to check if it's running in the browser,
// but process is undefined in the browser and referencing `process.browser` causes an error.
// Ref: https://github.com/iden3/ffjavascript/blob/e670bfeb17e80b961eab77e15a6b9eca8e31a0be/src/threadman.js#L43
'process.browser': JSON.stringify(true),
// To avoid unexpected behavior that the warning suggests.
'preventAssignment': true,
}),
// Resolve the import from node_modules.
// `browser: true` is required for `window` not to be undefined
// Ref: https://github.com/iden3/snarkjs/blob/782894ab72b09cfad4dd8b517599d5e7b2340468/src/taskmanager.js#L20-L24
nodeResolve({ browser: true }),
commonjs(),
json(),
// Replace node built-in modules with polyfills
nodePolyfills(),
]
export default {
input,
output: { file: pkg.module, format: 'es', banner },
plugins: [
...browserPlugins,
visualizerPlugin,
],
}

File diff suppressed because one or more lines are too long

View File

@@ -1,166 +0,0 @@
import { ethers } from "ethers";
import { MemoryCache, RLN, Status } from "test-rlnjs";
import { deployERC20, deployRLNContract, deployVerifier, treeDepth, url } from "./configs";
async function main() {
const rlnIdentifier = BigInt(5566)
const messageLimit = BigInt(1);
const message0 = "Hello World"
const message1 = "Hello World 2"
const epoch = BigInt(1234)
const signerTestERC20Amount = BigInt(100000000)
const slasher = "0x0000000000000000000000000000000000009876"
const rlnContractArgs = {
minimalDeposit: BigInt(100),
treeDepth: treeDepth,
feePercentage: BigInt(10),
feeReceiver: "0x0000000000000000000000000000000000006789",
freezePeriod: BigInt(1),
}
console.log(`Connecting to endpoint at ${url}`)
const provider = new ethers.JsonRpcProvider(url)
const signer = await provider.getSigner(0)
// Here we use a mock verifier since we don't have a proof verifier deployed yet.
console.log(`Deploying contracts...`)
const verifierContract = await deployVerifier(signer)
console.log(`Deployed mock verifier at ${await verifierContract.getAddress()}`)
const erc20Contract = await deployERC20(signer, signerTestERC20Amount)
console.log(`Deployed test ERC20 at ${await erc20Contract.getAddress()}`)
const rlnContract = await deployRLNContract(
signer,
await erc20Contract.getAddress(),
await verifierContract.getAddress(),
rlnContractArgs.minimalDeposit,
rlnContractArgs.treeDepth,
rlnContractArgs.feePercentage,
rlnContractArgs.feeReceiver,
rlnContractArgs.freezePeriod,
)
const rlnContractAddress = await rlnContract.getAddress()
const rlnContractAtBlock = await provider.getBlockNumber()
console.log(`Deployed RLN contract at ${rlnContractAddress} at block ${rlnContractAtBlock}`)
function createRLNInstance() {
return RLN.createWithContractRegistry({
/* Required */
rlnIdentifier,
provider,
contractAddress: rlnContractAddress,
/* Optional */
contractAtBlock: rlnContractAtBlock,
signer,
})
}
async function mineBlocks(numBlocks: number) {
provider.send("hardhat_mine", ["0x" + numBlocks.toString(16)])
}
const rln = createRLNInstance()
console.log(`rln created: identityCommitment=${rln.identityCommitment}`)
if (await rln.isRegistered()) {
throw new Error(`rln should not have yet registered`);
}
console.log(`Try with rate limit ${messageLimit}...`)
/* Register */
await rln.register(messageLimit);
if (!await rln.isRegistered()) {
throw new Error(`Failed to register`);
}
console.log(`Successfully registered`);
/* Create Proof */
console.log(`Creating proof...`)
const proof = await rln.createProof(epoch, message0);
if (!await rln.verifyProof(epoch, message0, proof)) {
throw new Error(`Proof is invalid`);
}
console.log(`Successfully created proof`);
console.log(`Try creating proof for another message but it should exceed the rate limit ${messageLimit}...`)
try {
await rln.createProof(epoch, message1);
} catch (e) {
const message = (e as Error).toString()
if (!message.includes(`Error: Message ID counter exceeded message limit ${messageLimit}`)) {
throw e
}
}
console.log(`Failed to create proof for another message as expected`);
/* Withdraw */
console.log("`withdraw`...")
await rln.withdraw();
// wait after freeze period
const blockHeightFreezePeriodEnds = Number(rlnContractArgs.freezePeriod) + 1
console.log("Wait after freeze period ends (freezePeriod + 1 blocks)...")
await mineBlocks(blockHeightFreezePeriodEnds)
console.log("`releaseWithdrawal`...")
await rln.releaseWithdrawal();
if (await rln.isRegistered()) {
throw new Error(`Failed to withdraw`);
}
console.log(`Successfully withdrew`);
/* Slash */
console.log("Try `slash` by making rlnAnother create more than " + `${messageLimit} proofs and get slashed by rln`)
class ResettableCache extends MemoryCache {
async reset() {
this.cache = {}
}
}
const resettableCache = new ResettableCache()
const rlnAnother = createRLNInstance()
rlnAnother.setCache(resettableCache)
console.log(`rlnAnother created: identityCommitment=${rlnAnother.identityCommitment}`)
class FaultyMessageIDCounter {
private counter: bigint = BigInt(0)
constructor(readonly messageLimit: bigint) { }
async getMessageIDAndIncrement(_: bigint): Promise<bigint> {
// Don't increment counter, so that it will exceed the message limit
return this.counter
}
async peekNextMessageID(_: bigint): Promise<bigint> {
return BigInt(this.counter)
}
}
console.log(`Registering rlnAnother...`)
// Intentionally uses a faulty message ID counter, so that it will use the same message ID
// and exceed the message limit. This will cause it to get slashed.
await rlnAnother.register(messageLimit, new FaultyMessageIDCounter(messageLimit));
console.log(`Creating proof0 for rlnAnother...`)
const proof0 = await rlnAnother.createProof(epoch, message0);
console.log(`Creating proof1 for rlnAnother...`)
// Intentionally clear the cache of rlnAnother, so that it will create a proof which
// will cause a breach and get slashed.
resettableCache.reset()
const proof1 = await rlnAnother.createProof(epoch, message1);
console.log(`rln saving proof0 from rlnAnother...`)
const res0 = await rln.saveProof(proof0);
if (res0.status != Status.VALID) {
throw new Error(`rlnAnother's proof should have been valid`);
}
console.log(`rln saving proof1 for rlnAnother...`)
const res1 = await rln.saveProof(proof1);
if (res1.status != Status.BREACH) {
throw new Error(`rlnAnother's secret should have been breached`);
}
const secret = res1.secret as bigint
console.log(`Successfully breached rlnAnother's secret=${secret}`);
await rln.slash(secret, slasher)
if (await rlnAnother.isRegistered()) {
throw new Error(`rlnAnother should have been slashed`);
}
console.log(`Successfully slashed rlnAnother`);
RLN.cleanUp()
}
main().catch((e) => {
console.error(e)
});

View File

@@ -1,23 +0,0 @@
{
"compilerOptions": {
"target": "ES6",
"module": "ES6",
"moduleResolution": "node",
"allowJs": true,
"allowSyntheticDefaultImports": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitAny": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noEmitOnError": true,
"forceConsistentCasingInFileNames": true,
"sourceMap": true,
"esModuleInterop": true,
"declaration": true,
"declarationDir": "./dist/types",
"outDir": "./dist",
"strict": true,
"resolveJsonModule": true,
}
}

View File

@@ -1,26 +0,0 @@
# Example: use RLNjs in NodeJS
The example go through the following steps:
1. Deploy the necessary contracts: verifier (the mock proof verifier since it's not yet generated), ERC20 token, and the RLN contract
2. Create a RLN instance `rln`, and demonstrate how to `register`, `createProof`, `verifyProof`, `withdraw`, and `releaseWithdrawal`.
3. Create a RLN instance `rlnAnother` and demonstrate how to slash a spammer. `rlnAnother` simply `register` and send more proofs than they should. `rln` can detect the spam by examine the output from `saveProof`. If the status of the output is BREACH, the secret is leaked. `rln` can use the recovered secret to slash the spammer by calling `slash`.
To install and run the example, follow the steps below.
1. Install the project
```bash
$ npm install
```
2. Run a local testing RPC
Here we use a hardhat node. If the RPC is not listening `http://localhost:8545`, you need to change the `url` in [config.ts](./src/configs.ts).
In a new terminal, run:
```bash
$ npx hardhat node
```
3. Run the example
```bash
$ npm run test
```

View File

@@ -1,19 +0,0 @@
{
"name": "example-node",
"version": "0.0.1",
"description": "An example of how to use RLN with NodeJS",
"main": "dist/index.js",
"scripts": {
"test": "tsc && node dist/index.js",
"clean": "rm -rf dist"
},
"author": "",
"license": "ISC",
"dependencies": {
"ethers": "^6.6.0",
"rlnjs": "^3.1.5"
},
"devDependencies": {
"hardhat": "^2.15.0"
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,166 +0,0 @@
import { ethers } from "ethers";
import { MemoryCache, RLN, Status } from "test-rlnjs";
import { deployERC20, deployRLNContract, deployVerifier, treeDepth, url } from "./configs";
async function main() {
const rlnIdentifier = BigInt(5566)
const messageLimit = BigInt(1);
const message0 = "Hello World"
const message1 = "Hello World 2"
const epoch = BigInt(1234)
const signerTestERC20Amount = BigInt(100000000)
const slasher = "0x0000000000000000000000000000000000009876"
const rlnContractArgs = {
minimalDeposit: BigInt(100),
treeDepth: treeDepth,
feePercentage: BigInt(10),
feeReceiver: "0x0000000000000000000000000000000000006789",
freezePeriod: BigInt(1),
}
console.log(`Connecting to endpoint at ${url}`)
const provider = new ethers.JsonRpcProvider(url)
const signer = await provider.getSigner(0)
// Here we use a mock verifier since we don't have a proof verifier deployed yet.
console.log(`Deploying contracts...`)
const verifierContract = await deployVerifier(signer)
console.log(`Deployed mock verifier at ${await verifierContract.getAddress()}`)
const erc20Contract = await deployERC20(signer, signerTestERC20Amount)
console.log(`Deployed test ERC20 at ${await erc20Contract.getAddress()}`)
const rlnContract = await deployRLNContract(
signer,
await erc20Contract.getAddress(),
await verifierContract.getAddress(),
rlnContractArgs.minimalDeposit,
rlnContractArgs.treeDepth,
rlnContractArgs.feePercentage,
rlnContractArgs.feeReceiver,
rlnContractArgs.freezePeriod,
)
const rlnContractAddress = await rlnContract.getAddress()
const rlnContractAtBlock = await provider.getBlockNumber()
console.log(`Deployed RLN contract at ${rlnContractAddress} at block ${rlnContractAtBlock}`)
function createRLNInstance() {
return RLN.createWithContractRegistry({
/* Required */
rlnIdentifier,
provider,
contractAddress: rlnContractAddress,
/* Optional */
contractAtBlock: rlnContractAtBlock,
signer,
})
}
async function mineBlocks(numBlocks: number) {
provider.send("hardhat_mine", ["0x" + numBlocks.toString(16)])
}
const rln = createRLNInstance()
console.log(`rln created: identityCommitment=${rln.identityCommitment}`)
if (await rln.isRegistered()) {
throw new Error(`rln should not have yet registered`);
}
console.log(`Try with rate limit ${messageLimit}...`)
/* Register */
await rln.register(messageLimit);
if (!await rln.isRegistered()) {
throw new Error(`Failed to register`);
}
console.log(`Successfully registered`);
/* Create Proof */
console.log(`Creating proof...`)
const proof = await rln.createProof(epoch, message0);
if (!await rln.verifyProof(epoch, message0, proof)) {
throw new Error(`Proof is invalid`);
}
console.log(`Successfully created proof`);
console.log(`Try creating proof for another message but it should exceed the rate limit ${messageLimit}...`)
try {
await rln.createProof(epoch, message1);
} catch (e) {
const message = (e as Error).toString()
if (!message.includes(`Error: Message ID counter exceeded message limit ${messageLimit}`)) {
throw e
}
}
console.log(`Failed to create proof for another message as expected`);
/* Withdraw */
console.log("`withdraw`...")
await rln.withdraw();
// wait after freeze period
const blockHeightFreezePeriodEnds = Number(rlnContractArgs.freezePeriod) + 1
console.log("Wait after freeze period ends (freezePeriod + 1 blocks)...")
await mineBlocks(blockHeightFreezePeriodEnds)
console.log("`releaseWithdrawal`...")
await rln.releaseWithdrawal();
if (await rln.isRegistered()) {
throw new Error(`Failed to withdraw`);
}
console.log(`Successfully withdrew`);
/* Slash */
console.log("Try `slash` by making rlnAnother create more than " + `${messageLimit} proofs and get slashed by rln`)
class ResettableCache extends MemoryCache {
async reset() {
this.cache = {}
}
}
const resettableCache = new ResettableCache()
const rlnAnother = createRLNInstance()
rlnAnother.setCache(resettableCache)
console.log(`rlnAnother created: identityCommitment=${rlnAnother.identityCommitment}`)
class FaultyMessageIDCounter {
private counter: bigint = BigInt(0)
constructor(readonly messageLimit: bigint) { }
async getMessageIDAndIncrement(_: bigint): Promise<bigint> {
// Don't increment counter, so that it will exceed the message limit
return this.counter
}
async peekNextMessageID(_: bigint): Promise<bigint> {
return BigInt(this.counter)
}
}
console.log(`Registering rlnAnother...`)
// Intentionally uses a faulty message ID counter, so that it will use the same message ID
// and exceed the message limit. This will cause it to get slashed.
await rlnAnother.register(messageLimit, new FaultyMessageIDCounter(messageLimit));
console.log(`Creating proof0 for rlnAnother...`)
const proof0 = await rlnAnother.createProof(epoch, message0);
console.log(`Creating proof1 for rlnAnother...`)
// Intentionally clear the cache of rlnAnother, so that it will create a proof which
// will cause a breach and get slashed.
resettableCache.reset()
const proof1 = await rlnAnother.createProof(epoch, message1);
console.log(`rln saving proof0 from rlnAnother...`)
const res0 = await rln.saveProof(proof0);
if (res0.status != Status.VALID) {
throw new Error(`rlnAnother's proof should have been valid`);
}
console.log(`rln saving proof1 for rlnAnother...`)
const res1 = await rln.saveProof(proof1);
if (res1.status != Status.BREACH) {
throw new Error(`rlnAnother's secret should have been breached`);
}
const secret = res1.secret as bigint
console.log(`Successfully breached rlnAnother's secret=${secret}`);
await rln.slash(secret, slasher)
if (await rlnAnother.isRegistered()) {
throw new Error(`rlnAnother should have been slashed`);
}
console.log(`Successfully slashed rlnAnother`);
RLN.cleanUp()
}
main().catch((e) => {
console.error(e)
});

View File

@@ -1,22 +0,0 @@
{
"compilerOptions": {
"target": "ES6",
"module": "CommonJS",
"moduleResolution": "node",
"allowJs": true,
"allowSyntheticDefaultImports": true,
"noUnusedParameters": true,
"noImplicitAny": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noEmitOnError": true,
"forceConsistentCasingInFileNames": true,
"sourceMap": true,
"esModuleInterop": true,
"declaration": true,
"declarationDir": "./dist/types",
"outDir": "./dist",
"strict": true,
"baseUrl": "."
}
}

View File

@@ -1,4 +0,0 @@
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.8.18",
};

View File

@@ -1,16 +0,0 @@
import type { Config } from '@jest/types';
const config: Config.InitialOptions = {
preset: 'ts-jest',
testEnvironment: 'node',
"transform": {
"^.+\\.jsx?$": "babel-jest",
"^.+\\.tsx?$": ["ts-jest", { tsconfig: "tsconfig.test.json" }]
},
"silent": true,
"testTimeout": 100000,
"collectCoverage": true,
"forceExit": true,
};
export default config;

View File

@@ -1,88 +0,0 @@
{
"name": "@cryptkeeperzk/rln-proof",
"private": true,
"version": "0.1.0-beta.1",
"description": "A forked RLN proof library to generate and verify RLN proofs.",
"license": "MIT",
"main": "./dist/src/index.js",
"types": "./dist/src/index.d.ts",
"files": [
"dist/",
"src/",
"LICENSE",
"README.md"
],
"browser": {
"./dist/index.node.cjs": "./dist/index.mjs"
},
"directories": {
"dist": "./dist",
"src": "./src",
"test": "./tests"
},
"repository": "https://github.com/Rate-Limiting-Nullifier/rlnjs",
"homepage": "https://github.com/Rate-Limiting-Nullifier/rlnjs",
"contributors": [
{
"name": "AtHeartEngineer"
},
{
"name": "Mai-Hsuan (Kevin) Chia"
},
{
"name": "bdim1"
},
{
"name": "Seohee Park"
}
],
"keywords": [
"rln",
"rate-limiting-nullifier",
"ethereum",
"circom",
"zk",
"zero-knowledge",
"zk-snarks",
"zero-knowledge-proofs"
],
"scripts": {
"build": "tsc -p tsconfig.build.json",
"lint": "eslint --ext .ts,.tsx,.js,.jsx src/ -c .eslintrc.js",
"lint:fix": "pnpm run lint --fix",
"types": "tsc -p tsconfig.json --noEmit",
"format": "eslint --ext .ts,.tsx,.js,.jsx src/ -c .eslintrc.js --fix"
},
"dependencies": {
"@ethersproject/bytes": "^5.7.0",
"@ethersproject/keccak256": "^5.7.0",
"@ethersproject/solidity": "^5.7.0",
"@ethersproject/strings": "^5.7.0",
"@semaphore-protocol/group": "^3.10.1",
"@semaphore-protocol/identity": "^3.10.1",
"@zk-kit/incremental-merkle-tree": "^0.4.3",
"base64-js": "^1.5.1",
"ethers": "^6.4.0",
"ffjavascript": "0.2.55",
"poseidon-lite": "^0.0.2",
"snarkjs": "git+https://github.com/CryptKeeperZK/snarkjs.git"
},
"devDependencies": {
"@ethersproject/bignumber": "^5.7.0",
"@types/jest": "^29.2.4",
"@typescript-eslint/eslint-plugin": "^5.50.0",
"@typescript-eslint/parser": "^5.51.0",
"eslint": "^8.33.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-airbnb-typescript": "^17.0.0",
"eslint-import-resolver-typescript": "^3.5.3",
"eslint-plugin-import": "^2.27.5",
"hardhat": "^2.14.1",
"husky": "^8.0.3",
"jest": "^29.5.0",
"prettier": "^2.8.1",
"ts-jest": "^29.0.3",
"tslib": "^2.5.0",
"typescript": "^4.9.5"
}
}

View File

@@ -1,101 +0,0 @@
#!/bin/bash
#
# Clone rln-circuits and build params
#
# Usage:
# ./scripts/build-zkeys.sh <circuit_name>
# Example:
# Build rln circuit
# $ ./scripts/build-zkeys.sh rln
# Build withdraw circuit
# $ ./scripts/build-zkeys.sh withdraw
# Build all circuits defined in `supported_circuit_names`
# $ ./scripts/build-zkeys.sh
#
# Configs
#
supported_circuit_names=("rln" "withdraw")
# https://github.com/Rate-Limiting-Nullifier/circom-rln/commit/17f0fed7d8d19e8b127fd0b3e5295a4831193a0d
rln_circuits_version=v1.0.0
rln_circuits_repo='circom-rln'
rln_circuits_repo_url="https://github.com/Rate-Limiting-Nullifier/$rln_circuits_repo.git"
#
# Determine which circuits to build
#
declare -a circuit_names_to_build
circuit_name_arg=$1;
# Check if the circuit name is supported
for name in "${supported_circuit_names[@]}"
do
if [ "$circuit_name_arg" == "$name" ]; then
circuit_names_to_build=($circuit_name_arg)
break
fi
done
# If circuit name
if [ -z "$circuit_names_to_build" ] && [ -z "$circuit_name_arg" ]; then
circuit_names_to_build=("${supported_circuit_names[@]}")
fi
install_circom_script='./scripts/install-circom.sh'
rln_circuits_build_script='./scripts/build-circuits.sh'
# Go to project root of rlnjs
cd `dirname $0`/..
# Make sure circom is installed
bash $install_circom_script
which circom && circom --version
# Clone rln_circuits_repo, checkout the right version, and install the deps
git clone $rln_circuits_repo_url $rln_circuits_repo
# Go to circuits repo and the right version
cd $rln_circuits_repo
git checkout $rln_circuits_version
# Install the deps and the project
npm install
build_and_copy_params() {
# We should already be in the rln-circuits-v2 project root
circuit_name=$1
# Return if the params already exist
# rlnjs
# |_rln-circuits-v2
# |_zkeyFiles
target_zkeyfiles_dir="../zkeyFiles/$circuit_name"
target_rln_wasm_path="$target_zkeyfiles_dir/circuit.wasm"
target_rln_zkey_path="$target_zkeyfiles_dir/final.zkey"
target_rln_verifiation_key_path="$target_zkeyfiles_dir/verification_key.json"
if [[ -f $target_rln_wasm_path ]] && [[ -f $target_rln_zkey_path ]] && [[ -f $target_rln_verifiation_key_path ]]; then
echo "All params exist. Don't build them"
return
fi
echo "Building circuit: $circuit_name"
bash "$rln_circuits_build_script" $circuit_name
mkdir -p $target_zkeyfiles_dir
# Copy params from rln-circuits-v2/zkeyFiles/<circuit_name> to rlnjs/zkeyFiles/<circuit_name>
built_params_relative_path="./zkeyFiles/$circuit_name"
cp $built_params_relative_path/circuit.wasm $target_rln_wasm_path
cp $built_params_relative_path/final.zkey $target_rln_zkey_path
cp $built_params_relative_path/verification_key.json $target_rln_verifiation_key_path
ls -al $target_zkeyfiles_dir
}
for circuit_name in "${circuit_names_to_build[@]}"
do
build_and_copy_params $circuit_name
done
# Remove the circuits repo
cd ..
rm -rf $rln_circuits_repo

View File

@@ -1,45 +0,0 @@
#!/bin/bash
#
# Clone zkeys (params) from js-rln
#
js_rln_version=d77370f
js_rln_repo='js-rln'
js_rln_repo_url="https://github.com/waku-org/$js_rln_repo.git"
# Go to project root
cd `dirname $0`/..
target_zkeyfiles_dir="./zkeyFiles/js-rln"
target_rln_wasm_path="$target_zkeyfiles_dir/rln.wasm"
target_rln_zkey_path="$target_zkeyfiles_dir/rln_final.zkey"
target_rln_verifiation_key_path="$target_zkeyfiles_dir/verification_key.json"
# Build params if any of them does not exist
if [[ -f $target_rln_wasm_path ]] && [[ -f $target_rln_zkey_path ]] && [[ -f $target_rln_verifiation_key_path ]]; then
echo "All params exist. Don't need to clone them"
exit 0;
fi
# Clone js-rln in project root and checkout to the right version
git clone $js_rln_repo_url $js_rln_repo
cd $js_rln_repo
git checkout $js_rln_version
cd ..
js_rln_zkeys_relative_path="$js_rln_repo/src/resources"
js_rln_zkeys_verification_js="$js_rln_zkeys_relative_path/verification_key.js"
js_rln_zkeys_verification_json="$js_rln_zkeys_relative_path/verification_key.json"
# Change js to json format in place
sed -i.bak -e "s/const verificationKey = {/{/" -e "/export default verificationKey/d" $js_rln_zkeys_verification_js
mv $js_rln_zkeys_verification_js $js_rln_zkeys_verification_json
# Copy the clone & converted params to the zkeyFiles folder
mkdir -p $target_zkeyfiles_dir
cp $js_rln_zkeys_relative_path/* $target_zkeyfiles_dir
ls -al $target_zkeyfiles_dir
# Remove the js-rln repo
rm -rf $js_rln_repo

View File

@@ -1,13 +0,0 @@
#!/bin/bash
circom_version=v2.1.5
if ! [ -x "$(command -v circom)" ]; then
git clone https://github.com/iden3/circom.git
cd circom
git checkout $circom_version
cargo build --release
cargo install --path circom
cd ..
rm -rf circom
fi

View File

@@ -1,164 +0,0 @@
/**
* @module cache
* @description `ICache` is only responsible for storing necessary information to detect spams and automatically
* evaluating proofs for rate limit breaches. No proof validation inside and thus proofs **must** be validated
* before added to the `ICache`.
*/
import { StrBigInt } from './types'
import { shamirRecovery } from './common'
/**
* Store necessary information of a proof to detect spams
*/
export type CachedProof = {
x: StrBigInt,
y: StrBigInt,
// epoch is used to remove stale proofs
epoch: StrBigInt,
// internalNullifier
nullifier: StrBigInt,
}
type EpochCache = {
[nullifier: string]: CachedProof[]
}
type CacheMap = {
[epoch: string]: EpochCache
}
export enum Status {
VALID,
DUPLICATE,
BREACH,
}
export type EvaluatedProof = {
status: Status,
nullifier?: StrBigInt,
secret?: bigint,
msg?: string,
}
export interface ICache {
/**
* Add a proof to the cache and automatically evaluate it for rate limit breaches.
* @param proof CachedProof
* @returns an object with the status of the proof and the nullifier and secret if the proof is a breach
*/
addProof(proof: CachedProof): EvaluatedProof
/**
* Check the proof if it is either valid, duplicate, or breaching.
* Does not add the proof to the cache to avoid side effects.
* @param proof CachedProof
* @returns an object with the status of the proof and the nullifier and secret if the proof is a breach
*/
checkProof(proof: CachedProof): EvaluatedProof
}
export const DEFAULT_CACHE_SIZE = 100
/**
* Cache for storing proofs and automatically evaluating them for rate limit breaches
* in the memory.
*/
export class MemoryCache implements ICache {
cacheLength: number
cache: CacheMap
epochs: string[]
/**
* @param cacheLength the maximum number of epochs to store in the cache, default is 100, set to 0 to automatic pruning
* @param cache the cache object to use, default is an empty object
*/
constructor(cacheLength?: number) {
this.cacheLength = cacheLength ? cacheLength : DEFAULT_CACHE_SIZE
this.cache = {}
this.epochs = []
}
/**
* Add a proof to the cache and automatically evaluate it for rate limit breaches.
* @param proof CachedProof
* @returns an object with the status of the proof and the nullifier and secret if the proof is a breach
*/
addProof(proof: CachedProof): EvaluatedProof {
// epoch, nullifier, x, y
// Since `BigInt` can't be used as key, use String instead
const epochString = String(proof.epoch)
const nullifier = String(proof.nullifier)
// Check if the proof status
const resCheckProof = this.checkProof(proof)
// Only add the proof to the cache automatically if it's not seen before.
if (resCheckProof.status === Status.VALID || resCheckProof.status === Status.BREACH) {
// Add proof to cache
this.cache[epochString][nullifier].push(proof)
}
return resCheckProof
}
/**
* Check the proof if it is either valid, duplicate, or breaching.
* Does not add the proof to the cache to avoid side effects.
* @param proof CachedProof
* @returns an object with the status of the proof and the nullifier and secret if the proof is a breach
*/
checkProof(proof: CachedProof): EvaluatedProof {
const epochString = String(proof.epoch)
const nullifier = String(proof.nullifier)
this.shiftEpochs(epochString)
// If nullifier doesn't exist for this epoch, create an empty array
this.cache[epochString][nullifier] = this.cache[epochString][nullifier] || []
const proofs = this.cache[epochString][nullifier]
// Check if the proof already exists. It's O(n) but it's not a big deal since n is exactly the
// rate limit and it's usually small.
function isSameProof(proof1: CachedProof, proof2: CachedProof): boolean {
return (
BigInt(proof1.x) === BigInt(proof2.x) &&
BigInt(proof1.y) === BigInt(proof2.y) &&
BigInt(proof1.epoch) === BigInt(proof2.epoch) &&
BigInt(proof1.nullifier) === BigInt(proof2.nullifier)
)
}
// OK
if (proofs.length === 0) {
return { status: Status.VALID, nullifier: nullifier, msg: 'Proof added to cache' }
// Exists proof with same epoch and nullifier. Possible breach or duplicate proof
} else {
const sameProofs = this.cache[epochString][nullifier].filter(p => isSameProof(p, proof))
if (sameProofs.length > 0) {
return { status: Status.DUPLICATE, msg: 'Proof already exists' }
} else {
const otherProof = proofs[0]
// Breach. Return secret
const [x1, y1] = [BigInt(proof.x), BigInt(proof.y)]
const [x2, y2] = [BigInt(otherProof.x), BigInt(otherProof.y)]
const secret = shamirRecovery(x1, x2, y1, y2)
return { status: Status.BREACH, nullifier: nullifier, secret: secret, msg: 'Rate limit breach, secret attached' }
}
}
}
private shiftEpochs(epoch: string) {
if (this.cache[epoch]) {
// If epoch already exists, return
return
} else {
// If epoch doesn't exist, create it
this.cache[epoch] = {}
this.epochs.push(epoch)
if (this.cacheLength > 0 && this.epochs.length > this.cacheLength) {
this.removeEpoch(this.epochs[0])
}
}
this.cache[epoch] = this.cache[epoch] || {}
}
private removeEpoch(epoch: string) {
delete this.cache[epoch]
this.epochs.shift()
}
}

View File

@@ -1,190 +0,0 @@
import { MerkleProof } from '@zk-kit/incremental-merkle-tree'
import { groth16 } from '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;
messageHash: 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.messageHash,
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)
}
}

View File

@@ -1,59 +0,0 @@
import { hexlify } from '@ethersproject/bytes'
import { toUtf8Bytes } from '@ethersproject/strings'
import { keccak256 } from '@ethersproject/keccak256'
import { ZqField } from 'ffjavascript'
import poseidon from 'poseidon-lite'
import { Identity } from '@semaphore-protocol/identity'
/*
This is the "Baby Jubjub" curve described here:
https://iden3-docs.readthedocs.io/en/latest/_downloads/33717d75ab84e11313cc0d8a090b636f/Baby-Jubjub.pdf
*/
export const SNARK_FIELD_SIZE = BigInt('21888242871839275222246405745257275088548364400416034343698204186575808495617')
// Creates the finite field
export const Fq = new ZqField(SNARK_FIELD_SIZE)
export const DEFAULT_MERKLE_TREE_DEPTH = 20
export function calculateIdentityCommitment(identity: Identity): bigint {
return poseidon([
identity.getNullifier(),
identity.getTrapdoor(),
])
}
export function calculateExternalNullifier(epoch: bigint, rlnIdentifier: bigint): bigint {
return poseidon([epoch, rlnIdentifier])
}
export function calculateRateCommitment(identityCommitment: bigint, userMessageLimit: bigint): bigint {
return poseidon([identityCommitment, userMessageLimit])
}
/**
* Hashes a signal string with Keccak256.
* @param signal The RLN signal.
* @returns The signal hash.
*/
export function calculateSignalHash(signal: string): bigint {
const converted = hexlify(toUtf8Bytes(signal))
return BigInt(keccak256(converted)) >> BigInt(8)
}
/**
* Recovers secret from two shares
* @param x1 signal hash of first message
* @param x2 signal hash of second message
* @param y1 yshare of first message
* @param y2 yshare of second message
* @returns identity secret
*/
export function shamirRecovery(x1: bigint, x2: bigint, y1: bigint, y2: bigint): bigint {
const slope = Fq.div(Fq.sub(y2, y1), Fq.sub(x2, x1))
const privateKey = Fq.sub(y1, Fq.mul(slope, x1))
return Fq.normalize(privateKey)
}

View File

@@ -1,210 +0,0 @@
import { Proof } from './types'
import { ethers } from 'ethers'
const erc20ABI = JSON.parse('[{"constant": true, "inputs": [], "name": "name", "outputs": [{"name": "", "type": "string"}], "payable": false, "stateMutability": "view", "type": "function"}, {"constant": false, "inputs": [{"name": "_spender", "type": "address"}, {"name": "_value", "type": "uint256"}], "name": "approve", "outputs": [{"name": "", "type": "bool"}], "payable": false, "stateMutability": "nonpayable", "type": "function"}, {"constant": true, "inputs": [], "name": "totalSupply", "outputs": [{"name": "", "type": "uint256"}], "payable": false, "stateMutability": "view", "type": "function"}, {"constant": false, "inputs": [{"name": "_from", "type": "address"}, {"name": "_to", "type": "address"}, {"name": "_value", "type": "uint256"}], "name": "transferFrom", "outputs": [{"name": "", "type": "bool"}], "payable": false, "stateMutability": "nonpayable", "type": "function"}, {"constant": true, "inputs": [], "name": "decimals", "outputs": [{"name": "", "type": "uint8"}], "payable": false, "stateMutability": "view", "type": "function"}, {"constant": true, "inputs": [{"name": "_owner", "type": "address"}], "name": "balanceOf", "outputs": [{"name": "balance", "type": "uint256"}], "payable": false, "stateMutability": "view", "type": "function"}, {"constant": true, "inputs": [], "name": "symbol", "outputs": [{"name": "", "type": "string"}], "payable": false, "stateMutability": "view", "type": "function"}, {"constant": false, "inputs": [{"name": "_to", "type": "address"}, {"name": "_value", "type": "uint256"}], "name": "transfer", "outputs": [{"name": "", "type": "bool"}], "payable": false, "stateMutability": "nonpayable", "type": "function"}, {"constant": true, "inputs": [{"name": "_owner", "type": "address"}, {"name": "_spender", "type": "address"}], "name": "allowance", "outputs": [{"name": "", "type": "uint256"}], "payable": false, "stateMutability": "view", "type": "function"}, {"payable": true, "stateMutability": "payable", "type": "fallback"}, {"anonymous": false, "inputs": [{"indexed": true, "name": "owner", "type": "address"}, {"indexed": true, "name": "spender", "type": "address"}, {"indexed": false, "name": "value", "type": "uint256"}], "name": "Approval", "type": "event"}, {"anonymous": false, "inputs": [{"indexed": true, "name": "from", "type": "address"}, {"indexed": true, "name": "to", "type": "address"}, {"indexed": false, "name": "value", "type": "uint256"}], "name": "Transfer", "type": "event"}]')
export const rlnContractABI = JSON.parse('[{"inputs": [{"internalType": "uint256", "name": "minimalDeposit", "type": "uint256"}, {"internalType": "uint256", "name": "depth", "type": "uint256"}, {"internalType": "uint8", "name": "feePercentage", "type": "uint8"}, {"internalType": "address", "name": "feeReceiver", "type": "address"}, {"internalType": "uint256", "name": "freezePeriod", "type": "uint256"}, {"internalType": "address", "name": "_token", "type": "address"}, {"internalType": "address", "name": "_verifier", "type": "address"}], "stateMutability": "nonpayable", "type": "constructor"}, {"anonymous": false, "inputs": [{"indexed": false, "internalType": "uint256", "name": "identityCommitment", "type": "uint256"}, {"indexed": false, "internalType": "uint256", "name": "messageLimit", "type": "uint256"}, {"indexed": false, "internalType": "uint256", "name": "index", "type": "uint256"}], "name": "MemberRegistered", "type": "event"}, {"anonymous": false, "inputs": [{"indexed": false, "internalType": "uint256", "name": "index", "type": "uint256"}, {"indexed": false, "internalType": "address", "name": "slasher", "type": "address"}], "name": "MemberSlashed", "type": "event"}, {"anonymous": false, "inputs": [{"indexed": false, "internalType": "uint256", "name": "index", "type": "uint256"}], "name": "MemberWithdrawn", "type": "event"}, {"anonymous": false, "inputs": [{"indexed": true, "internalType": "address", "name": "previousOwner", "type": "address"}, {"indexed": true, "internalType": "address", "name": "newOwner", "type": "address"}], "name": "OwnershipTransferred", "type": "event"}, {"inputs": [], "name": "DEPTH", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"inputs": [], "name": "MINIMAL_DEPOSIT", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"inputs": [], "name": "SET_SIZE", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"inputs": [], "name": "identityCommitmentIndex", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"inputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "name": "members", "outputs": [{"internalType": "address", "name": "userAddress", "type": "address"}, {"internalType": "uint256", "name": "messageLimit", "type": "uint256"}, {"internalType": "uint256", "name": "index", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"inputs": [], "name": "owner", "outputs": [{"internalType": "address", "name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"inputs": [{"internalType": "uint256", "name": "identityCommitment", "type": "uint256"}, {"internalType": "uint256", "name": "amount", "type": "uint256"}], "name": "register", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"internalType": "uint256", "name": "identityCommitment", "type": "uint256"}], "name": "release", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [], "name": "renounceOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"internalType": "uint256", "name": "identityCommitment", "type": "uint256"}, {"internalType": "address", "name": "receiver", "type": "address"}, {"internalType": "uint256[8]", "name": "proof", "type": "uint256[8]"}], "name": "slash", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [], "name": "token", "outputs": [{"internalType": "contract IERC20", "name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"inputs": [{"internalType": "address", "name": "newOwner", "type": "address"}], "name": "transferOwnership", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [], "name": "verifier", "outputs": [{"internalType": "contract IVerifier", "name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"inputs": [{"internalType": "uint256", "name": "identityCommitment", "type": "uint256"}, {"internalType": "uint256[8]", "name": "proof", "type": "uint256[8]"}], "name": "withdraw", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "name": "withdrawals", "outputs": [{"internalType": "uint256", "name": "blockNumber", "type": "uint256"}, {"internalType": "uint256", "name": "amount", "type": "uint256"}, {"internalType": "address", "name": "receiver", "type": "address"}], "stateMutability": "view", "type": "function"}]')
type User = {
userAddress: string,
messageLimit: bigint,
index: bigint,
}
type Withdrawal = {
blockNumber: bigint,
amount: bigint,
receiver: string,
}
function proofToArray(proof: Proof) {
// verifier.verifyProof(
// [proof[0], proof[1]],
// [[proof[2], proof[3]], [proof[4], proof[5]]],
// [proof[6], proof[7]],
// [identityCommitment, uint256(uint160(receiver))]
// );
return [
BigInt(proof.pi_a[0]),
BigInt(proof.pi_a[1]),
BigInt(proof.pi_b[0][0]),
BigInt(proof.pi_b[0][1]),
BigInt(proof.pi_b[1][0]),
BigInt(proof.pi_b[1][1]),
BigInt(proof.pi_c[0]),
BigInt(proof.pi_c[1]),
]
}
/**
event MemberRegistered(uint256 identityCommitment, uint256 messageLimit, uint256 index);
event MemberWithdrawn(uint256 index);
event MemberSlashed(uint256 index, address slasher);
*/
export type EventMemberRegistered = {
name: 'MemberRegistered',
identityCommitment: bigint,
messageLimit: bigint,
index: bigint,
}
export type EventMemberWithdrawn = {
name: 'MemberWithdrawn',
index: bigint,
}
export type EventMemberSlashed = {
name: 'MemberSlashed',
index: bigint,
slasher: string,
}
export class RLNContract {
// Either a signer (with private key) or a provider (without private key and read-only)
private provider: ethers.Provider
private signer?: ethers.Signer
private rlnContract: ethers.Contract
private contractAtBlock: number
constructor(args: {
provider: ethers.Provider,
signer?: ethers.Signer,
contractAddress: string,
contractAtBlock: number,
}) {
this.provider = args.provider
this.signer = args.signer
this.rlnContract = new ethers.Contract(args.contractAddress, rlnContractABI, this.getContractRunner())
this.contractAtBlock = args.contractAtBlock
}
private getContractRunner() {
// If signer is given, use signer. Else, use provider.
return this.signer || this.provider
}
async getTokenAddress() {
return this.rlnContract.token()
}
async getSignerAddress() {
if (this.signer === undefined) {
throw new Error('Cannot get signer address if signer is not set')
}
return this.signer.getAddress()
}
async getLogs() {
const rlnContractAddress = await this.rlnContract.getAddress()
const currentBlockNumber = await this.provider.getBlockNumber()
if (currentBlockNumber < this.contractAtBlock) {
throw new Error('Current block number is lower than the block number at which the contract was deployed')
}
const logs = await this.provider.getLogs({
address: rlnContractAddress,
fromBlock: this.contractAtBlock,
toBlock: currentBlockNumber,
})
const events = await Promise.all(logs.map(log => this.handleLog(log)))
return events.filter(x => x !== undefined) as (EventMemberRegistered | EventMemberWithdrawn | EventMemberSlashed)[]
}
private async handleLog(log: ethers.Log): Promise<EventMemberRegistered | EventMemberWithdrawn | EventMemberSlashed | undefined> {
const memberRegisteredFilter = this.rlnContract.filters.MemberRegistered()
const memberWithdrawnFilter = this.rlnContract.filters.MemberWithdrawn()
const memberSlashedFilter = this.rlnContract.filters.MemberSlashed()
const memberRegisteredTopics: ethers.TopicFilter = await memberRegisteredFilter.getTopicFilter()
const memberWithdrawnTopics: ethers.TopicFilter = await memberWithdrawnFilter.getTopicFilter()
const memberSlashedTopics: ethers.TopicFilter = await memberSlashedFilter.getTopicFilter()
if (log.topics[0] === memberRegisteredTopics[0]) {
const decoded = this.rlnContract.interface.decodeEventLog(memberRegisteredFilter.fragment, log.data)
return {
name: 'MemberRegistered',
identityCommitment: decoded.identityCommitment,
messageLimit: decoded.messageLimit,
index: decoded.index,
}
} else if (log.topics[0] === memberWithdrawnTopics[0]) {
const decoded = this.rlnContract.interface.decodeEventLog(memberWithdrawnFilter.fragment, log.data)
return {
name: 'MemberWithdrawn',
index: decoded.index,
}
} else if (log.topics[0] === memberSlashedTopics[0]) {
const decoded = this.rlnContract.interface.decodeEventLog(memberSlashedFilter.fragment, log.data)
return {
name: 'MemberSlashed',
index: decoded.index,
slasher: decoded.slasher,
}
} else {
// Just skip this log
return undefined
}
}
async register(identityCommitment: bigint, messageLimit: bigint): Promise<ethers.TransactionReceipt> {
const rlnContractAddress = await this.rlnContract.getAddress()
const pricePerMessageLimit = await this.rlnContract.MINIMAL_DEPOSIT()
const amount = messageLimit * pricePerMessageLimit
const tokenContract = new ethers.Contract(
await this.getTokenAddress(),
erc20ABI,
this.getContractRunner(),
)
const txApprove = await tokenContract.approve(rlnContractAddress, amount)
await txApprove.wait()
const txRegister = await this.rlnContract.register(identityCommitment, amount)
const receipt = await txRegister.wait()
return receipt
}
async getUser(identityCommitment: bigint): Promise<User> {
const [ userAddress, messageLimit, index] = await this.rlnContract.members(identityCommitment)
return {
userAddress,
messageLimit,
index,
}
}
async getWithdrawal(identityCommitment: bigint): Promise<Withdrawal> {
const [ blockNumber, amount, receiver ] = await this.rlnContract.withdrawals(identityCommitment)
return {
blockNumber,
amount,
receiver,
}
}
async withdraw(identityCommitment: bigint, proof: Proof): Promise<ethers.TransactionReceipt> {
const proofArray = proofToArray(proof)
const tx = await this.rlnContract.withdraw(identityCommitment, proofArray)
const receipt = await tx.wait()
return receipt
}
async release(identityCommitment: bigint): Promise<ethers.TransactionReceipt> {
const tx = await this.rlnContract.release(identityCommitment)
const receipt = await tx.wait()
return receipt
}
async slash(identityCommitment: bigint, receiver: string, proof: Proof): Promise<ethers.TransactionReceipt> {
const proofArray = proofToArray(proof)
const tx = await this.rlnContract.slash(identityCommitment, receiver, proofArray)
const receipt = await tx.wait()
return receipt
}
async isRegistered(identityCommitment: bigint): Promise<boolean> {
const user = await this.getUser(identityCommitment)
return user.userAddress !== ethers.ZeroAddress
}
}

View File

@@ -1,8 +0,0 @@
export { type IRLN, RLN } from './rln'
export { ContractRLNRegistry, type IRLNRegistry, MemoryRLNRegistry } from './registry'
export { type CachedProof, type ICache, MemoryCache, Status } from './cache'
export { type IMessageIDCounter } from './message-id-counter'
export * from './types'
export { type RLNFullProof, type RLNSNARKProof, type RLNWitness, type RLNPublicSignals, RLNProver, RLNVerifier, WithdrawProver } from './circuit-wrapper'

View File

@@ -1,47 +0,0 @@
/**
* Always return **next** the current counter value and increment the counter.
* @param epoch epoch of the message
* @throws Error if the counter exceeds the message limit
*/
export interface IMessageIDCounter {
messageLimit: bigint;
/**
* Return the current counter value and increment the counter.
*
* @param epoch
*/
getMessageIDAndIncrement(epoch: bigint): Promise<bigint>
}
type EpochMap = {
[epoch: string]: bigint
}
export class MemoryMessageIDCounter implements IMessageIDCounter {
protected _messageLimit: bigint
protected epochToMessageID: EpochMap
constructor(messageLimit: bigint) {
this._messageLimit = messageLimit
this.epochToMessageID = {}
}
get messageLimit(): bigint {
return this._messageLimit
}
async getMessageIDAndIncrement(epoch: bigint): Promise<bigint> {
const epochStr = epoch.toString()
// Initialize the message id counter if it doesn't exist
if (this.epochToMessageID[epochStr] === undefined) {
this.epochToMessageID[epochStr] = BigInt(0)
}
const messageID = this.epochToMessageID[epochStr]
if (messageID >= this.messageLimit) {
throw new Error(`Message ID counter exceeded message limit ${this.messageLimit}`)
}
this.epochToMessageID[epochStr] = messageID + BigInt(1)
return messageID
}
}

View File

@@ -1,272 +0,0 @@
import { Group } from '@semaphore-protocol/group'
import { MerkleProof } from './types'
import { DEFAULT_MERKLE_TREE_DEPTH, calculateRateCommitment } from './common'
import { RLNContract } from './contract-wrapper'
import { ethers } from 'ethers'
import poseidon from 'poseidon-lite'
import { WithdrawProver } from './circuit-wrapper'
export interface IRLNRegistry {
isRegistered(identityCommitment: bigint): Promise<boolean>
getMerkleRoot(): Promise<bigint>
getMessageLimit(identityCommitment: bigint): Promise<bigint>
getRateCommitment(identityCommitment: bigint): Promise<bigint>
getAllRateCommitments(): Promise<bigint[]>
generateMerkleProof(identityCommitment: bigint): Promise<MerkleProof>
register(identityCommitment: bigint, messageLimit: bigint): Promise<void>
withdraw(identitySecret: bigint): Promise<void>
releaseWithdrawal(identityCommitment: bigint): Promise<void>
slash(identitySecret: bigint, receiver?: string): Promise<void>
}
export class ContractRLNRegistry implements IRLNRegistry {
private rlnContract: RLNContract
// the withdrawProver allows user to generate withdraw proof, which is verified in the RLN contract
private withdrawProver?: WithdrawProver
private rlnIdentifier: bigint
private treeDepth: number
constructor(args: {
rlnIdentifier: bigint,
rlnContract: RLNContract,
treeDepth?: number,
withdrawWasmFilePath?: string | Uint8Array,
withdrawFinalZkeyPath?: string | Uint8Array,
}) {
this.treeDepth = args.treeDepth ? args.treeDepth : DEFAULT_MERKLE_TREE_DEPTH
this.rlnContract = args.rlnContract
this.rlnIdentifier = args.rlnIdentifier
if (args.withdrawWasmFilePath !== undefined && args.withdrawFinalZkeyPath !== undefined) {
this.withdrawProver = new WithdrawProver(args.withdrawWasmFilePath, args.withdrawFinalZkeyPath)
}
}
async getSignerAddress(): Promise<string> {
return this.rlnContract.getSignerAddress()
}
async isRegistered(identityCommitment: bigint): Promise<boolean> {
return this.rlnContract.isRegistered(identityCommitment)
}
async getMessageLimit(identityCommitment: bigint): Promise<bigint> {
const user = await this.rlnContract.getUser(identityCommitment)
if (user.userAddress === ethers.ZeroAddress) {
throw new Error('Identity commitment is not registered')
}
return user.messageLimit
}
async getRateCommitment(identityCommitment: bigint): Promise<bigint> {
const messageLimit = await this.getMessageLimit(identityCommitment)
return calculateRateCommitment(identityCommitment, messageLimit)
}
private async generateLatestGroup(): Promise<Group> {
const group = new Group(this.rlnIdentifier, this.treeDepth)
const events = await this.rlnContract.getLogs()
for (const event of events) {
if (event.name === 'MemberRegistered') {
const identityCommitment = BigInt(event.identityCommitment)
const messageLimit = BigInt(event.messageLimit)
const rateCommitment = calculateRateCommitment(identityCommitment, messageLimit)
group.addMember(rateCommitment)
} else if (event.name === 'MemberWithdrawn' || event.name === 'MemberSlashed') {
const index = event.index
group.removeMember(Number(index))
}
}
return group
}
async getAllRateCommitments(): Promise<bigint[]> {
const group = await this.generateLatestGroup()
return group.members.map((member) => BigInt(member))
}
async getMerkleRoot(): Promise<bigint> {
const group = await this.generateLatestGroup()
return BigInt(group.root)
}
/**
* Creates a Merkle Proof.
* @param identityCommitment The leaf for which Merkle proof should be created.
* @returns The Merkle proof.
*/
async generateMerkleProof(identityCommitment: bigint): Promise<MerkleProof> {
const group = await this.generateLatestGroup()
const user = await this.rlnContract.getUser(identityCommitment)
if (user.userAddress === ethers.ZeroAddress) {
throw new Error('Identity commitment is not registered')
}
const rateCommitment = calculateRateCommitment(identityCommitment, user.messageLimit)
const index = group.indexOf(rateCommitment)
if (index === -1) {
// Should only happen when a user was registered before `const user = ...` and then withdraw/slashed
// after `const user = ...`.
throw new Error('Rate commitment is not in the merkle tree')
}
return group.generateMerkleProof(index)
}
async register(identityCommitment: bigint, messageLimit: bigint): Promise<void> {
if (await this.isRegistered(identityCommitment)) {
throw new Error('Identity commitment is already registered')
}
await this.rlnContract.register(identityCommitment, messageLimit)
}
async withdraw(identitySecret: bigint): Promise<void> {
if (this.withdrawProver === undefined) {
throw new Error('Withdraw prover is not initialized')
}
const identityCommitment = poseidon([identitySecret])
const user = await this.rlnContract.getUser(identityCommitment)
if (user.userAddress === ethers.ZeroAddress) {
throw new Error('Identity commitment is not registered')
}
const userAddressBigInt = BigInt(user.userAddress)
const proof = await this.withdrawProver.generateProof({
identitySecret,
address: userAddressBigInt,
})
await this.rlnContract.withdraw(identityCommitment, proof.proof)
}
async releaseWithdrawal(identityCommitment: bigint): Promise<void> {
if (!await this.isRegistered(identityCommitment)) {
throw new Error('Identity commitment is not registered')
}
const withdrawal = await this.rlnContract.getWithdrawal(identityCommitment)
if (withdrawal.blockNumber == BigInt(0)) {
throw new Error('Withdrawal is not initiated')
}
await this.rlnContract.release(identityCommitment)
}
async slash(identitySecret: bigint, receiver?: string): Promise<void> {
if (this.withdrawProver === undefined) {
throw new Error('Withdraw prover is not initialized')
}
const identityCommitment = poseidon([identitySecret])
receiver = receiver ? receiver : await this.rlnContract.getSignerAddress()
const receiverBigInt = BigInt(receiver)
const proof = await this.withdrawProver.generateProof({
identitySecret,
address: receiverBigInt,
})
await this.rlnContract.slash(identityCommitment, receiver, proof.proof)
}
}
export class MemoryRLNRegistry implements IRLNRegistry {
// map of identityCommitment -> messageLimit
private mapIsWithdrawing: Map<string, boolean>
private mapMessageLimit: Map<string, bigint>
private group: Group
constructor(
readonly rlnIdentifier: bigint,
readonly treeDepth?: number | undefined,
) {
this.mapIsWithdrawing = new Map<string, boolean>()
this.mapMessageLimit = new Map<string, bigint>()
this.group = new Group(this.rlnIdentifier, this.treeDepth)
}
async isRegistered(identityCommitment: bigint): Promise<boolean> {
const messageLimit = this.mapMessageLimit.get(identityCommitment.toString())
return messageLimit !== undefined
}
async getMerkleRoot(): Promise<bigint> {
return BigInt(this.group.root)
}
async getMessageLimit(identityCommitment: bigint): Promise<bigint> {
const messageLimit = this.mapMessageLimit.get(identityCommitment.toString())
if (messageLimit === undefined) {
throw new Error('Identity commitment is not registered')
}
return messageLimit
}
async getRateCommitment(identityCommitment: bigint): Promise<bigint> {
const messageLimit = await this.getMessageLimit(identityCommitment)
return calculateRateCommitment(identityCommitment, messageLimit)
}
async getAllRateCommitments(): Promise<bigint[]> {
return this.group.members.map((member) => BigInt(member))
}
async generateMerkleProof(identityCommitment: bigint): Promise<MerkleProof> {
const rateCommitment = await this.getRateCommitment(identityCommitment)
const index = this.group.indexOf(rateCommitment)
if (index === -1) {
// Sanity check
throw new Error('Rate commitment is not in the merkle tree. This should never happen.')
}
return this.group.generateMerkleProof(index)
}
async register(identityCommitment: bigint, messageLimit: bigint): Promise<void> {
if (await this.isRegistered(identityCommitment)) {
throw new Error('Identity commitment is already registered')
}
this.mapMessageLimit.set(identityCommitment.toString(), messageLimit)
const rateCommitment = await this.getRateCommitment(identityCommitment)
this.group.addMember(rateCommitment)
}
async withdraw(identitySecret: bigint): Promise<void> {
const identityCommitment = poseidon([identitySecret])
if (!await this.isRegistered(identityCommitment)) {
throw new Error('Identity commitment is not registered')
}
const isWithdrawing = this.mapIsWithdrawing.get(identityCommitment.toString())
if (isWithdrawing !== undefined) {
throw new Error('Identity is already withdrawing')
}
this.mapIsWithdrawing.set(identityCommitment.toString(), true)
}
async releaseWithdrawal(identityCommitment: bigint): Promise<void> {
const rateCommitment = await this.getRateCommitment(identityCommitment)
const index = this.group.indexOf(rateCommitment)
if (index === -1) {
// Sanity check
throw new Error('Rate commitment is not in the merkle tree. This should never happen')
}
const isWithdrawing = this.mapIsWithdrawing.get(identityCommitment.toString())
if (isWithdrawing === undefined) {
throw new Error('Identity is not withdrawing')
}
this.mapIsWithdrawing.delete(identityCommitment.toString())
this.mapMessageLimit.delete(identityCommitment.toString())
this.group.removeMember(index)
}
async slash(identitySecret: bigint, _?: string): Promise<void> {
const identityCommitment = poseidon([identitySecret])
const rateCommitment = await this.getRateCommitment(identityCommitment)
const index = this.group.indexOf(rateCommitment)
if (index === -1) {
// Sanity check
throw new Error('Rate commitment is not in the merkle tree. This should never happen')
}
this.mapIsWithdrawing.delete(identityCommitment.toString())
this.mapMessageLimit.delete(identityCommitment.toString())
this.group.removeMember(index)
}
}

View File

@@ -1,32 +0,0 @@
import { toByteArray } from 'base64-js'
import { VerificationKey } from '../types'
import { params as rawDefaultRLN20Params } from './rln-20'
import { params as rawDefaultWithdrawParams } from './withdraw'
function decodeBase64(str: string): Uint8Array {
return new Uint8Array(toByteArray(str))
}
type ICircuitParams = {
wasmFile: Uint8Array,
finalZkey: Uint8Array,
}
type IRLNParams = ICircuitParams & { verificationKey: VerificationKey }
type IWithdrawParams = ICircuitParams
export const treeDepthToDefaultRLNParams: { [treeDepth: string]: IRLNParams } = {
'20': {
wasmFile: decodeBase64(rawDefaultRLN20Params.wasmFileB64),
finalZkey: decodeBase64(rawDefaultRLN20Params.finalZkeyB64),
verificationKey: JSON.parse(rawDefaultRLN20Params.verificationKey),
},
}
export const defaultWithdrawParams: IWithdrawParams = {
wasmFile: decodeBase64(rawDefaultWithdrawParams.wasmFileB64),
finalZkey: decodeBase64(rawDefaultWithdrawParams.finalZkeyB64),
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,548 +0,0 @@
import { Identity } from '@semaphore-protocol/identity'
import { VerificationKey } from './types'
import { DEFAULT_MERKLE_TREE_DEPTH, calculateIdentityCommitment, calculateSignalHash } from './common'
import { IRLNRegistry, ContractRLNRegistry } from './registry'
import { MemoryCache, EvaluatedProof, ICache, Status } from './cache'
import { IMessageIDCounter, MemoryMessageIDCounter } from './message-id-counter'
import { RLNFullProof, RLNProver, RLNVerifier } from './circuit-wrapper'
import { ethers } from 'ethers'
import { RLNContract } from './contract-wrapper'
import { defaultWithdrawParams, treeDepthToDefaultRLNParams } from './resources'
// Ref: https://github.com/Rate-Limiting-Nullifier/circom-rln/blob/55c7da2227b501175076bf73e3ff6dc512c4c813/circuits/rln.circom#L40
const LIMIT_BIT_SIZE = 16
const MAX_MESSAGE_LIMIT = (BigInt(1) << BigInt(LIMIT_BIT_SIZE)) - BigInt(1)
export interface IRLN {
/* Membership */
/**
* Register the user to the registry.
* @param userMessageLimit The message limit of the user.
* @param messageIDCounter The messageIDCounter is used to **safely** generate the latest messageID for the user.
* If not provided, a new `MemoryMessageIDCounter` is created.
*/
register(userMessageLimit: bigint, messageIDCounter?: IMessageIDCounter): Promise<void>
/**
* Withdraw the user from the registry.
*/
withdraw(): Promise<void>
/**
* Slash the user with the given secret.
* @param secretToBeSlashed The secret to be slashed.
* @param receiver The address of the slash reward receiver. If not provided,
* the signer will receive the reward.
*/
slash(secretToBeSlashed: bigint, receiver?: string): Promise<void>
/* Proof-related */
/**
* Create a proof for the given epoch and message.
* @param epoch the timestamp of the message
* @param message the message to be proved
*/
createProof(epoch: bigint, message: string): Promise<RLNFullProof>
/**
* Verify a RLNFullProof
* @param epoch the timestamp of the message
* @param message the message to be proved
* @param proof the RLNFullProof to be verified
*/
verifyProof(epoch: bigint, message: string, proof: RLNFullProof): Promise<boolean>
/**
* Save a proof to the cache and check if it's a spam.
* @param proof the RLNFullProof to save and detect spam
* @returns result of the check. It could be VALID if the proof hasn't been seen,
* or DUPLICATE if the proof has been seen before, else BREACH means it could be spam.
*/
saveProof(proof: RLNFullProof): Promise<EvaluatedProof>
}
/**
* RLN handles all operations for a RLN user, including registering, withdrawing, creating proof, verifying proof.
*/
export class RLN implements IRLN {
// the unique identifier of the app using RLN
readonly rlnIdentifier: bigint
// the semaphore identity of the user
readonly identity: Identity
// the prover allows user to generate proof with the RLN circuit
private prover?: RLNProver
// the verifier allows user to verify proof with the RLN circuit
private verifier?: RLNVerifier
// the registry that stores the registered users
private registry: IRLNRegistry
// the cache that stores proofs added by the user with `addProof`, and detect spams automatically
private cache: ICache
// the messageIDCounter is used to **safely** generate the latest messageID for the user
public messageIDCounter?: IMessageIDCounter
constructor(args: {
/** Required */
/**
* The unique identifier of the app using RLN. The identifier must be unique for every app.
*/
rlnIdentifier: bigint
/**
* `IRegistry` that stores the registered users. If not provided, a new `ContractRLNRegistry` is created.
* @see {@link ContractRLNRegistry}
*/
registry: IRLNRegistry
/** Optional */
/**
* Semaphore identity of the user. If not provided, a new `Identity` is created.
*/
identity?: Identity
/**
* Tree depth of the merkle tree used by the circuit. If not provided, the default value will be used.
* @default 20
*/
treeDepth?: number
/**
* The maximum number of epochs that the cache can store. If not provided, the default value will be used.
* This is only used when `cache` is not provided.
* @default 100
* @see {@link MemoryCache}
*/
cacheSize?: number
/**
* `ICache` that stores proofs added by the user with `addProof`, and detect spams automatically.
* If not provided, a new `MemoryCache` is created.
* @see {@link MemoryCache}
*/
cache?: ICache
// File paths of the wasm and zkey file. If not provided, `createProof` will not work.
/**
* File path of the RLN wasm file. If not provided, `createProof` will not work.
* @see {@link https://github.com/Rate-Limiting-Nullifier/circom-rln/blob/main/circuits/rln.circom}
*/
wasmFilePath?: string | Uint8Array
/**
* File path of the RLN final zkey file. If not provided, `createProof` will not work.
* @see {@link https://github.com/Rate-Limiting-Nullifier/circom-rln/blob/main/circuits/rln.circom}
*/
finalZkeyPath?: string | Uint8Array
// Verification key of the circuit. If not provided, `verifyProof` and `saveProof` will not work.
/**
* Verification key of the RLN circuit. If not provided, `verifyProof` and `saveProof` will not work.
* @see {@link https://github.com/Rate-Limiting-Nullifier/circom-rln/blob/main/circuits/rln.circom}
*/
verificationKey?: VerificationKey
}) {
if (args.rlnIdentifier < 0) {
throw new Error('rlnIdentifier must be positive')
}
if (args.treeDepth !== undefined && args.treeDepth <= 0) {
throw new Error('treeDepth must be positive')
}
if (args.cacheSize !== undefined && args.cacheSize <= 0) {
throw new Error('cacheSize must be positive')
}
this.rlnIdentifier = args.rlnIdentifier
this.registry = args.registry
this.cache = args.cache ? args.cache : new MemoryCache(args.cacheSize)
this.identity = args.identity ? args.identity : new Identity()
// 3. Else, leave them undefined
let wasmFilePath: string | Uint8Array | undefined
let finalZkeyPath: string | Uint8Array | undefined
let verificationKey: VerificationKey | undefined
const treeDepth = args.treeDepth ? args.treeDepth : DEFAULT_MERKLE_TREE_DEPTH
// If `args.treeDepth` is given, `wasmFilePath`, `finalZkeyPath`, and `verificationKey` will be
// set to default first
if (treeDepth !== undefined) {
const defaultParams = treeDepthToDefaultRLNParams[treeDepth]
if (defaultParams !== undefined) {
wasmFilePath = defaultParams.wasmFile
finalZkeyPath = defaultParams.finalZkey
verificationKey = defaultParams.verificationKey
}
}
// If `args.wasmFilePath`, `args.finalZkeyPath`, and `args.verificationKey` are given, use them
// over the default
wasmFilePath = args.wasmFilePath ? args.wasmFilePath : wasmFilePath
finalZkeyPath = args.finalZkeyPath ? args.finalZkeyPath : finalZkeyPath
verificationKey = args.verificationKey ? args.verificationKey : verificationKey
if ((wasmFilePath === undefined || finalZkeyPath === undefined) && verificationKey === undefined) {
throw new Error(
'Either both `wasmFilePath` and `finalZkeyPath` must be supplied to generate proofs, ' +
'or `verificationKey` must be provided to verify proofs.',
)
}
if (wasmFilePath !== undefined && finalZkeyPath !== undefined) {
this.prover = new RLNProver(wasmFilePath, finalZkeyPath)
}
if (verificationKey !== undefined) {
this.verifier = new RLNVerifier(verificationKey)
}
}
/**
* Create RLN instance, using a deployed RLN contract as registry.
*/
static createWithContractRegistry(args: {
/** Required */
/**
* The unique identifier of the app using RLN. The identifier must be unique for every app.
*/
rlnIdentifier: bigint
/**
* The ethers provider that is used to interact with the RLN contract.
* @see {@link https://docs.ethers.io/v5/api/providers/}
*/
provider: ethers.Provider
/**
* The address of the RLN contract.
*/
contractAddress: string
/** Optional */
/**
* The ethers signer that is used to interact with the RLN contract. If not provided,
* user can only do read-only operations. Functions like `register` and `withdraw` will not work
* since they need to send transactions to interact with the RLN contract.
* @see {@link https://docs.ethers.io/v5/api/signer/#Signer}
*/
signer?: ethers.Signer,
/**
* The block number where the RLN contract is deployed. If not provided, `0` will be used.
* @default 0
* @see {@link https://docs.ethers.io/v5/api/providers/provider/#Provider-getLogs}
*/
contractAtBlock?: number,
/**
* Semaphore identity of the user. If not provided, a new `Identity` is created.
*/
identity?: Identity
// File paths of the wasm and zkey file. If not provided, `createProof` will not work.
/**
* File path of the RLN wasm file. If not provided, `createProof` will not work.
* @see {@link https://github.com/Rate-Limiting-Nullifier/circom-rln/blob/main/circuits/rln.circom}
*/
wasmFilePath?: string | Uint8Array
/**
* File path of the RLN final zkey file. If not provided, `createProof` will not work.
* @see {@link https://github.com/Rate-Limiting-Nullifier/circom-rln/blob/main/circuits/rln.circom}
*/
finalZkeyPath?: string | Uint8Array
// Verification key of the circuit. If not provided, `verifyProof` and `saveProof` will not work.
/**
* Verification key of the RLN circuit. If not provided, `verifyProof` and `saveProof` will not work.
* @see {@link https://github.com/Rate-Limiting-Nullifier/circom-rln/blob/main/circuits/rln.circom}
*/
verificationKey?: VerificationKey
/**
* Tree depth of the merkle tree used by the circuit. If not provided, the default value will be used.
* @default 20
*/
treeDepth?: number
/* Registry configs */
/**
* File path of the wasm file for withdraw circuit. If not provided, `withdraw` will not work.
* @see {@link https://github.com/Rate-Limiting-Nullifier/circom-rln/blob/main/circuits/withdraw.circom}
*/
withdrawWasmFilePath?: string | Uint8Array,
/**
* File path of the final zkey file for withdraw circuit. If not provided, `withdraw` will not work.
* @see {@link https://github.com/Rate-Limiting-Nullifier/circom-rln/blob/main/circuits/withdraw.circom}
*/
withdrawFinalZkeyPath?: string | Uint8Array,
/** Others */
/**
* `ICache` that stores proofs added by the user with `addProof`, and detect spams automatically.
* If not provided, a new `MemoryCache` is created.
* @see {@link MemoryCache}
*/
cache?: ICache
/**
* The maximum number of epochs that the cache can store. If not provided, the default value will be used.
* This is only used when `cache` is not provided.
* @default 100
* @see {@link MemoryCache}
*/
cacheSize?: number
}) {
const rlnContractWrapper = new RLNContract({
provider: args.provider,
signer: args.signer,
contractAddress: args.contractAddress,
contractAtBlock: args.contractAtBlock ? args.contractAtBlock : 0,
})
const treeDepth = args.treeDepth ? args.treeDepth : DEFAULT_MERKLE_TREE_DEPTH
// If `args.withdrawWasmFilePath`, `args.withdrawFinalZkeyPath` are given, use them
// over the default
const withdrawWasmFilePath = args.withdrawWasmFilePath ? args.withdrawWasmFilePath : defaultWithdrawParams.wasmFile
const withdrawFinalZkeyPath = args.withdrawFinalZkeyPath ? args.withdrawFinalZkeyPath : defaultWithdrawParams.finalZkey
const registry = new ContractRLNRegistry({
rlnIdentifier: args.rlnIdentifier,
rlnContract: rlnContractWrapper,
treeDepth,
withdrawWasmFilePath: withdrawWasmFilePath,
withdrawFinalZkeyPath: withdrawFinalZkeyPath,
})
const argsWithRegistry = {
...args,
registry,
}
return new RLN(argsWithRegistry)
}
/**
* Set a custom messageIDCounter
* @param messageIDCounter The custom messageIDCounter
*/
async setMessageIDCounter(messageIDCounter?: IMessageIDCounter) {
if (await this.isRegistered() === false) {
throw new Error('Cannot set messageIDCounter for an unregistered user.')
}
if (messageIDCounter !== undefined) {
this.messageIDCounter = messageIDCounter
} else {
const userMessageLimit = await this.registry.getMessageLimit(this.identityCommitment)
this.messageIDCounter = new MemoryMessageIDCounter(userMessageLimit)
}
}
/**
* Set a custom cache
* @param cache The custom cache
*/
setCache(cache: ICache) {
this.cache = cache
}
/**
* Set a custom registry
* @param registry The custom registry
*/
setRegistry(registry: IRLNRegistry) {
this.registry = registry
}
/**
* Get the latest merkle root of the registry.
* @returns the latest merkle root of the registry
*/
async getMerkleRoot(): Promise<bigint> {
return this.registry.getMerkleRoot()
}
/**
* Get the identity commitment of the user.
*/
get identityCommitment(): bigint {
return this.identity.commitment
}
private get identitySecret(): bigint {
return calculateIdentityCommitment(this.identity)
}
/**
* Get the rate commitment of the user, i.e. hash(identitySecret, messageLimit)
* @returns the rate commitment
*/
async getRateCommitment(): Promise<bigint> {
return this.registry.getRateCommitment(this.identityCommitment)
}
/**
* @returns the user has been registered or not
*/
async isRegistered(): Promise<boolean> {
return this.registry.isRegistered(this.identityCommitment)
}
/**
* @returns all rate commitments in the registry
*/
async getAllRateCommitments(): Promise<bigint[]> {
return this.registry.getAllRateCommitments()
}
/**
* User registers to the registry.
* @param userMessageLimit the maximum number of messages that the user can send in one epoch
* @param messageIDCounter the messageIDCounter that the user wants to use. If not provided, a new `MemoryMessageIDCounter` is created.
*/
async register(userMessageLimit: bigint, messageIDCounter?: IMessageIDCounter) {
if (userMessageLimit <= BigInt(0) || userMessageLimit > MAX_MESSAGE_LIMIT) {
throw new Error(
`userMessageLimit must be in range (0, ${MAX_MESSAGE_LIMIT}]. Got ${userMessageLimit}.`,
)
}
await this.registry.register(this.identityCommitment, userMessageLimit)
this.messageIDCounter = messageIDCounter ? messageIDCounter : new MemoryMessageIDCounter(userMessageLimit)
}
/**
* User withdraws from the registry. User will not receive the funds immediately,
* they need to wait `freezePeriod + 1` blocks and call `releaseWithdrawal` to get the funds.
*/
async withdraw() {
await this.registry.withdraw(this.identitySecret)
}
/**
* Release the funds from the pending withdrawal requested by `withdraw`.
*/
async releaseWithdrawal() {
await this.registry.releaseWithdrawal(this.identityCommitment)
this.messageIDCounter = undefined
}
/**
* Slash a user by its identity secret.
* @param secretToBeSlashed the identity secret of the user to be slashed
* @param receiver the receiver of the slashed funds. If not provided, the funds will be sent to
* the `signer` given in the constructor.
*/
async slash(secretToBeSlashed: bigint, receiver?: string) {
await this.registry.slash(secretToBeSlashed, receiver)
}
/**
* Create a proof for the given epoch and message.
* @param epoch the epoch to create the proof for
* @param message the message to create the proof for
* @returns the RLNFullProof
*/
async createProof(epoch: bigint, message: string): Promise<RLNFullProof> {
if (epoch < 0) {
throw new Error('epoch cannot be negative')
}
if (this.prover === undefined) {
throw new Error('Prover is not initialized')
}
if (!await this.isRegistered()) {
throw new Error('User has not registered before')
}
if (this.messageIDCounter === undefined) {
throw new Error(
'State is not synced with the registry. ' +
'If user is currently registered, `messageIDCounter` should be non-undefined',
)
}
const merkleProof = await this.registry.generateMerkleProof(this.identityCommitment)
// NOTE: get the message id and increment the counter.
// Even if the message is not sent, the counter is still incremented.
// It's intended to avoid any possibly for user to reuse the same message id.
const messageId = await this.messageIDCounter.getMessageIDAndIncrement(epoch)
const userMessageLimit = await this.registry.getMessageLimit(this.identityCommitment)
const proof = await this.prover.generateProof({
rlnIdentifier: this.rlnIdentifier,
identitySecret: this.identitySecret,
userMessageLimit: userMessageLimit,
messageId,
merkleProof,
messageHash: calculateSignalHash(message),
epoch,
})
// Double check if the proof will spam or not using the cache.
// Even if messageIDCounter is used, it is possible that the user restart and the counter is reset.
const res = await this.checkProof(proof)
if (res.status === Status.DUPLICATE) {
throw new Error('Proof has been generated before')
} else if (res.status === Status.BREACH) {
throw new Error('Proof will spam')
} else if (res.status === Status.VALID) {
const resSaveProof = await this.saveProof(proof)
if (resSaveProof.status !== res.status) {
// Sanity check
throw new Error('Status of save proof and check proof mismatch')
}
return proof
} else {
// Sanity check
throw new Error('Unknown status')
}
}
/**
* Verify a proof is valid and indeed for `epoch` and `message`.
* @param epoch the epoch to verify the proof for
* @param message the message to verify the proof for
* @param proof the RLNFullProof to verify
* @returns true if the proof is valid, false otherwise
*/
async verifyProof(epoch: bigint, message: string, proof: RLNFullProof): Promise<boolean> {
if (epoch < BigInt(0)) {
throw new Error('epoch cannot be negative')
}
if (this.verifier === undefined) {
throw new Error('Verifier is not initialized')
}
// Check if the proof is using the same parameters
const snarkProof = proof.snarkProof
const epochInProof = proof.epoch
const rlnIdentifier = proof.rlnIdentifier
const { root, x } = snarkProof.publicSignals
// Check if the proof is using the same rlnIdentifier
if (BigInt(rlnIdentifier) !== this.rlnIdentifier) {
return false
}
// Check if the proof is using the same epoch
if (BigInt(epochInProof) !== epoch) {
return false
}
// Check if the proof and message match
const messageToX = calculateSignalHash(message)
if (BigInt(x) !== messageToX) {
return false
}
// Check if the merkle root is the same as the registry's
const registryMerkleRoot = await this.registry.getMerkleRoot()
if (BigInt(root) !== registryMerkleRoot) {
return false
}
// Verify snark proof
return this.verifier.verifyProof(rlnIdentifier, proof)
}
/**
* Save a proof to the cache and check if it's a spam.
* @param proof the RLNFullProof to save and detect spam
* @returns result of the check. `status` could be status.VALID if the proof is not a spam or invalid.
* Otherwise, it will be status.DUPLICATE or status.BREACH.
*/
async saveProof(proof: RLNFullProof): Promise<EvaluatedProof> {
const { snarkProof, epoch } = proof
const { x, y, nullifier } = snarkProof.publicSignals
return this.cache.addProof({ x, y, nullifier, epoch })
}
private async checkProof(proof: RLNFullProof): Promise<EvaluatedProof> {
const { snarkProof, epoch } = proof
const { x, y, nullifier } = snarkProof.publicSignals
return this.cache.checkProof({ x, y, nullifier, epoch })
}
/**
* Clean up the worker threads used by the prover and verifier in snarkjs
* This function should be called when the user is done with the library
* and wants to clean up the worker threads.
*
* Ref: https://github.com/iden3/snarkjs/issues/152#issuecomment-1164821515
*/
static cleanUp() {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
// TODO: error TS7017: Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature.
globalThis.curve_bn128.terminate()
}
}

View File

@@ -1,30 +0,0 @@
export type StrBigInt = string | bigint
/**
* snarkjs proof.
*/
export type Proof = {
pi_a: StrBigInt[]
pi_b: StrBigInt[][]
pi_c: StrBigInt[]
protocol: string
curve: string
}
/**
* snarkjs verification key.
*/
export type VerificationKey = {
protocol: string,
curve: string,
nPublic: number,
vk_alpha_1: string[],
vk_beta_2: string[][],
vk_gamma_2: string[][],
vk_delta_2: string[][],
vk_alphabeta_12: string[][][],
IC: string[][],
}
export { type MerkleProof } from '@zk-kit/incremental-merkle-tree'

View File

@@ -1,531 +0,0 @@
/* eslint "@typescript-eslint/no-explicit-any": 0 */
/* eslint "@typescript-eslint/no-unused-vars": 0 */
/** Declaration file generated by dts-gen */
export class BigBuffer {
constructor(...args: any[])
set(...args: any[]): void
slice(...args: any[]): void
}
export class ChaCha {
constructor(...args: any[])
nextBool(...args: any[]): void
nextU32(...args: any[]): void
nextU64(...args: any[]): void
update(...args: any[]): void
}
export class EC {
constructor(...args: any[])
add(...args: any[]): void
affine(...args: any[]): void
double(...args: any[]): void
eq(...args: any[]): void
fromRng(...args: any[]): void
fromRprBE(...args: any[]): void
fromRprBEM(...args: any[]): void
fromRprCompressed(...args: any[]): void
fromRprLE(...args: any[]): void
fromRprLEJM(...args: any[]): void
fromRprLEM(...args: any[]): void
fromRprUncompressed(...args: any[]): void
isZero(...args: any[]): void
mulScalar(...args: any[]): void
multiAffine(...args: any[]): void
neg(...args: any[]): void
sub(...args: any[]): void
timesScalar(...args: any[]): void
toRprBE(...args: any[]): void
toRprBEM(...args: any[]): void
toRprCompressed(...args: any[]): void
toRprLE(...args: any[]): void
toRprLEJM(...args: any[]): void
toRprLEM(...args: any[]): void
toRprUncompressed(...args: any[]): void
toString(...args: any[]): void
}
export class F1Field {
constructor(...args: any[])
e(...args: any[]): any
add(...args: any[]): any
sub(...args: any[]): any
neg(...args: any[]): any
mul(...args: any[]): any
mulScalar(...args: any[]): any
square(...args: any[]): any
eq(...args: any[]): any
neq(...args: any[]): any
lt(...args: any[]): any
gt(...args: any[]): any
leq(...args: any[]): any
geq(...args: any[]): any
div(...args: any[]): any
idiv(...args: any[]): any
inv(...args: any[]): any
mod(...args: any[]): any
pow(...args: any[]): any
exp(...args: any[]): any
band(...args: any[]): any
bor(...args: any[]): any
bxor(...args: any[]): any
bnot(...args: any[]): any
shl(...args: any[]): any
shr(...args: any[]): any
land(...args: any[]): any
lor(...args: any[]): any
lnot(...args: any[]): any
sqrt_old(...args: any[]): any
normalize(...args: any[]): any
random(...args: any[]): any
toString(...args: any[]): any
isZero(...args: any[]): any
fromRng(...args: any[]): any
fromRprBE(...args: any[]): void
fromRprBEM(...args: any[]): void
fromRprLE(...args: any[]): void
fromRprLEM(...args: any[]): void
toObject(...args: any[]): void
toRprBE(...args: any[]): void
toRprBEM(...args: any[]): void
toRprLE(...args: any[]): void
toRprLEM(...args: any[]): void
}
export class F2Field {
constructor(...args: any[])
add(...args: any[]): void
conjugate(...args: any[]): void
copy(...args: any[]): void
div(...args: any[]): void
double(...args: any[]): void
eq(...args: any[]): void
exp(...args: any[]): void
fromRng(...args: any[]): void
fromRprBE(...args: any[]): void
fromRprBEM(...args: any[]): void
fromRprLE(...args: any[]): void
fromRprLEM(...args: any[]): void
geq(...args: any[]): void
gt(...args: any[]): void
inv(...args: any[]): void
isZero(...args: any[]): void
leq(...args: any[]): void
lt(...args: any[]): void
mul(...args: any[]): void
mulScalar(...args: any[]): void
neg(...args: any[]): void
neq(...args: any[]): void
pow(...args: any[]): void
random(...args: any[]): void
square(...args: any[]): void
sub(...args: any[]): void
toObject(...args: any[]): void
toRprBE(...args: any[]): void
toRprBEM(...args: any[]): void
toRprLE(...args: any[]): void
toRprLEM(...args: any[]): void
toString(...args: any[]): void
}
export class F3Field {
constructor(...args: any[])
add(...args: any[]): void
affine(...args: any[]): void
copy(...args: any[]): void
div(...args: any[]): void
double(...args: any[]): void
eq(...args: any[]): void
exp(...args: any[]): void
fromRng(...args: any[]): void
fromRprBE(...args: any[]): void
fromRprBEM(...args: any[]): void
fromRprLE(...args: any[]): void
fromRprLEM(...args: any[]): void
geq(...args: any[]): void
gt(...args: any[]): void
inv(...args: any[]): void
isZero(...args: any[]): void
leq(...args: any[]): void
lt(...args: any[]): void
mul(...args: any[]): void
mulScalar(...args: any[]): void
neg(...args: any[]): void
neq(...args: any[]): void
pow(...args: any[]): void
random(...args: any[]): void
square(...args: any[]): void
sub(...args: any[]): void
toObject(...args: any[]): void
toRprBE(...args: any[]): void
toRprBEM(...args: any[]): void
toRprLE(...args: any[]): void
toRprLEM(...args: any[]): void
toString(...args: any[]): void
}
export class PolField {
constructor(...args: any[])
add(...args: any[]): void
computeVanishingPolinomial(...args: any[]): void
div(...args: any[]): void
double(...args: any[]): void
eq(...args: any[]): void
eval(...args: any[]): void
eval2(...args: any[]): void
evaluateLagrangePolynomials(...args: any[]): void
extend(...args: any[]): void
fft(...args: any[]): void
fft2(...args: any[]): void
ifft(...args: any[]): void
ifft2(...args: any[]): void
lagrange(...args: any[]): void
log2(...args: any[]): void
mul(...args: any[]): void
mulFFT(...args: any[]): void
mulNormal(...args: any[]): void
mulScalar(...args: any[]): void
normalize(...args: any[]): void
oneRoot(...args: any[]): void
reduce(...args: any[]): void
ruffini(...args: any[]): void
scaleX(...args: any[]): void
square(...args: any[]): void
sub(...args: any[]): void
toString(...args: any[]): void
}
export class ZqField {
constructor(...args: any[])
e(...args: any[]): any
add(...args: any[]): any
sub(...args: any[]): any
neg(...args: any[]): any
mul(...args: any[]): any
mulScalar(...args: any[]): any
square(...args: any[]): any
eq(...args: any[]): any
neq(...args: any[]): any
lt(...args: any[]): any
gt(...args: any[]): any
leq(...args: any[]): any
geq(...args: any[]): any
div(...args: any[]): any
idiv(...args: any[]): any
inv(...args: any[]): any
mod(...args: any[]): any
pow(...args: any[]): any
exp(...args: any[]): any
band(...args: any[]): any
bor(...args: any[]): any
bxor(...args: any[]): any
bnot(...args: any[]): any
shl(...args: any[]): any
shr(...args: any[]): any
land(...args: any[]): any
lor(...args: any[]): any
lnot(...args: any[]): any
sqrt_old(...args: any[]): any
normalize(...args: any[]): any
random(...args: any[]): any
toString(...args: any[]): any
isZero(...args: any[]): any
fromRng(...args: any[]): any
fromRprBE(...args: any[]): void
fromRprBEM(...args: any[]): void
fromRprLE(...args: any[]): void
fromRprLEM(...args: any[]): void
toObject(...args: any[]): void
toRprBE(...args: any[]): void
toRprBEM(...args: any[]): void
toRprLE(...args: any[]): void
toRprLEM(...args: any[]): void
}
export function buildBls12381(singleThread: any, plugins: any): any
export function buildBn128(singleThread: any, plugins: any): Promise<any>
export function getCurveFromName(name: any, singleThread: any, plugins: any): any
export function getCurveFromQ(q: any, singleThread: any, plugins: any): any
export function getCurveFromR(r: any, singleThread: any, plugins: any): any
export namespace Scalar {
const one: any
const zero: any
function abs(a: any): any
function add(a: any, b: any): any
function band(a: any, b: any): any
function bitLength(a: any): any
function bits(n: any): any
function bor(a: any, b: any): any
function bxor(a: any, b: any): any
function div(a: any, b: any): any
function e(s: any, radix: any): any
function eq(a: any, b: any): any
function exp(a: any, b: any): any
function fromArray(a: any, radix: any): any
function fromRprBE(buff: any, o: any, n8: any): any
function fromRprLE(buff: any, o: any, n8: any): any
function fromString(s: any, radix: any): any
function geq(a: any, b: any): any
function gt(a: any, b: any): any
function isNegative(a: any): any
function isOdd(a: any): any
function isZero(a: any): any
function land(a: any, b: any): any
function leq(a: any, b: any): any
function lnot(a: any): any
function lor(a: any, b: any): any
function lt(a: any, b: any): any
function mod(a: any, b: any): any
function mul(a: any, b: any): any
function naf(n: any): any
function neg(a: any): any
function neq(a: any, b: any): any
function pow(a: any, b: any): any
function shiftLeft(a: any, n: any): any
function shiftRight(a: any, n: any): any
function shl(a: any, n: any): any
function shr(a: any, n: any): any
function square(a: any): any
function sub(a: any, b: any): any
function toArray(s: any, radix: any): any
function toLEBuff(a: any): any
function toNumber(s: any): any
// e is inevitably shadowed since it's from ffjavascript and we shouldn't change it
function toRprBE(buff: any, o: any, e: any, n8: any): void // eslint-disable-line @typescript-eslint/no-shadow
// e is inevitably shadowed since it's from ffjavascript and we shouldn't change it
function toRprLE(buff: any, o: any, e: any, n8: any): void // eslint-disable-line @typescript-eslint/no-shadow
function toString(a: any, radix: any): any
}
export namespace utils {
function array2buffer(arr: any, sG: any): any
function beBuff2int(buff: any): any
function beInt2Buff(n: any, len: any): any
function bitReverse(idx: any, bits: any): any
function buffReverseBits(buff: any, eSize: any): void
function buffer2array(buff: any, sG: any): any
function leBuff2int(buff: any): any
function leInt2Buff(n: any, len: any): any
function log2(V: any): any
function stringifyBigInts(o: any): any
function stringifyFElements(F: any, o: any): any
function unstringifyBigInts(o: any): any
function unstringifyFElements(F: any, o: any): any
}

View File

@@ -1,59 +0,0 @@
/* eslint "@typescript-eslint/no-explicit-any": 0 */
/* eslint "@typescript-eslint/no-unused-vars": 0 */
/** Declaration file generated by dts-gen */
export = snarkjs // eslint-disable-line @typescript-eslint/no-use-before-define
declare const snarkjs: {
groth16: {
exportSolidityCallData: any;
fullProve(_input: any, wasmFile: any, zkeyFileName: any, logger?: any): Promise<any>;
prove(zkeyFileName: any, witnessFileName: any, logger?: any): Promise<any>;
verify(_vk_verifier: any, _publicSignals: any, _proof: any, logger?: any): Promise<any>;
};
plonk: {
exportSolidityCallData: any;
fullProve: any;
prove: any;
setup: any;
verify: any;
};
powersOfTau: {
beacon: any;
challengeContribute: any;
contribute: any;
convert: any;
exportChallenge: any;
exportJson: any;
importResponse: any;
newAccumulator: any;
preparePhase2: any;
truncate: any;
verify: any;
};
r1cs: {
exportJson: any;
info: any;
print: any;
};
wtns: {
calculate: any;
debug: any;
exportJson: any;
};
zKey: {
beacon: any;
bellmanContribute: any;
contribute: any;
exportBellman: any;
exportJson: any;
exportSolidityVerifier: any;
exportVerificationKey: any;
importBellman: any;
newZKey: any;
verifyFromInit: any;
verifyFromR1cs: any;
};
}

View File

@@ -1,82 +0,0 @@
import { MemoryCache } from "../src"
import { CachedProof, DEFAULT_CACHE_SIZE, Status } from '../src/cache'
import { fieldFactory } from "./utils"
describe("MemoryCache", () => {
const signal1 = BigInt(11111)
const signal2 = BigInt(22222)
const epoch1 = fieldFactory()
const epoch2 = fieldFactory([epoch1])
const cache = new MemoryCache()
let proof1: CachedProof;
let proof2: CachedProof;
let proof3: CachedProof;
let proof4: CachedProof;
beforeAll(async () => {
// NOTE: `internalNullifier` is determined by (identitySecret, rlnIdentifier, epoch, messageID)
const nullifier1 = fieldFactory()
const nullifier2 = fieldFactory([nullifier1])
const nullifier3 = fieldFactory([nullifier1, nullifier2])
// A random proof generated by user A
proof1 = {x: signal1, y: fieldFactory(), epoch: epoch1, nullifier: nullifier1}
// A proof generated by user A with different x
proof2 = {x: signal2, y: fieldFactory([BigInt(proof1.y)]), epoch: epoch1, nullifier: nullifier1}
// A proof generated by user B with the same x as proof2
// It possesses a different nullifier from `proof2` since identitySecret is different
proof3 = {x: signal2, y: fieldFactory(), epoch: epoch1, nullifier: nullifier2}
// A proof generated by user B with the same x as proof2 but different epoch
// It possesses a different nullifier from `proof3` since epoch is different
proof4 = {x: signal2, y: fieldFactory([BigInt(proof3.y)]), epoch: epoch2, nullifier: nullifier3}
})
test("should have a cache length of 100", () => {
expect(cache.cacheLength).toBe(DEFAULT_CACHE_SIZE)
})
test("should successfully add proof", () => {
const resultCheckProof = cache.checkProof(proof1)
expect(resultCheckProof.status).toBe(Status.VALID)
const result1 = cache.addProof(proof1)
expect(result1.status).toBe(resultCheckProof.status)
expect(Object.keys(cache.cache).length
).toBe(1)
})
test("should detect breach and return secret", () => {
const resultCheckProof = cache.checkProof(proof2)
expect(resultCheckProof.status).toBe(Status.BREACH)
expect(resultCheckProof.secret).toBeGreaterThan(0)
const result2 = cache.addProof(proof2)
expect(result2.status).toBe(resultCheckProof.status)
expect(result2.secret).toBeGreaterThan(0)
expect(Object.keys(cache.cache).length
).toBe(1)
})
test("should check proof 3", () => {
const resultCheckProof = cache.checkProof(proof3)
expect(resultCheckProof.status).toBe(Status.VALID)
const result3 = cache.addProof(proof3)
expect(result3.status).toBe(resultCheckProof.status)
})
test("should check proof 4", () => {
const resultCheckProof = cache.checkProof(proof4)
expect(resultCheckProof.status).toBe(Status.VALID)
const result4 = cache.addProof(proof4)
expect(result4.status).toBe(resultCheckProof.status)
// two epochs are added to the cache now
expect(Object.keys(cache.cache).length
).toBe(2)
})
test("should fail for proof 1 (duplicate proof)", () => {
// Proof 1 is already in the cache
const resultCheckProof = cache.checkProof(proof1)
expect(resultCheckProof.status).toBe(Status.DUPLICATE)
const result1 = cache.addProof(proof1)
expect(result1.status).toBe(Status.DUPLICATE)
});
})

View File

@@ -1,66 +0,0 @@
import { RLNProver, RLNVerifier, WithdrawProver, WithdrawVerifier } from '../src/circuit-wrapper';
import { rlnParams, withdrawParams } from './configs';
import { fieldFactory, generateMerkleProof } from './utils';
import poseidon from 'poseidon-lite';
import { DEFAULT_MERKLE_TREE_DEPTH } from '../src/common';
// `userMessageLimit` is at most 16 bits
// Ref: https://github.com/Rate-Limiting-Nullifier/rln-circuits-v2/blob/b40dfa63b7b1248527d7ab417d0d9cf538cad93a/circuits/utils.circom#L36-L37
const LIMIT_BIT_SIZE = 16;
describe('RLN', function () {
const rlnIdentifier = fieldFactory();
const rlnProver = new RLNProver(rlnParams.wasmFilePath, rlnParams.finalZkeyPath);
const rlnVerifier = new RLNVerifier(rlnParams.verificationKey);
const identitySecret = fieldFactory();
const identityCommitment = poseidon([identitySecret]);
const leaves = [identityCommitment];
const userMessageLimit = (BigInt(1) << BigInt(LIMIT_BIT_SIZE)) - BigInt(1);
const messageId = BigInt(0);
const x = fieldFactory();
const epoch = fieldFactory();
const treeDepth = DEFAULT_MERKLE_TREE_DEPTH;
test('should generate valid proof', async function () {
const m0 = performance.now();
const merkleProof = generateMerkleProof(rlnIdentifier, leaves, treeDepth, 0);
const m1 = performance.now();
const proof = await rlnProver.generateProof({
rlnIdentifier,
identitySecret,
userMessageLimit,
messageId,
merkleProof,
messageHash: x,
epoch
});
const m2 = performance.now();
const isValid = await rlnVerifier.verifyProof(rlnIdentifier, proof);
const m3 = performance.now();
console.log(`Merkle proof generation: ${m1 - m0} ms`);
console.log(`RLN proof generation: ${m2 - m1} ms`);
console.log(`RLN proof verification: ${m3 - m2} ms`);
expect(isValid).toBeTruthy();
});
});
describe('Withdraw', function () {
const withdrawProver = new WithdrawProver(
withdrawParams.wasmFilePath,
withdrawParams.finalZkeyPath
);
const withdrawVerifier = new WithdrawVerifier(withdrawParams.verificationKey);
test('should generate valid proof', async function () {
const identitySecret = fieldFactory();
const address = fieldFactory();
const m0 = performance.now();
const proof = await withdrawProver.generateProof({ identitySecret, address });
const m1 = performance.now();
const isValid = await withdrawVerifier.verifyProof(proof);
const m2 = performance.now();
console.log(`Withdraw proof generation: ${m1 - m0} ms`);
console.log(`Withdraw proof verification: ${m2 - m1} ms`);
expect(isValid).toBeTruthy();
});
});

File diff suppressed because one or more lines are too long

View File

@@ -1,155 +0,0 @@
import { ethers } from "ethers";
import { ChildProcessWithoutNullStreams } from "child_process";
import { RLNContract } from "../src/contract-wrapper";
import { DEFAULT_MERKLE_TREE_DEPTH } from "../src/common";
import { fieldFactory } from "./utils";
import { Proof } from "../src";
import { setupTestingContracts } from "./factories";
describe("RLNContract", () => {
let node: ChildProcessWithoutNullStreams
let provider: ethers.JsonRpcProvider
let signer: ethers.Signer
let rlnContract: ethers.Contract
let erc20Contract: ethers.Contract
let mockVerifierContract: ethers.Contract
let rlnContractWrapper: RLNContract
let waitUntilFreezePeriodPassed: () => Promise<void>
let killNode: () => Promise<void>
let signerAnother: ethers.Signer
let rlnContractWrapperAnother: RLNContract
const tokenAmount = BigInt("1000000000000000000")
// 10 token
const minimalDeposit = BigInt(10)
const treeDepth = DEFAULT_MERKLE_TREE_DEPTH
// 10%
const feePercentage = BigInt(10)
const feeReceiver = "0x0000000000000000000000000000000000005566"
const freezePeriod = BigInt(1)
const expectedMessageLimit = BigInt(2)
const expectedDepositAmount = expectedMessageLimit * minimalDeposit
const mockProof: Proof = {
pi_a: [fieldFactory(), fieldFactory()],
pi_b: [
[
fieldFactory(),
fieldFactory(),
],
[
fieldFactory(),
fieldFactory(),
],
],
pi_c: [fieldFactory(), fieldFactory()],
protocol: "groth",
curve: "bn128",
}
const identityCommitment = fieldFactory()
const identityCommitmentAnother = fieldFactory()
beforeAll(async () => {
const deployed = await setupTestingContracts({
initialTokenAmount: tokenAmount,
minimalDeposit,
treeDepth,
feePercentage,
feeReceiver,
freezePeriod,
});
node = deployed.node
provider = deployed.provider
signer = deployed.signer0
mockVerifierContract = deployed.mockVerifierContract
erc20Contract = deployed.erc20Contract
rlnContract = deployed.rlnContract
rlnContractWrapper = deployed.rlnContractWrapper
waitUntilFreezePeriodPassed = deployed.waitUntilFreezePeriodPassed
killNode = deployed.killNode
signerAnother = deployed.signer1
rlnContractWrapperAnother = new RLNContract({
provider,
signer: signerAnother,
contractAddress: await rlnContract.getAddress(),
contractAtBlock: deployed.contractAtBlock,
})
});
afterAll(async () => {
console.log("killing node")
await killNode()
console.log("node killed")
});
test("should be enough tokens in signer account", async () => {
expect(await erc20Contract.balanceOf(await signer.getAddress())).toBeGreaterThanOrEqual(expectedDepositAmount)
});
// RLNContract
test("should register", async () => {
const balanceBefore = await erc20Contract.balanceOf(await signer.getAddress())
await rlnContractWrapper.register(identityCommitment, expectedMessageLimit)
const balanceAfter = await erc20Contract.balanceOf(await signer.getAddress())
const user = await rlnContractWrapper.getUser(identityCommitment)
expect(user.userAddress).toBe(await signer.getAddress())
expect(user.messageLimit).toBe(expectedMessageLimit)
expect(user.index).toBe(BigInt(0))
expect(balanceBefore - balanceAfter).toBe(expectedDepositAmount)
});
test("should withdraw and release", async () => {
// Test: after calling withdraw, user should not receive tokens until calling release
const balanceBefore = await erc20Contract.balanceOf(await signer.getAddress())
await rlnContractWrapper.withdraw(identityCommitment, mockProof)
const balanceAfter = await erc20Contract.balanceOf(await signer.getAddress())
expect(balanceAfter - balanceBefore).toBe(BigInt(0))
// Release
await expect(async () => {
await rlnContractWrapper.release(identityCommitment)
}).rejects.toThrow('RLN, release: cannot release yet')
await waitUntilFreezePeriodPassed()
// Test: should receive tokens after release
const balanceBeforeRelease = await erc20Contract.balanceOf(await signer.getAddress())
await rlnContractWrapper.release(identityCommitment)
const balanceAfterRelease = await erc20Contract.balanceOf(await signer.getAddress())
expect(balanceAfterRelease - balanceBeforeRelease).toBe(expectedDepositAmount)
});
test("should register another and slash with proof", async () => {
// Test: should register
await rlnContractWrapperAnother.register(identityCommitmentAnother, expectedMessageLimit)
const user = await rlnContractWrapperAnother.getUser(identityCommitmentAnother)
expect(user.userAddress).toBe(await signerAnother.getAddress())
expect(user.messageLimit).toBe(expectedMessageLimit)
expect(user.index).toBe(BigInt(1))
// Test: should be slashed since verifier always consider proof valid
const slashReceiver = "0x0000000000000000000000000000000000001234"
const expectedFee = expectedDepositAmount * feePercentage / BigInt(100)
const expectedReceivedAmount = expectedDepositAmount - expectedFee
const balanceBefore = await erc20Contract.balanceOf(slashReceiver)
await rlnContractWrapper.slash(identityCommitmentAnother, slashReceiver, mockProof)
const balanceAfter = await erc20Contract.balanceOf(slashReceiver)
expect(balanceAfter - balanceBefore).toBe(expectedReceivedAmount)
});
test("should get logs", async () => {
const logs = await rlnContractWrapper.getLogs()
expect(logs.length).toBe(4)
expect(logs[0].name).toBe("MemberRegistered")
expect(logs[1].name).toBe("MemberWithdrawn")
expect(logs[2].name).toBe("MemberRegistered")
expect(logs[3].name).toBe("MemberSlashed")
})
});

View File

@@ -1,144 +0,0 @@
import { ethers } from "ethers";
import { spawn } from "child_process";
import { rlnContractABI, RLNContract } from "../src/contract-wrapper";
import { rlnContractBytecode, testERC20ContractBytecode, mockVerifierBytecode } from "./configs";
const testERC20ABI = '[{"inputs": [{"internalType": "uint256", "name": "amount", "type": "uint256"}], "stateMutability": "nonpayable", "type": "constructor"}, {"anonymous": false, "inputs": [{"indexed": true, "internalType": "address", "name": "owner", "type": "address"}, {"indexed": true, "internalType": "address", "name": "spender", "type": "address"}, {"indexed": false, "internalType": "uint256", "name": "value", "type": "uint256"}], "name": "Approval", "type": "event"}, {"anonymous": false, "inputs": [{"indexed": true, "internalType": "address", "name": "from", "type": "address"}, {"indexed": true, "internalType": "address", "name": "to", "type": "address"}, {"indexed": false, "internalType": "uint256", "name": "value", "type": "uint256"}], "name": "Transfer", "type": "event"}, {"inputs": [{"internalType": "address", "name": "owner", "type": "address"}, {"internalType": "address", "name": "spender", "type": "address"}], "name": "allowance", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"inputs": [{"internalType": "address", "name": "spender", "type": "address"}, {"internalType": "uint256", "name": "amount", "type": "uint256"}], "name": "approve", "outputs": [{"internalType": "bool", "name": "", "type": "bool"}], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"internalType": "address", "name": "account", "type": "address"}], "name": "balanceOf", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"inputs": [], "name": "decimals", "outputs": [{"internalType": "uint8", "name": "", "type": "uint8"}], "stateMutability": "view", "type": "function"}, {"inputs": [{"internalType": "address", "name": "spender", "type": "address"}, {"internalType": "uint256", "name": "subtractedValue", "type": "uint256"}], "name": "decreaseAllowance", "outputs": [{"internalType": "bool", "name": "", "type": "bool"}], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"internalType": "address", "name": "spender", "type": "address"}, {"internalType": "uint256", "name": "addedValue", "type": "uint256"}], "name": "increaseAllowance", "outputs": [{"internalType": "bool", "name": "", "type": "bool"}], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [], "name": "name", "outputs": [{"internalType": "string", "name": "", "type": "string"}], "stateMutability": "view", "type": "function"}, {"inputs": [], "name": "symbol", "outputs": [{"internalType": "string", "name": "", "type": "string"}], "stateMutability": "view", "type": "function"}, {"inputs": [], "name": "totalSupply", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"inputs": [{"internalType": "address", "name": "to", "type": "address"}, {"internalType": "uint256", "name": "amount", "type": "uint256"}], "name": "transfer", "outputs": [{"internalType": "bool", "name": "", "type": "bool"}], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"internalType": "address", "name": "from", "type": "address"}, {"internalType": "address", "name": "to", "type": "address"}, {"internalType": "uint256", "name": "amount", "type": "uint256"}], "name": "transferFrom", "outputs": [{"internalType": "bool", "name": "", "type": "bool"}], "stateMutability": "nonpayable", "type": "function"}]'
const mockVerifierABI = '[{"inputs": [], "stateMutability": "nonpayable", "type": "constructor"}, {"inputs": [{"internalType": "bool", "name": "_result", "type": "bool"}], "name": "changeResult", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [], "name": "result", "outputs": [{"internalType": "bool", "name": "", "type": "bool"}], "stateMutability": "view", "type": "function"}, {"inputs": [{"internalType": "uint256[2]", "name": "", "type": "uint256[2]"}, {"internalType": "uint256[2][2]", "name": "", "type": "uint256[2][2]"}, {"internalType": "uint256[2]", "name": "", "type": "uint256[2]"}, {"internalType": "uint256[2]", "name": "", "type": "uint256[2]"}], "name": "verifyProof", "outputs": [{"internalType": "bool", "name": "", "type": "bool"}], "stateMutability": "view", "type": "function"}]'
const timeout = 100000
async function deployContract(signer: ethers.Signer, bytecode: string, abi: string, args?: any[]) {
const factory = new ethers.ContractFactory(abi, bytecode, signer)
if (!args) {
args = []
}
const contract = await factory.deploy(...args)
await contract.waitForDeployment()
const address = await contract.getAddress()
return new ethers.Contract(address, abi, signer)
}
export async function setupTestingContracts(args: {
initialTokenAmount: bigint,
minimalDeposit: bigint,
treeDepth: number,
feePercentage: bigint,
feeReceiver: string,
freezePeriod: bigint,
}) {
// Let os choose port for us to avoid conflicts
const node = spawn("npx", ["hardhat", "node", "--port", "0"])
const pid = node.pid
if (!pid) {
throw new Error("process failed to start")
}
// This line is printed when the node is ready
const endString = "Any funds sent to them on Mainnet or any other live network WILL BE LOST."
let url: string | undefined = undefined;
await new Promise((resolve, reject) => {
const t = setTimeout(() => {
reject(new Error("Timeout when waiting for hardhat node to start"))
}, timeout)
const f = (data) => {
const dataString = data.toString()
// Get url printed from the node
const ipAddressMatch = dataString.match(/Started HTTP and WebSocket JSON-RPC server at (http:\/\/127\.0\.0\.1:\d+)/)
if (ipAddressMatch !== null) {
url = ipAddressMatch[1]
return;
}
// If we see this line, we know the node is ready and we can return
if (dataString.indexOf(endString) !== -1) {
clearTimeout(t)
resolve(undefined)
node.stdout.removeListener("data", f)
}
}
node.stdout.on("data", f);
})
if (url === undefined) {
throw new Error("Failed to get url from hardhat node")
}
const provider = new ethers.JsonRpcProvider(url)
const signer0 = await provider.getSigner(0)
const signer1 = await provider.getSigner(1)
const mockVerifierContract = await deployContract(signer0, mockVerifierBytecode, mockVerifierABI)
const erc20Contract = await deployContract(signer0, testERC20ContractBytecode, testERC20ABI, [args.initialTokenAmount])
// Gives signer1 some tokens to test
await erc20Contract.transfer(await signer1.getAddress(), args.initialTokenAmount / BigInt(2));
const contractAtBlock = await provider.getBlockNumber()
const rlnContractArgs = [
args.minimalDeposit,
args.treeDepth,
args.feePercentage,
args.feeReceiver,
args.freezePeriod,
await erc20Contract.getAddress(),
await mockVerifierContract.getAddress(),
]
const rlnContract = await deployContract(signer0, rlnContractBytecode, rlnContractABI, rlnContractArgs)
const rlnContractWrapper = new RLNContract({
provider,
signer: signer0,
contractAddress: await rlnContract.getAddress(),
contractAtBlock,
})
async function waitUntilFreezePeriodPassed() {
const numBlocks = Number(args.freezePeriod) + 1
const blockNumberBefore = await provider.getBlockNumber()
for (let i = 0; i < numBlocks; i++) {
const tx = await mockVerifierContract.changeResult(true)
await tx.wait()
}
const blockNumberAfter = await provider.getBlockNumber()
if (blockNumberAfter - blockNumberBefore !== numBlocks) {
throw new Error(`Expected to mine ${numBlocks} blocks, but mined ${blockNumberAfter - blockNumberBefore} blocks`)
}
}
// Kill node
async function killNode() {
await new Promise((resolve, reject) => {
// Only wait for timeout seconds
const t = setTimeout(() => {
reject(new Error('Killing node process timeout'));
}, timeout);
// Returns when node is killed
node.on('exit', (code, signal) => {
console.log(
`RPC node exited with code = ${code}, signal = ${signal}`
);
clearTimeout(t);
resolve(undefined)
});
// FIXME: Still not able to solve "A worker process has failed to exit
// gracefully and has been force exited. This is likely caused by tests leaking due to
// improper teardown. Try running with --detectOpenHandles to find leaks.
// Active timers can also cause this, ensure that .unref() was called on them."
// NOTE: No errors are shown when `detectOpenHandles: true` is set in jest.config.ts
node.stdin.destroy();
node.kill();
node.unref();
})
}
return {
node,
provider,
signer0,
signer1,
mockVerifierContract,
erc20Contract,
rlnContract,
rlnContractWrapper,
contractAtBlock,
waitUntilFreezePeriodPassed,
killNode,
}
}

View File

@@ -1,66 +0,0 @@
import { Fq } from "../src/common"
describe("Field arithmetics", () => {
describe("Test bunch of calculations in Fq", () => {
test("Retrieve n from y = kx + n", () => {
const k = Fq.random()
const n = Fq.random()
const x1 = Fq.random()
const y1 = Fq.add(Fq.mul(k, x1), n)
const x2 = Fq.random()
const y2 = Fq.add(Fq.mul(k, x2), n)
const ydiff = Fq.sub(y2, y1)
const xdiff = Fq.sub(x2, x1)
const slope = Fq.div(ydiff, xdiff)
const retrieved = Fq.sub(y1, Fq.mul(x1, slope))
expect(retrieved).toEqual(n)
})
test("Lagrange in Fq", () => {
const degree = 4
const coeffs: Array<bigint> = [BigInt(7), BigInt(6), BigInt(9), BigInt(1), BigInt(7)]
const xs: Array<bigint> = []
for (let i = 0; i < degree; i += 1) {
xs.push(BigInt(i))
}
const ys: Array<bigint> = []
for (let i = 0; i < degree; i += 1) {
const x: bigint = xs[i]
let tmpX: bigint = x
let y: bigint = coeffs[0]
for (let j = 1; j < degree + 1; j += 1) {
y = Fq.add(y, Fq.mul(tmpX, coeffs[j]))
tmpX = Fq.mul(tmpX, x)
}
ys.push(y)
}
let f0 = BigInt(0)
for (let i = 0; i < degree; i += 1) {
let p = BigInt(1)
for (let j = 0; j < degree; j += 1) {
if (j !== i) {
p = Fq.mul(p, Fq.div(xs[j], Fq.sub(xs[j], xs[i])))
}
}
f0 = Fq.add(f0, Fq.mul(ys[i], p))
}
expect(Fq.eq(f0, coeffs[0])).toBe(true)
})
})
})

View File

@@ -1,39 +0,0 @@
import { FakeMessageIDCounter } from "./utils";
describe('MessageIDCounter', () => {
const messageLimit = BigInt(1)
const messageIDCounter = new FakeMessageIDCounter(messageLimit)
test('should return correct message limit', async () => {
expect(messageIDCounter.messageLimit).toEqual(messageLimit)
});
test('should return correct message ID', async () => {
const epoch0 = BigInt(0)
const epoch1 = BigInt(1)
const epoch2 = BigInt(2)
// Initial state
expect(await messageIDCounter.peekNextMessageID(epoch0)).toEqual(BigInt(0))
// Test: try increment for epoch 0
expect(await messageIDCounter.getMessageIDAndIncrement(epoch0)).toEqual(BigInt(0))
expect(await messageIDCounter.peekNextMessageID(epoch0)).toEqual(BigInt(1))
// Test: try increment for epoch 1
expect(await messageIDCounter.peekNextMessageID(epoch1)).toEqual(BigInt(0))
expect(await messageIDCounter.getMessageIDAndIncrement(epoch1)).toEqual(BigInt(0))
expect(await messageIDCounter.peekNextMessageID(epoch1)).toEqual(BigInt(1))
// epoch0 is not affected
expect(await messageIDCounter.peekNextMessageID(epoch0)).toEqual(BigInt(1))
// Test: try increment for both epoch 0 and 1 again, message limits are reached
await expect(async () => {
await messageIDCounter.getMessageIDAndIncrement(epoch0)
}).rejects.toThrow('Message ID counter exceeded message limit')
await expect(async () => {
await messageIDCounter.getMessageIDAndIncrement(epoch1)
}).rejects.toThrow('Message ID counter exceeded message limit')
// Test: still work for epoch 2
expect(await messageIDCounter.getMessageIDAndIncrement(epoch2)).toEqual(BigInt(0))
});
});

View File

@@ -1,255 +0,0 @@
import { IncrementalMerkleTree } from "@zk-kit/incremental-merkle-tree"
import { calculateRateCommitment } from '../src/common';
import { ContractRLNRegistry, MemoryRLNRegistry, IRLNRegistry } from '../src/registry'
import { fieldFactory } from './utils';
import poseidon from "poseidon-lite";
import { zeroPad } from '@ethersproject/bytes'
import { BigNumber } from '@ethersproject/bignumber'
import { keccak256 } from '@ethersproject/keccak256'
import { setupTestingContracts } from "./factories";
import { withdrawParams } from "./configs";
describe('MemoryRLNRegistry', () => {
let registry: IRLNRegistry;
const rlnIdentifier = BigInt(1)
const identitySecret0 = fieldFactory();
const identitySecret1 = fieldFactory([identitySecret0]);
const identityCommitment0 = poseidon([identitySecret0]);
const identityCommitment1 = poseidon([identitySecret1]);
const messageLimit0 = BigInt(100)
const messageLimit1 = BigInt(101)
const treeDepth = 20
beforeAll(() => {
registry = new MemoryRLNRegistry(rlnIdentifier, treeDepth);
});
test('should be initialized correctly', async () => {
expect(await registry.getAllRateCommitments()).toEqual([])
});
test('should fail when we query `identityCommitment0` before registration', async () => {
expect(await registry.isRegistered(identityCommitment0)).toBeFalsy()
await expect(async () => {
await registry.getMessageLimit(identityCommitment0)
}).rejects.toThrow()
await expect(async () => {
await registry.getRateCommitment(identityCommitment0)
}).rejects.toThrow()
await expect(async () => {
await registry.generateMerkleProof(identityCommitment0)
}).rejects.toThrow()
});
test('should register with `messageLimit`', async () => {
await registry.register(identityCommitment0, messageLimit0)
expect(await registry.isRegistered(identityCommitment0)).toBeTruthy()
expect(await registry.getMessageLimit(identityCommitment0)).toEqual(messageLimit0)
const expectedRateCommitment = calculateRateCommitment(identityCommitment0, messageLimit0);
expect(await registry.getRateCommitment(identityCommitment0)).toEqual(expectedRateCommitment)
});
test('should fail to register `identityCommitment0` again', async () => {
await expect(async () => {
await registry.register(identityCommitment0, messageLimit0)
}).rejects.toThrow()
// Even with different message limit
await expect(async () => {
await registry.register(identityCommitment0, messageLimit1)
}).rejects.toThrow()
});
test('should register `identityCommitment1`', async () => {
await registry.register(identityCommitment1, messageLimit1)
expect(await registry.isRegistered(identityCommitment1)).toBeTruthy()
expect(await registry.getMessageLimit(identityCommitment1)).toEqual(messageLimit1)
const expectedRateCommitment = calculateRateCommitment(identityCommitment1, messageLimit1);
expect(await registry.getRateCommitment(identityCommitment1)).toEqual(expectedRateCommitment)
});
test('should delete `identityCommitment0`', async () => {
await registry.withdraw(identitySecret0)
await registry.releaseWithdrawal(identityCommitment0)
expect(await registry.isRegistered(identityCommitment0)).toBeFalsy()
await expect(async () => {
await registry.getMessageLimit(identityCommitment0)
}).rejects.toThrow()
await expect(async () => {
await registry.getRateCommitment(identityCommitment0)
}).rejects.toThrow()
});
test('should fail to delete `identityCommitment0` again', async () => {
await expect(async () => {
await registry.withdraw(identitySecret0)
}).rejects.toThrow()
});
test('should return correct final states', async () => {
expect((await registry.getAllRateCommitments()).length).toEqual(2)
});
test('should generate valid merkle proof for `identityCommitment1`', async () => {
const proof = await registry.generateMerkleProof(identityCommitment1)
expect(proof.root).toEqual(await registry.getMerkleRoot())
function calculateZeroValue(message: bigint): bigint {
const hexStr = BigNumber.from(message).toTwos(256).toHexString()
const zeroPadded = zeroPad(hexStr, 32)
return BigInt(keccak256(zeroPadded)) >> BigInt(8)
}
function verifyMerkleProof() {
const zeroValue = calculateZeroValue(BigInt(1))
const tree = new IncrementalMerkleTree(poseidon, treeDepth, zeroValue, 2)
proof.siblings = proof.siblings.map((s) => [s])
return tree.verifyProof(proof)
}
expect(verifyMerkleProof()).toBeTruthy()
});
});
describe('ContractRLNRegistry', () => {
let waitUntilFreezePeriodPassed: () => Promise<void>
let killNode: () => Promise<void>
let registry: IRLNRegistry;
const rlnIdentifier = BigInt(1)
const identitySecret0 = fieldFactory();
const identitySecret1 = fieldFactory([identitySecret0]);
const identityCommitment0 = poseidon([identitySecret0]);
const identityCommitment1 = poseidon([identitySecret1]);
const messageLimit0 = BigInt(100)
const messageLimit1 = BigInt(101)
const treeDepth = 20
const tokenAmount = BigInt("1000000000000000000")
// 10 token
const minimalDeposit = BigInt(10)
// 10%
const feePercentage = BigInt(10)
const feeReceiver = "0x0000000000000000000000000000000000005566"
const freezePeriod = BigInt(1)
beforeAll(async () => {
const deployed = await setupTestingContracts({
initialTokenAmount: tokenAmount,
minimalDeposit,
treeDepth,
feePercentage,
feeReceiver,
freezePeriod,
});
waitUntilFreezePeriodPassed = deployed.waitUntilFreezePeriodPassed
killNode = deployed.killNode
registry = new ContractRLNRegistry({
rlnIdentifier,
rlnContract: deployed.rlnContractWrapper,
treeDepth,
withdrawWasmFilePath: withdrawParams.wasmFilePath,
withdrawFinalZkeyPath: withdrawParams.finalZkeyPath,
})
registry = new MemoryRLNRegistry(rlnIdentifier, treeDepth);
});
afterAll(async () => {
console.log("killing node")
await killNode()
console.log("node killed")
});
test('should be initialized correctly', async () => {
expect(await registry.getAllRateCommitments()).toEqual([])
});
test('should fail when we query `identityCommitment0` before registration', async () => {
expect(await registry.isRegistered(identityCommitment0)).toBeFalsy()
await expect(async () => {
await registry.getMessageLimit(identityCommitment0)
}).rejects.toThrow()
await expect(async () => {
await registry.getRateCommitment(identityCommitment0)
}).rejects.toThrow()
await expect(async () => {
await registry.generateMerkleProof(identityCommitment0)
}).rejects.toThrow()
});
test('should register with `messageLimit`', async () => {
await registry.register(identityCommitment0, messageLimit0)
expect(await registry.isRegistered(identityCommitment0)).toBeTruthy()
expect(await registry.getMessageLimit(identityCommitment0)).toEqual(messageLimit0)
const expectedRateCommitment = calculateRateCommitment(identityCommitment0, messageLimit0);
expect(await registry.getRateCommitment(identityCommitment0)).toEqual(expectedRateCommitment)
});
test('should fail to register `identityCommitment0` again', async () => {
await expect(async () => {
await registry.register(identityCommitment0, messageLimit0)
}).rejects.toThrow()
// Even with different message limit
await expect(async () => {
await registry.register(identityCommitment0, messageLimit1)
}).rejects.toThrow()
});
test('should register `identityCommitment1`', async () => {
await registry.register(identityCommitment1, messageLimit1)
expect(await registry.isRegistered(identityCommitment1)).toBeTruthy()
expect(await registry.getMessageLimit(identityCommitment1)).toEqual(messageLimit1)
const expectedRateCommitment = calculateRateCommitment(identityCommitment1, messageLimit1);
expect(await registry.getRateCommitment(identityCommitment1)).toEqual(expectedRateCommitment)
});
test('should delete `identityCommitment0`', async () => {
await registry.withdraw(identitySecret0)
await waitUntilFreezePeriodPassed()
await registry.releaseWithdrawal(identityCommitment0)
expect(await registry.isRegistered(identityCommitment0)).toBeFalsy()
await expect(async () => {
await registry.getMessageLimit(identityCommitment0)
}).rejects.toThrow()
await expect(async () => {
await registry.getRateCommitment(identityCommitment0)
}).rejects.toThrow()
});
test('should fail to delete `identityCommitment0` again', async () => {
await expect(async () => {
await registry.withdraw(identitySecret0)
}).rejects.toThrow()
});
test('should return correct final states', async () => {
expect((await registry.getAllRateCommitments()).length).toEqual(2)
});
test('should generate valid merkle proof for `identityCommitment1`', async () => {
const proof = await registry.generateMerkleProof(identityCommitment1)
expect(proof.root).toEqual(await registry.getMerkleRoot())
function calculateZeroValue(message: bigint): bigint {
const hexStr = BigNumber.from(message).toTwos(256).toHexString()
const zeroPadded = zeroPad(hexStr, 32)
return BigInt(keccak256(zeroPadded)) >> BigInt(8)
}
function verifyMerkleProof() {
const zeroValue = calculateZeroValue(BigInt(1))
const tree = new IncrementalMerkleTree(poseidon, treeDepth, zeroValue, 2)
proof.siblings = proof.siblings.map((s) => [s])
return tree.verifyProof(proof)
}
expect(verifyMerkleProof()).toBeTruthy()
});
});

View File

@@ -1,353 +0,0 @@
import { RLN, RLNFullProof } from "../src";
import { ICache, MemoryCache, Status } from "../src/cache";
import { rlnParams, withdrawParams } from "./configs";
import { ethers } from "ethers";
import { setupTestingContracts } from "./factories";
import { FakeMessageIDCounter, fieldFactory } from "./utils";
import { MemoryRLNRegistry } from "../src/registry";
describe("RLN", function () {
const rlnIdentifierA = BigInt(1);
const treeDepthWithoutDefaultParams = 5566;
describe("constructor params", function () {
const registry = new MemoryRLNRegistry(rlnIdentifierA, 20)
test("should fail when neither proving params nor verification key is given", async function () {
expect(() => {
new RLN({
rlnIdentifier: rlnIdentifierA,
treeDepth: treeDepthWithoutDefaultParams,
registry,
});
}).toThrow(
'Either both `wasmFilePath` and `finalZkeyPath` must be supplied to generate proofs, ' +
'or `verificationKey` must be provided to verify proofs.'
);
});
test("should fail to prove if no proving params is given as constructor arguments", async function () {
const rln = new RLN({
rlnIdentifier: rlnIdentifierA,
treeDepth: treeDepthWithoutDefaultParams,
registry,
verificationKey: rlnParams.verificationKey,
})
await expect(async () => {
await rln.createProof(BigInt(0), "abc")
}).rejects.toThrow("Prover is not initialized");
});
test("should fail when verifying if no verification key is given as constructor arguments", async function () {
const rln = new RLN({
rlnIdentifier: rlnIdentifierA,
treeDepth: treeDepthWithoutDefaultParams,
registry,
wasmFilePath: rlnParams.wasmFilePath,
finalZkeyPath: rlnParams.finalZkeyPath,
})
const randomEpoch = fieldFactory()
const randomMessage = "abc"
const mockProof = {} as RLNFullProof
await expect(async () => {
await rln.verifyProof(randomEpoch, randomMessage, mockProof)
}).rejects.toThrow("Verifier is not initialized");
});
});
describe("createWithContractRegistry params", function () {
const fakeProvider = {} as ethers.Provider
const fakeContractAddress = "0x0000000000000000000000000000000000005678"
test("should fail when neither proving params nor verification key is given", async function () {
expect(() => {
RLN.createWithContractRegistry({
rlnIdentifier: rlnIdentifierA,
treeDepth: treeDepthWithoutDefaultParams,
provider: fakeProvider,
contractAddress: fakeContractAddress,
});
}).toThrow(
'Either both `wasmFilePath` and `finalZkeyPath` must be supplied to generate proofs, ' +
'or `verificationKey` must be provided to verify proofs.'
);
});
test("should fail to prove if no proving params is given as constructor arguments", async function () {
const rln = RLN.createWithContractRegistry({
rlnIdentifier: rlnIdentifierA,
treeDepth: treeDepthWithoutDefaultParams,
provider: fakeProvider,
contractAddress: fakeContractAddress,
verificationKey: rlnParams.verificationKey,
})
await expect(async () => {
await rln.createProof(BigInt(0), "abc")
}).rejects.toThrow("Prover is not initialized");
});
test("should fail when verifying if no verification key is given as constructor arguments", async function () {
const rln = RLN.createWithContractRegistry({
rlnIdentifier: rlnIdentifierA,
treeDepth: treeDepthWithoutDefaultParams,
provider: fakeProvider,
contractAddress: fakeContractAddress,
wasmFilePath: rlnParams.wasmFilePath,
finalZkeyPath: rlnParams.finalZkeyPath,
})
const randomEpoch = fieldFactory()
const randomMessage = "abc"
const mockProof = {} as RLNFullProof
await expect(async () => {
await rln.verifyProof(randomEpoch, randomMessage, mockProof)
}).rejects.toThrow("Verifier is not initialized");
});
});
describe("functionalities", function () {
const rlnIdentifierA = BigInt(1);
const rlnIdentifierB = BigInt(2);
const epoch0 = BigInt(0);
const epoch1 = BigInt(1);
const message0 = "abc";
const message1 = "abcd";
let deployed;
let waitUntilFreezePeriodPassed: () => Promise<void>
let killNode: () => Promise<void>
let rlnA0: RLN;
const messageLimitA0 = BigInt(1);
const messageIDCounterA0 = new FakeMessageIDCounter(messageLimitA0)
let proofA00: RLNFullProof;
let rlnA1: RLN;
let contractAddress: string
const messageLimitA1 = BigInt(1);
// Use a fake messageIDCounter which allows us to adjust reset message id for testing
const messageIDCounterA1 = new FakeMessageIDCounter(messageLimitA1)
const cacheA1 = new MemoryCache()
let proofA10: RLNFullProof;
let proofA11: RLNFullProof;
const treeDepth = 20
const tokenAmount = BigInt("1000000000000000000")
// 10 token
const minimalDeposit = BigInt(10)
// 10%
const feePercentage = BigInt(10)
const feeReceiver = "0x0000000000000000000000000000000000005566"
const freezePeriod = BigInt(1)
function rlnInstanceFactory(args: {
rlnIdentifier: bigint,
signer?: ethers.Signer,
}) {
return RLN.createWithContractRegistry({
wasmFilePath: rlnParams.wasmFilePath,
finalZkeyPath: rlnParams.finalZkeyPath,
verificationKey: rlnParams.verificationKey,
rlnIdentifier: args.rlnIdentifier,
provider: deployed.provider,
signer: args.signer,
contractAddress,
withdrawWasmFilePath: withdrawParams.wasmFilePath,
withdrawFinalZkeyPath: withdrawParams.finalZkeyPath,
})
}
beforeAll(async () => {
deployed = await setupTestingContracts({
initialTokenAmount: tokenAmount,
minimalDeposit,
treeDepth,
feePercentage,
feeReceiver,
freezePeriod,
});
waitUntilFreezePeriodPassed = deployed.waitUntilFreezePeriodPassed
killNode = deployed.killNode
contractAddress = await deployed.rlnContract.getAddress()
rlnA0 = rlnInstanceFactory({
rlnIdentifier: rlnIdentifierA,
signer: deployed.signer0,
});
rlnA1 = rlnInstanceFactory({
rlnIdentifier: rlnIdentifierA,
signer: deployed.signer1,
});
rlnA1.setCache(cacheA1);
});
afterAll(async () => {
console.log("killing node")
await killNode()
console.log("node killed")
});
test("should have correct members after initialization", async function () {
expect(await rlnA0.isRegistered()).toBe(false);
expect((await rlnA0.getAllRateCommitments()).length).toBe(0);
expect(await rlnA0.getMerkleRoot()).toBe(await rlnA1.getMerkleRoot());
});
test("should fail when creating proof if not registered", async function () {
await expect(async () => {
await rlnA0.createProof(BigInt(0), "abc")
}).rejects.toThrow("User has not registered before");
});
test("should register A0 successfully", async function () {
await rlnA0.register(messageLimitA0, messageIDCounterA0);
// A0 has not been updated in the registry
expect(await rlnA0.isRegistered()).toBe(true);
const allRateCommitments = await rlnA0.getAllRateCommitments();
expect(allRateCommitments.length).toBe(1);
expect(allRateCommitments[0]).toBe(await rlnA0.getRateCommitment());
});
test("should be able to set message id counter", async function () {
// Test: set a new message id counter with zero message limit
// I.e. no message can be sent
const zeroMessageLimit = BigInt(0)
const newMessageIDCounter = new FakeMessageIDCounter(zeroMessageLimit)
await rlnA0.setMessageIDCounter(newMessageIDCounter)
await expect(async () => {
await rlnA0.createProof(epoch0, message1)
}).rejects.toThrow(`Message ID counter exceeded message limit ${zeroMessageLimit}`);
// Change it back to make other tests work
await rlnA0.setMessageIDCounter(messageIDCounterA0)
})
test("should be able to create proof", async function () {
const messageIDBefore = await messageIDCounterA0.peekNextMessageID(epoch0);
proofA00 = await rlnA0.createProof(epoch0, message0);
const messageIDAfter = await messageIDCounterA0.peekNextMessageID(epoch0);
expect(messageIDAfter).toBe(messageIDBefore + BigInt(1));
expect(await rlnA0.verifyProof(epoch0, message0, proofA00)).toBe(true);
});
test("should fail to create proof if messageID exceeds limit", async function () {
const currentMessageID = await messageIDCounterA0.peekNextMessageID(epoch0);
// Sanity check: messageID should be equal to limit now
expect(currentMessageID).toBe(messageLimitA0);
await expect(async () => {
await rlnA0.createProof(epoch0, message0);
}).rejects.toThrow("Message ID counter exceeded message limit")
});
test("should fail to verify invalid proof", async function () {
const proofA00Invalid: RLNFullProof = {
...proofA00,
snarkProof: {
proof: {
...proofA00.snarkProof.proof,
pi_a: [BigInt(1), BigInt(2)],
},
publicSignals: proofA00.snarkProof.publicSignals,
}
}
expect(await rlnA0.verifyProof(epoch0, message0, proofA00Invalid)).toBeFalsy()
});
test("should be able to withdraw", async function () {
await rlnA0.withdraw();
await waitUntilFreezePeriodPassed()
await rlnA0.releaseWithdrawal();
expect(await rlnA0.isRegistered()).toBe(false);
expect((await rlnA0.getAllRateCommitments()).length).toBe(1);
});
test("should fail to create proof after withdraw", async function () {
await expect(async () => {
await rlnA0.createProof(epoch0, message0);
}).rejects.toThrow("User has not registered before");
});
test("should be able to get the latest state with A1", async function () {
expect(await rlnA1.isRegistered()).toBe(false);
const allRateCommitmentsA1 = await rlnA1.getAllRateCommitments();
expect(allRateCommitmentsA1.length).toBe(1);
expect(allRateCommitmentsA1[0]).toBe((await rlnA0.getAllRateCommitments())[0]);
expect(await rlnA1.getMerkleRoot()).toBe(await rlnA0.getMerkleRoot());
});
test("should be able to register A1", async function () {
await rlnA1.register(messageLimitA1, messageIDCounterA1);
expect(await rlnA1.isRegistered()).toBe(true);
const allRateCommitmentsA1 = await rlnA1.getAllRateCommitments();
expect(allRateCommitmentsA1.length).toBe(2);
expect(allRateCommitmentsA1[1]).toBe(await rlnA1.getRateCommitment());
});
test("should reveal its secret by itself if A1 creates more than `messageLimitA1` messages", async function () {
// messageLimitA1 is 1, so A1 can only create 1 proof per epoch
// Test: can save the first proof
proofA10 = await rlnA1.createProof(epoch0, message0);
// Test: status should be DUPLICATE when saving duplicate proof since it has been saved in createProof
const resA10Again = await rlnA1.saveProof(proofA10);
expect(resA10Again.status).toBe(Status.DUPLICATE);
// Reset messageIDCounterA1 at epoch0 to bypass the message id counter and
// let it create a proof when it already exceeds `messageLimitA1`.
await rlnA1.setMessageIDCounter(new FakeMessageIDCounter(BigInt(messageLimitA1)));
// Test: even messageIDCounter is reset, there is another guard `cache`
// to prevent creating more than `messageLimitA1` proofs
await expect(async () => {
await rlnA1.createProof(epoch0, message1);
}).rejects.toThrow("Proof will spam");
// Reset cache too, to allow rln createProof
cacheA1.cache[epoch0.toString()] = {}
// Reset messageIDCounterA1 again to bypass the message id counter since it increments
// the message id counter when `createProof` even if it fails.
await rlnA1.setMessageIDCounter(new FakeMessageIDCounter(BigInt(messageLimitA1)));
// Test: number of proofs per epoch exceeds `messageLimitA1`, breach/ slashed when `saveProof`
proofA11 = await rlnA1.createProof(epoch0, message1);
const resA10AgainAgain = await rlnA1.saveProof(proofA10);
expect(resA10AgainAgain.status).toBe(Status.BREACH);
if (resA10AgainAgain.secret === undefined) {
throw new Error("secret should not be undefined")
}
// Test: but A1 cannot slash itself
const secret = resA10AgainAgain.secret;
await expect(async () => {
await rlnA1.slash(secret)
}).rejects.toThrow('execution reverted: "RLN, slash: self-slashing is prohibited"');
// Test: epoch1 is a new epoch, so A1 can create 1 proof
await rlnA1.createProof(epoch1, message1);
});
test("should reveal its secret and get slashed by others", async function () {
// Test: A0 is up-to-date and receives more than `messageLimitA1` proofs,
// so A1's secret is breached by A0
const resA10 = await rlnA0.saveProof(proofA10);
expect(resA10.status).toBe(Status.VALID);
const resA12 = await rlnA0.saveProof(proofA11);
expect(resA12.status).toBe(Status.BREACH);
if (resA12.secret === undefined) {
throw new Error("secret should not be undefined")
}
// Test: A0 should be able to slash A1
await rlnA0.slash(resA12.secret)
expect(await rlnA1.isRegistered()).toBe(false);
});
test("should be incompatible for RLN if rlnIdentifier is different", async function () {
// Create another rlnInstance with different rlnIdentifier
const rlnB = rlnInstanceFactory({
rlnIdentifier: rlnIdentifierB,
});
// Test: verifyProof fails since proofA10.rlnIdentifier mismatches rlnB's rlnIdentifier
expect(await rlnB.verifyProof(epoch0, message0, proofA10)).toBe(false);
});
});
});

View File

@@ -1,55 +0,0 @@
import { IncrementalMerkleTree, MerkleProof } from "@zk-kit/incremental-merkle-tree"
import poseidon from "poseidon-lite";
import { zeroPad } from '@ethersproject/bytes'
import { BigNumber } from '@ethersproject/bignumber'
import { keccak256 } from '@ethersproject/keccak256'
import { Fq } from "../src/common"
import { MemoryMessageIDCounter } from "../src/message-id-counter";
export function fieldFactory(excludes?: bigint[], trials: number = 100): bigint {
if (excludes) {
for (let i = 0; i < trials; i++) {
const epoch = Fq.random()
if (!excludes.includes(epoch)) {
return epoch
}
}
throw new Error("Failed to generate a random epoch")
} else {
return Fq.random()
}
}
export class FakeMessageIDCounter extends MemoryMessageIDCounter {
async peekNextMessageID(epoch: bigint): Promise<bigint> {
const epochStr = epoch.toString()
if (this.epochToMessageID[epochStr] === undefined) {
return BigInt(0)
}
return this.epochToMessageID[epochStr]
}
}
function calculateZeroValue(id: bigint): bigint {
const hexStr = BigNumber.from(id).toTwos(256).toHexString()
const zeroPadded = zeroPad(hexStr, 32)
return BigInt(keccak256(zeroPadded)) >> BigInt(8)
}
export function verifyMerkleProof(rlnIdentifier: bigint, proof: MerkleProof, treeDepth: number) {
const zeroValue = calculateZeroValue(rlnIdentifier)
const tree = new IncrementalMerkleTree(poseidon, treeDepth, zeroValue, 2)
proof.siblings = proof.siblings.map((s) => [s])
return tree.verifyProof(proof)
}
export function generateMerkleProof(rlnIdentifier: bigint, leaves: bigint[], treeDepth: number, index: number) {
const zeroValue = calculateZeroValue(rlnIdentifier)
const tree = new IncrementalMerkleTree(poseidon, treeDepth, zeroValue, 2)
for (const leaf of leaves) {
tree.insert(leaf)
}
return tree.createProof(index)
}

View File

@@ -1,6 +0,0 @@
{
"extends": "./tsconfig.json",
"files": [
"src/index.ts",
]
}

View File

@@ -1,22 +0,0 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"rootDir": ".",
"outDir": "./dist",
"typeRoots": ["node_modules/@types", "src/types"],
"paths": {
"ffjavascript": [
"./src/types/ffjavascript"
],
"snarkjs": [
"./src/types/snarkjs"
],
}
},
"include": ["src"],
"exclude": [
"node_modules",
"dist"
]
}

View File

@@ -1,12 +0,0 @@
{
"extends": "./tsconfig.json",
"noUnusedLocals": false,
"noUnusedParameters": false,
"noImplicitAny": false,
"noImplicitReturns": false,
"noEmitOnError": false,
"include": [
"tests"
],
"exclude": []
}

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2022 Ethereum Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,124 +0,0 @@
<p align="center">
<h1 align="center">
Semaphore proof
</h1>
<p align="center">A library to generate and verify Semaphore proofs.</p>
</p>
<p align="center">
<a href="https://github.com/semaphore-protocol">
<img src="https://img.shields.io/badge/project-Semaphore-blue.svg?style=flat-square">
</a>
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/LICENSE">
<img alt="Github license" src="https://img.shields.io/github/license/semaphore-protocol/semaphore.svg?style=flat-square">
</a>
<a href="https://www.npmjs.com/package/@semaphore-protocol/proof">
<img alt="NPM version" src="https://img.shields.io/npm/v/@semaphore-protocol/proof?style=flat-square" />
</a>
<a href="https://npmjs.org/package/@semaphore-protocol/proof">
<img alt="Downloads" src="https://img.shields.io/npm/dm/@semaphore-protocol/proof.svg?style=flat-square" />
</a>
<a href="https://js.semaphore.appliedzkp.org/proof">
<img alt="Documentation typedoc" src="https://img.shields.io/badge/docs-typedoc-744C7C?style=flat-square">
</a>
<a href="https://eslint.org/">
<img alt="Linter eslint" src="https://img.shields.io/badge/linter-eslint-8080f2?style=flat-square&logo=eslint" />
</a>
<a href="https://prettier.io/">
<img alt="Code style prettier" src="https://img.shields.io/badge/code%20style-prettier-f8bc45?style=flat-square&logo=prettier" />
</a>
</p>
<div align="center">
<h4>
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/CONTRIBUTING.md">
👥 Contributing
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://github.com/semaphore-protocol/semaphore/blob/main/CODE_OF_CONDUCT.md">
🤝 Code of conduct
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://github.com/semaphore-protocol/semaphore/contribute">
🔎 Issues
</a>
<span>&nbsp;&nbsp;|&nbsp;&nbsp;</span>
<a href="https://semaphore.appliedzkp.org/discord">
🗣️ Chat &amp; Support
</a>
</h4>
</div>
| This library provides utility functions to generate and verify Semaphore proofs compatible with the Semaphore [circuits](https://github.com/semaphore-protocol/semaphore/tree/main/circuits). Generating valid zero-knowledge proofs requires files that can only be obtained in an attested [trusted-setup ceremony](https://storage.googleapis.com/trustedsetup-a86f4.appspot.com/semaphore/semaphore_top_index.html). For a complete list of ready-to-use files visit [trusted-setup-pse.org](http://www.trusted-setup-pse.org/). |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
## 🛠 Install
### npm or yarn
Install the `@semaphore-protocol/proof` package and its peer dependencies with npm:
```bash
npm i @semaphore-protocol/identity @semaphore-protocol/group @semaphore-protocol/proof
```
or yarn:
```bash
yarn add @semaphore-protocol/identity @semaphore-protocol/group @semaphore-protocol/proof
```
## 📜 Usage
\# **generateProof**(
identity: _Identity_,
group: _Group_ | _MerkleProof_,
externalNullifier: _BytesLike | Hexable | number | bigint_,
signal: _BytesLike | Hexable | number | bigint_,
snarkArtifacts?: _SnarkArtifacts_
): Promise\<_SemaphoreFullProof_>
```typescript
import { Identity } from "@semaphore-protocol/identity"
import { Group } from "@semaphore-protocol/group"
import { generateProof } from "@semaphore-protocol/proof"
import { utils } from "ethers"
const identity = new Identity()
const group = new Group()
const externalNullifier = utils.formatBytes32String("Topic")
const signal = utils.formatBytes32String("Hello world")
group.addMembers([...identityCommitments, identity.generateCommitment()])
const fullProof = await generateProof(identity, group, externalNullifier, signal, {
zkeyFilePath: "./semaphore.zkey",
wasmFilePath: "./semaphore.wasm"
})
// You can also use the default zkey/wasm files (it only works from browsers!).
// const fullProof = await generateProof(identity, group, externalNullifier, signal)
```
\# **verifyProof**(fullProof: _FullProof_, treeDepth: _number_): Promise\<_boolean_>
```typescript
import { verifyProof } from "@semaphore-protocol/proof"
await verifyProof(fullProof, 20)
```
\# **calculateNullifierHash**(
identityNullifier: _bigint | number | string_,
externalNullifier: \__BytesLike | Hexable | number | bigint_
): bigint
```typescript
import { Identity } from "@semaphore-protocol/identity"
import { calculateNullifierHash } from "@semaphore-protocol/proof"
const identity = new Identity()
const externalNullifier = utils.formatBytes32String("Topic")
const nullifierHash = calculateNullifierHash(identity.nullifier, externalNullifier)
```

View File

@@ -1,49 +0,0 @@
{
"name": "@cryptkeeperzk/semaphore-proof",
"private": true,
"version": "0.1.0-beta.1",
"description": "A forked Semaphore proof library to generate and verify Semaphore proofs.",
"license": "MIT",
"main": "./dist/src/index.js",
"types": "./dist/src/index.d.ts",
"files": [
"dist/",
"src/",
"LICENSE",
"README.md"
],
"repository": "https://github.com/semaphore-protocol/semaphore",
"homepage": "https://github.com/semaphore-protocol/semaphore/tree/main/packages/proof",
"bugs": {
"url": "https://github.com/semaphore-protocol/semaphore.git/issues"
},
"scripts": {
"build:watch": "rollup -c rollup.config.ts -w --configPlugin typescript",
"build": "tsc -p tsconfig.build.json",
"docs": "typedoc src/index.ts --out ../../docs/proof"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^24.1.0",
"@rollup/plugin-json": "^5.0.1",
"@rollup/plugin-node-resolve": "^15.0.2",
"@types/jest": "^29.5.2",
"ffjavascript": "^0.2.54",
"fs": "0.0.1-security",
"poseidon-lite": "^0.2.0",
"rollup-plugin-cleanup": "^3.2.1",
"rollup-plugin-typescript2": "^0.31.2",
"typedoc": "^0.22.11"
},
"peerDependencies": {
"@semaphore-protocol/group": "3.10.1",
"@semaphore-protocol/identity": "3.10.1"
},
"dependencies": {
"@ethersproject/bignumber": "^5.5.0",
"@ethersproject/bytes": "^5.7.0",
"@ethersproject/keccak256": "^5.7.0",
"@ethersproject/strings": "^5.5.0",
"@zk-kit/incremental-merkle-tree": "0.4.3",
"snarkjs": "git+https://github.com/CryptKeeperZK/snarkjs.git"
}
}

View File

@@ -1,509 +0,0 @@
/** Declaration file generated by dts-gen */
declare module "ffjavascript" {
export class BigBuffer {
constructor(...args: any[])
set(...args: any[]): void
slice(...args: any[]): void
}
export class ChaCha {
constructor(...args: any[])
nextBool(...args: any[]): void
nextU32(...args: any[]): void
nextU64(...args: any[]): void
update(...args: any[]): void
}
export class EC {
constructor(...args: any[])
add(...args: any[]): void
affine(...args: any[]): void
double(...args: any[]): void
eq(...args: any[]): void
fromRng(...args: any[]): void
fromRprBE(...args: any[]): void
fromRprBEM(...args: any[]): void
fromRprCompressed(...args: any[]): void
fromRprLE(...args: any[]): void
fromRprLEJM(...args: any[]): void
fromRprLEM(...args: any[]): void
fromRprUncompressed(...args: any[]): void
isZero(...args: any[]): void
mulScalar(...args: any[]): void
multiAffine(...args: any[]): void
neg(...args: any[]): void
sub(...args: any[]): void
timesScalar(...args: any[]): void
toRprBE(...args: any[]): void
toRprBEM(...args: any[]): void
toRprCompressed(...args: any[]): void
toRprLE(...args: any[]): void
toRprLEJM(...args: any[]): void
toRprLEM(...args: any[]): void
toRprUncompressed(...args: any[]): void
toString(...args: any[]): void
}
export class F1Field {
constructor(...args: any[])
e(...args: any[]): any
add(...args: any[]): any
sub(...args: any[]): any
neg(...args: any[]): any
mul(...args: any[]): any
mulScalar(...args: any[]): any
square(...args: any[]): any
eq(...args: any[]): any
neq(...args: any[]): any
lt(...args: any[]): any
gt(...args: any[]): any
leq(...args: any[]): any
geq(...args: any[]): any
div(...args: any[]): any
idiv(...args: any[]): any
inv(...args: any[]): any
mod(...args: any[]): any
pow(...args: any[]): any
exp(...args: any[]): any
band(...args: any[]): any
bor(...args: any[]): any
bxor(...args: any[]): any
bnot(...args: any[]): any
shl(...args: any[]): any
shr(...args: any[]): any
land(...args: any[]): any
lor(...args: any[]): any
lnot(...args: any[]): any
sqrt_old(...args: any[]): any
normalize(...args: any[]): any
random(...args: any[]): any
toString(...args: any[]): any
isZero(...args: any[]): any
fromRng(...args: any[]): any
fromRprBE(...args: any[]): void
fromRprBEM(...args: any[]): void
fromRprLE(...args: any[]): void
fromRprLEM(...args: any[]): void
toRprBE(...args: any[]): void
toRprBEM(...args: any[]): void
toRprLE(...args: any[]): void
toRprLEM(...args: any[]): void
}
export class F2Field {
constructor(...args: any[])
add(...args: any[]): void
conjugate(...args: any[]): void
copy(...args: any[]): void
div(...args: any[]): void
double(...args: any[]): void
eq(...args: any[]): void
exp(...args: any[]): void
fromRng(...args: any[]): void
fromRprBE(...args: any[]): void
fromRprBEM(...args: any[]): void
fromRprLE(...args: any[]): void
fromRprLEM(...args: any[]): void
geq(...args: any[]): void
gt(...args: any[]): void
inv(...args: any[]): void
isZero(...args: any[]): void
leq(...args: any[]): void
lt(...args: any[]): void
mul(...args: any[]): void
mulScalar(...args: any[]): void
neg(...args: any[]): void
neq(...args: any[]): void
pow(...args: any[]): void
random(...args: any[]): void
square(...args: any[]): void
sub(...args: any[]): void
toRprBE(...args: any[]): void
toRprBEM(...args: any[]): void
toRprLE(...args: any[]): void
toRprLEM(...args: any[]): void
toString(...args: any[]): void
}
export class F3Field {
constructor(...args: any[])
add(...args: any[]): void
affine(...args: any[]): void
copy(...args: any[]): void
div(...args: any[]): void
double(...args: any[]): void
eq(...args: any[]): void
exp(...args: any[]): void
fromRng(...args: any[]): void
fromRprBE(...args: any[]): void
fromRprBEM(...args: any[]): void
fromRprLE(...args: any[]): void
fromRprLEM(...args: any[]): void
geq(...args: any[]): void
gt(...args: any[]): void
inv(...args: any[]): void
isZero(...args: any[]): void
leq(...args: any[]): void
lt(...args: any[]): void
mul(...args: any[]): void
mulScalar(...args: any[]): void
neg(...args: any[]): void
neq(...args: any[]): void
pow(...args: any[]): void
random(...args: any[]): void
square(...args: any[]): void
sub(...args: any[]): void
toRprBE(...args: any[]): void
toRprBEM(...args: any[]): void
toRprLE(...args: any[]): void
toRprLEM(...args: any[]): void
toString(...args: any[]): void
}
export class PolField {
constructor(...args: any[])
add(...args: any[]): void
computeVanishingPolinomial(...args: any[]): void
div(...args: any[]): void
double(...args: any[]): void
eq(...args: any[]): void
eval(...args: any[]): void
eval2(...args: any[]): void
evaluateLagrangePolynomials(...args: any[]): void
extend(...args: any[]): void
fft(...args: any[]): void
fft2(...args: any[]): void
ifft(...args: any[]): void
ifft2(...args: any[]): void
lagrange(...args: any[]): void
log2(...args: any[]): void
mul(...args: any[]): void
mulFFT(...args: any[]): void
mulNormal(...args: any[]): void
mulScalar(...args: any[]): void
normalize(...args: any[]): void
oneRoot(...args: any[]): void
reduce(...args: any[]): void
ruffini(...args: any[]): void
scaleX(...args: any[]): void
square(...args: any[]): void
sub(...args: any[]): void
toString(...args: any[]): void
}
export class ZqField {
constructor(...args: any[])
e(...args: any[]): any
add(...args: any[]): any
sub(...args: any[]): any
neg(...args: any[]): any
mul(...args: any[]): any
mulScalar(...args: any[]): any
square(...args: any[]): any
eq(...args: any[]): any
neq(...args: any[]): any
lt(...args: any[]): any
gt(...args: any[]): any
leq(...args: any[]): any
geq(...args: any[]): any
div(...args: any[]): any
idiv(...args: any[]): any
inv(...args: any[]): any
mod(...args: any[]): any
pow(...args: any[]): any
exp(...args: any[]): any
band(...args: any[]): any
bor(...args: any[]): any
bxor(...args: any[]): any
bnot(...args: any[]): any
shl(...args: any[]): any
shr(...args: any[]): any
land(...args: any[]): any
lor(...args: any[]): any
lnot(...args: any[]): any
sqrt_old(...args: any[]): any
normalize(...args: any[]): any
random(...args: any[]): any
toString(...args: any[]): any
isZero(...args: any[]): any
fromRng(...args: any[]): any
fromRprBE(...args: any[]): void
fromRprBEM(...args: any[]): void
fromRprLE(...args: any[]): void
fromRprLEM(...args: any[]): void
toRprBE(...args: any[]): void
toRprBEM(...args: any[]): void
toRprLE(...args: any[]): void
toRprLEM(...args: any[]): void
}
export function buildBls12381(singleThread: any, plugins: any): any
export function buildBn128(singleThread: any, plugins: any): any
export function getCurveFromName(name: any, singleThread?: any, plugins?: any): any
export function getCurveFromQ(q: any, singleThread: any, plugins: any): any
export function getCurveFromR(r: any, singleThread: any, plugins: any): any
export namespace Scalar {
const one: any
const zero: any
function abs(a: any): any
function add(a: any, b: any): any
function band(a: any, b: any): any
function bitLength(a: any): any
function bits(n: any): any
function bor(a: any, b: any): any
function bxor(a: any, b: any): any
function div(a: any, b: any): any
function e(s: any, radix: any): any
function eq(a: any, b: any): any
function exp(a: any, b: any): any
function fromArray(a: any, radix: any): any
function fromRprBE(buff: any, o: any, n8: any): any
function fromRprLE(buff: any, o: any, n8: any): any
function fromString(s: any, radix: any): any
function geq(a: any, b: any): any
function gt(a: any, b: any): any
function isNegative(a: any): any
function isOdd(a: any): any
function isZero(a: any): any
function land(a: any, b: any): any
function leq(a: any, b: any): any
function lnot(a: any): any
function lor(a: any, b: any): any
function lt(a: any, b: any): any
function mod(a: any, b: any): any
function mul(a: any, b: any): any
function naf(n: any): any
function neg(a: any): any
function neq(a: any, b: any): any
function pow(a: any, b: any): any
function shiftLeft(a: any, n: any): any
function shiftRight(a: any, n: any): any
function shl(a: any, n: any): any
function shr(a: any, n: any): any
function square(a: any): any
function sub(a: any, b: any): any
function toArray(s: any, radix: any): any
function toLEBuff(a: any): any
function toNumber(s: any): any
function toRprBE(buff: any, o: any, e: any, n8: any): void
function toRprLE(buff: any, o: any, e: any, n8: any): void
function toString(a: any, radix: any): any
}
export namespace utils {
function array2buffer(arr: any, sG: any): any
function beBuff2int(buff: any): any
function beInt2Buff(n: any, len: any): any
function bitReverse(idx: any, bits: any): any
function buffReverseBits(buff: any, eSize: any): void
function buffer2array(buff: any, sG: any): any
function leBuff2int(buff: any): any
function leInt2Buff(n: any, len: any): any
function log2(V: any): any
function stringifyBigInts(o: any): any
function stringifyFElements(F: any, o: any): any
function unstringifyBigInts(o: any): any
function unstringifyFElements(F: any, o: any): any
}
}

View File

@@ -1,57 +0,0 @@
/** Declaration file generated by dts-gen */
declare module "snarkjs" {
export = snarkjs
declare const snarkjs: {
groth16: {
exportSolidityCallData: any
fullProve: any
prove: any
verify: any
}
plonk: {
exportSolidityCallData: any
fullProve: any
prove: any
setup: any
verify: any
}
powersOfTau: {
beacon: any
challengeContribute: any
contribute: any
convert: any
exportChallenge: any
exportJson: any
importResponse: any
newAccumulator: any
preparePhase2: any
truncate: any
verify: any
}
r1cs: {
exportJson: any
info: any
print: any
}
wtns: {
calculate: any
debug: any
exportJson: any
}
zKey: {
beacon: any
bellmanContribute: any
contribute: any
exportBellman: any
exportJson: any
exportSolidityVerifier: any
exportVerificationKey: any
importBellman: any
newZKey: any
verifyFromInit: any
verifyFromR1cs: any
}
}
}

View File

@@ -1,16 +0,0 @@
import { BytesLike, Hexable } from "@ethersproject/bytes"
import { poseidon2 } from "poseidon-lite/poseidon2"
import hash from "./hash"
/**
* Given the identity nullifier and the external nullifier, it calculates nullifier hash.
* @param identityNullifier The identity nullifier.
* @param externalNullifier The external nullifier.
* @returns The nullifier hash.
*/
export default function calculateNullifierHash(
identityNullifier: number | bigint | string,
externalNullifier: BytesLike | Hexable | number | bigint
): bigint {
return poseidon2([hash(externalNullifier), identityNullifier])
}

View File

@@ -1,68 +0,0 @@
import { BigNumber } from "@ethersproject/bignumber"
import { BytesLike, Hexable } from "@ethersproject/bytes"
import { Group } from "@semaphore-protocol/group"
import type { Identity } from "@semaphore-protocol/identity"
import { MerkleProof } from "@zk-kit/incremental-merkle-tree"
import { groth16 } from "snarkjs"
import hash from "./hash"
import packProof from "./packProof"
import { FullProof, SnarkArtifacts } from "./types"
/**
* Generates a Semaphore proof.
* @param identity The Semaphore identity.
* @param groupOrMerkleProof The Semaphore group or its Merkle proof.
* @param externalNullifier The external nullifier.
* @param signal The Semaphore signal.
* @param snarkArtifacts The SNARK artifacts.
* @returns The Semaphore proof ready to be verified.
*/
export default async function generateProof(
{ trapdoor, nullifier, commitment }: Identity,
groupOrMerkleProof: Group | MerkleProof,
externalNullifier: BytesLike | Hexable | number | bigint,
signal: BytesLike | Hexable | number | bigint,
snarkArtifacts?: SnarkArtifacts
): Promise<FullProof> {
let merkleProof: MerkleProof
if ("depth" in groupOrMerkleProof) {
const index = groupOrMerkleProof.indexOf(commitment)
if (index === -1) {
throw new Error("The identity is not part of the group")
}
merkleProof = groupOrMerkleProof.generateMerkleProof(index)
} else {
merkleProof = groupOrMerkleProof
}
if (!snarkArtifacts) {
snarkArtifacts = {
wasmFilePath: `https://www.trusted-setup-pse.org/semaphore/${merkleProof.siblings.length}/semaphore.wasm`,
zkeyFilePath: `https://www.trusted-setup-pse.org/semaphore/${merkleProof.siblings.length}/semaphore.zkey`
}
}
const { proof, publicSignals } = await groth16.fullProve(
{
identityTrapdoor: trapdoor,
identityNullifier: nullifier,
treePathIndices: merkleProof.pathIndices,
treeSiblings: merkleProof.siblings,
externalNullifier: hash(externalNullifier),
signalHash: hash(signal)
},
snarkArtifacts.wasmFilePath,
snarkArtifacts.zkeyFilePath
)
return {
merkleTreeRoot: publicSignals[0],
nullifierHash: publicSignals[1],
signal: BigNumber.from(signal).toString(),
externalNullifier: BigNumber.from(externalNullifier).toString(),
proof: packProof(proof)
}
}

View File

@@ -1,15 +0,0 @@
import { BigNumber } from "@ethersproject/bignumber"
import { BytesLike, Hexable, zeroPad } from "@ethersproject/bytes"
import { keccak256 } from "@ethersproject/keccak256"
/**
* Creates a keccak256 hash of a message compatible with the SNARK scalar modulus.
* @param message The message to be hashed.
* @returns The message digest.
*/
export default function hash(message: BytesLike | Hexable | number | bigint): bigint {
message = BigNumber.from(message).toTwos(256).toHexString()
message = zeroPad(message, 32)
return BigInt(keccak256(message)) >> BigInt(8)
}

View File

@@ -1,167 +0,0 @@
import { formatBytes32String } from "@ethersproject/strings"
import { Group } from "@semaphore-protocol/group"
import { Identity } from "@semaphore-protocol/identity"
import { getCurveFromName } from "ffjavascript"
import calculateNullifierHash from "./calculateNullifierHash"
import generateProof from "./generateProof"
import hash from "./hash"
import packProof from "./packProof"
import { FullProof } from "./types"
import unpackProof from "./unpackProof"
import verifyProof from "./verifyProof"
describe("Proof", () => {
const treeDepth = Number(process.env.TREE_DEPTH) || 20
const externalNullifier = formatBytes32String("Topic")
const signal = formatBytes32String("Hello world")
const wasmFilePath = `./snark-artifacts/${treeDepth}/semaphore.wasm`
const zkeyFilePath = `./snark-artifacts/${treeDepth}/semaphore.zkey`
const identity = new Identity()
let fullProof: FullProof
let curve: any
beforeAll(async () => {
curve = await getCurveFromName("bn128")
})
afterAll(async () => {
await curve.terminate()
})
describe("# generateProof", () => {
it("Should not generate Semaphore proofs if the identity is not part of the group", async () => {
const group = new Group(treeDepth)
group.addMembers([BigInt(1), BigInt(2)])
const fun = () =>
generateProof(identity, group, externalNullifier, signal, {
wasmFilePath,
zkeyFilePath
})
await expect(fun).rejects.toThrow("The identity is not part of the group")
})
it("Should not generate a Semaphore proof with default snark artifacts with Node.js", async () => {
const group = new Group(treeDepth)
group.addMembers([BigInt(1), BigInt(2), identity.commitment])
const fun = () => generateProof(identity, group, externalNullifier, signal)
await expect(fun).rejects.toThrow("ENOENT: no such file or directory")
})
it("Should generate a Semaphore proof passing a group as parameter", async () => {
const group = new Group(treeDepth)
group.addMembers([BigInt(1), BigInt(2), identity.commitment])
fullProof = await generateProof(identity, group, externalNullifier, signal, {
wasmFilePath,
zkeyFilePath
})
expect(typeof fullProof).toBe("object")
expect(fullProof.merkleTreeRoot).toBe(group.root.toString())
}, 20000)
it("Should generate a Semaphore proof passing a Merkle proof as parameter", async () => {
const group = new Group(treeDepth)
group.addMembers([BigInt(1), BigInt(2), identity.commitment])
fullProof = await generateProof(identity, group.generateMerkleProof(2), externalNullifier, signal, {
wasmFilePath,
zkeyFilePath
})
expect(typeof fullProof).toBe("object")
expect(fullProof.merkleTreeRoot).toBe(group.root.toString())
}, 20000)
})
describe("# verifyProof", () => {
it("Should not verify a proof if the tree depth is wrong", () => {
const fun = () => verifyProof(fullProof, 3)
expect(fun).toThrow("The tree depth must be a number between 16 and 32")
})
it("Should verify a Semaphore proof", async () => {
const response = await verifyProof(fullProof, treeDepth)
expect(response).toBe(true)
})
})
describe("# hash", () => {
it("Should hash the signal value correctly", async () => {
const signalHash = hash(signal)
expect(signalHash.toString()).toBe(
"8665846418922331996225934941481656421248110469944536651334918563951783029"
)
})
it("Should hash the external nullifier value correctly", async () => {
const externalNullifierHash = hash(externalNullifier)
expect(externalNullifierHash.toString()).toBe(
"244178201824278269437519042830883072613014992408751798420801126401127326826"
)
})
it("Should hash a number", async () => {
expect(hash(2).toString()).toBe(
"113682330006535319932160121224458771213356533826860247409332700812532759386"
)
})
it("Should hash a big number", async () => {
expect(hash(BigInt(2)).toString()).toBe(
"113682330006535319932160121224458771213356533826860247409332700812532759386"
)
})
it("Should hash an hex number", async () => {
expect(hash("0x2").toString()).toBe(
"113682330006535319932160121224458771213356533826860247409332700812532759386"
)
})
it("Should hash an string number", async () => {
expect(hash("2").toString()).toBe(
"113682330006535319932160121224458771213356533826860247409332700812532759386"
)
})
it("Should hash an array", async () => {
expect(hash([2]).toString()).toBe(
"113682330006535319932160121224458771213356533826860247409332700812532759386"
)
})
})
describe("# calculateNullifierHash", () => {
it("Should calculate the nullifier hash correctly", async () => {
const nullifierHash = calculateNullifierHash(identity.nullifier, externalNullifier)
expect(fullProof.nullifierHash).toBe(nullifierHash.toString())
})
})
describe("# packProof/unpackProof", () => {
it("Should return a packed proof", async () => {
const originalProof = unpackProof(fullProof.proof)
const proof = packProof(originalProof)
expect(proof).toStrictEqual(fullProof.proof)
})
})
})

View File

@@ -1,6 +0,0 @@
import generateProof from "./generateProof"
import verifyProof from "./verifyProof"
import calculateNullifierHash from "./calculateNullifierHash"
export { generateProof, verifyProof, calculateNullifierHash }
export * from "./types"

View File

@@ -1,19 +0,0 @@
import { SnarkJSProof, Proof } from "./types"
/**
* Packs a proof into a format compatible with Semaphore.
* @param originalProof The proof generated with SnarkJS.
* @returns The proof compatible with Semaphore.
*/
export default function packProof(originalProof: SnarkJSProof): Proof {
return [
originalProof.pi_a[0],
originalProof.pi_a[1],
originalProof.pi_b[0][1],
originalProof.pi_b[0][0],
originalProof.pi_b[1][1],
originalProof.pi_b[1][0],
originalProof.pi_c[0],
originalProof.pi_c[1]
]
}

View File

@@ -1,33 +0,0 @@
export type BigNumberish = string | bigint
export type SnarkArtifacts = {
wasmFilePath: string
zkeyFilePath: string
}
export type SnarkJSProof = {
pi_a: BigNumberish[]
pi_b: BigNumberish[][]
pi_c: BigNumberish[]
protocol: string
curve: string
}
export type FullProof = {
merkleTreeRoot: BigNumberish
signal: BigNumberish
nullifierHash: BigNumberish
externalNullifier: BigNumberish
proof: Proof
}
export type Proof = [
BigNumberish,
BigNumberish,
BigNumberish,
BigNumberish,
BigNumberish,
BigNumberish,
BigNumberish,
BigNumberish
]

View File

@@ -1,19 +0,0 @@
import { SnarkJSProof, Proof } from "./types"
/**
* Unpacks a proof into its original form.
* @param proof The proof compatible with Semaphore.
* @returns The proof compatible with SnarkJS.
*/
export default function unpackProof(proof: Proof): SnarkJSProof {
return {
pi_a: [proof[0], proof[1]],
pi_b: [
[proof[3], proof[2]],
[proof[5], proof[4]]
],
pi_c: [proof[6], proof[7]],
protocol: "groth16",
curve: "bn128"
}
}

View File

@@ -1,712 +0,0 @@
{
"protocol": "groth16",
"curve": "bn128",
"nPublic": 4,
"vk_alpha_1": [
"20491192805390485299153009773594534940189261866228447918068658471970481763042",
"9383485363053290200918347156157836566562967994039712273449902621266178545958",
"1"
],
"vk_beta_2": [
[
"6375614351688725206403948262868962793625744043794305715222011528459656738731",
"4252822878758300859123897981450591353533073413197771768651442665752259397132"
],
[
"10505242626370262277552901082094356697409835680220590971873171140371331206856",
"21847035105528745403288232691147584728191162732299865338377159692350059136679"
],
["1", "0"]
],
"vk_gamma_2": [
[
"10857046999023057135944570762232829481370756359578518086990519993285655852781",
"11559732032986387107991004021392285783925812861821192530917403151452391805634"
],
[
"8495653923123431417604973247489272438418190587263600148770280649306958101930",
"4082367875863433681332203403145435568316851327593401208105741076214120093531"
],
["1", "0"]
],
"vk_delta_2": [
[
[
"16243966861079634958125511652590761846958471358623040426599000904006426210032",
"13406811599156507528361773763681356312643537981039994686313383243831956396116"
],
[
"15688083679237922164673518758181461582601853873216319711156397437601833996222",
"11781596534582143578120404722739278517564025497573071755253972265891888117374"
],
["1", "0"]
],
[
[
"13589689305661231568162336263197960570915890299814486885851912452076929115480",
"15629200772768268814959330350023920183087521275477047626405113853190187031523"
],
[
"16004221700357242255845535848024178544616388017965468694776181247983831995562",
"11464919285924930973853174493551975632739604254498590354200272115844983493029"
],
["1", "0"]
],
[
[
"19717684456458906358368865507225121991585492363133107109865920739019288468011",
"9218320951536642499143228327011901814587826948504871816273184688188019956292"
],
[
"18221695645112467945186983098720611586049108689347006136423489099202471884089",
"16717590750910963405756115910371408378114896008824240863060392362901176601412"
],
["1", "0"]
],
[
[
"15953239752392927777442331623182226063776310198012173504208557434319753428770",
"3995128789564535587814512245259203300137618476815456454931286633947953135662"
],
[
"2523786679709693946058523307330825034772478122295850507521258983130425334580",
"20957319343912866335583737646657534123362052690050674068142580221965936605075"
],
["1", "0"]
],
[
[
"1382518990777992893805140303684642328066746531257780279226677247567004248173",
"18976133691706015337908381757202123182841901611067930614519324084182946094218"
],
[
"21806956747910197517744499423107239699428979652113081469385876768212706694581",
"6627710380771660558660627878547223719795356903257079198333641681330388499309"
],
["1", "0"]
],
[
[
"9032545080831535702239063467087720597970266046938395860207839433937324718536",
"3811592683283527904145155808200366192489850711742363953668998371801696238057"
],
[
"12429982191499850873612518410809641163252887523090441166572590809691267943605",
"16308433125974933290258540904373317426123214107276055539769464205982500660715"
],
["1", "0"]
],
[
[
"17626503110323089701269363177710295379967225765713250625279671011873619640598",
"9485639152672984144988597737758037391807993615552051606205480347442429414340"
],
[
"18953587685067712486092665232725058638563458484886448540567142557894080640927",
"12391874700409435648975069978280047983726144854114915177376036190441913967689"
],
["1", "0"]
],
[
[
"11408965575174993375815840422438995549652812400401163392501956884932167624437",
"9830856103389248449121962275587399130605902703453384856543071762984116567573"
],
[
"19969543376625663966419118899515353499678204573709836615846115182224340858492",
"11814906841949499037550820576929552248172160643991870665022770052632331265834"
],
["1", "0"]
],
[
[
"10090041889587324002759549286390619541526396451963494627957072069124011137562",
"15035335306919942325459417688135340085377315274625768597233474641923619728582"
],
[
"10507786999799841055999967456762679569286329319056926475375760604262707147294",
"21342049717074059749518233491526445388158772701642182532370641230478027030319"
],
["1", "0"]
],
[
[
"43456740675249348549891878341522275183186932745162972528932808393415299552",
"15718373132479769904443326381037437528372212185108294117696143473979328398658"
],
[
"4289247401578837038775845192875793775418122783738936298355403103074020081838",
"11236864934894600819960883124570686936554376109344998527334431594565774237827"
],
["1", "0"]
],
[
[
"4023016874169005249382064394379671330447496454371261692205411970999350949293",
"1723458149089715907994189658689343304709709060535625667210252753337752162173"
],
[
"17710652158212212080502343565075513548898593397103675832636832371532093744857",
"7651670126664625790835334090273463062538865895183205964669372719235003083565"
],
["1", "0"]
],
[
[
"13132169670125192016391258838554965176628317453468870968867717287446623320643",
"745924679191739894055143748466112994378439645681039136007774787076115375124"
],
[
"20909608709868730010029182074820840312550443752829480953667886902663547957991",
"2126777833939378028304266129616145667925849332481755567268747182629795296580"
],
["1", "0"]
],
[
[
"16835654219229187428071649241190746119082269636345872682107941472241044260584",
"4553625243522856553165922942982108474187282402890756796515747778282922584601"
],
[
"873742823867191038535544062852920538566418819521732785500614249239215175476",
"3272293478534046729728233267765357195255129499603632413158978822084188871854"
],
["1", "0"]
],
[
[
"7601443214415704135008588588192028557655441716696726549510699770097979655628",
"7252337675475138150830402909353772156046809729627064992143762325769537840623"
],
[
"18500126298578278987997086114400065402270866280547473913420536595663876273004",
"436607343827794507835462908831699962173244647704538949914686722631806931932"
],
["1", "0"]
],
[
[
"15028154694713144242204861571552635520290993855826554325002991692907421516918",
"10202326166286888893675634318107715186834588694714750762952081034135561546271"
],
[
"12766289885372833812620582632847872978085960777075662988932200910695848591357",
"18486039841380105976272577521609866666900576498507352937328726490052296469859"
],
["1", "0"]
],
[
[
"13682963731073238132274278610660469286329368216526659590944079211949686450402",
"14930624777162656776068112402283260602512252179767747308433194885322661150422"
],
[
"21315724107376627085778492378001676935454590984229146391746301404292016287653",
"18705481657148807016785305378773304476425591636333098330324049960258682574070"
],
["1", "0"]
],
[
[
"18994803742708336446369128568423705404354655742604689352630273180469431952708",
"12315240965742683516581565369496371929586281338862761742109651525191835544242"
],
[
"12707009780301102830224094192984906206920666691015255692741008594808694787917",
"18019403342409608922812569436317484250134945386869657285229378095251425778096"
],
["1", "0"]
]
],
"vk_alphabeta_12": [
[
[
"2029413683389138792403550203267699914886160938906632433982220835551125967885",
"21072700047562757817161031222997517981543347628379360635925549008442030252106"
],
[
"5940354580057074848093997050200682056184807770593307860589430076672439820312",
"12156638873931618554171829126792193045421052652279363021382169897324752428276"
],
[
"7898200236362823042373859371574133993780991612861777490112507062703164551277",
"7074218545237549455313236346927434013100842096812539264420499035217050630853"
]
],
[
[
"7077479683546002997211712695946002074877511277312570035766170199895071832130",
"10093483419865920389913245021038182291233451549023025229112148274109565435465"
],
[
"4595479056700221319381530156280926371456704509942304414423590385166031118820",
"19831328484489333784475432780421641293929726139240675179672856274388269393268"
],
[
"11934129596455521040620786944827826205713621633706285934057045369193958244500",
"8037395052364110730298837004334506829870972346962140206007064471173334027475"
]
]
],
"IC": [
[
[
"1964404930528116823793003656764176108669615750422202377358993070935069307720",
"2137714996673694828207437580381836490878070731768805974506391024595988817424",
"1"
],
[
"19568893707760843340848992184233194433177372925415116053368211122719346671126",
"11639469568629189918046964192305250472192697612201524135560178632824282818614",
"1"
],
[
"5317268879687484957437879782519918549127939892210247573193613900261494313825",
"528174394975085006443543773707702838726735933116136102590448357278717993744",
"1"
],
[
"14865918005176722116473730206622066845866539143554731094374354951675249722731",
"3197770568483953664363740385883457803041685902965668289308665954510373380344",
"1"
],
[
"6863358721495494421022713667808247652425178970453300712435830652679038918987",
"15025816433373311798308762709072064417001390853103872064614174594927359131281",
"1"
]
],
[
[
"17789438292552571310739605737896030466581277887660997531707911256058650850910",
"4112657509505371631825493224748310061184972897405589115208158208294581472016",
"1"
],
[
"3322052920119834475842380240689494113984887785733316517680891208549118967155",
"381029395779795399840019487059126246243641886087320875571067736504031557148",
"1"
],
[
"8777645223617381095463415690983421308854368583891690388850387317049320450400",
"11923582117369144413749726090967341613266070909169947059497952692052020331958",
"1"
],
[
"15493263571528401950994933073246603557158047091963487223668240334879173885581",
"6315532173951617115856055775098532808695228294437279844344466163873167020700",
"1"
],
[
"3481637421055377106140197938175958155334313900824697193932986771017625492245",
"20088416136090515091300914661950097694450984520235647990572441134215240947932",
"1"
]
],
[
[
"4691595252082380256698158158199364410440273386659834000993210659508747323919",
"9205801980459323513061837717352821162780471027241700646145937351740096374660",
"1"
],
[
"16150531426263112884093068164597994126623437929929609532055221646496813246000",
"20245743178241899668170758952526381872637304119026868520579207157118516761827",
"1"
],
[
"6063536446992770713985314309889717594240410784717230886576072989709763902848",
"18258781411255795973918859665416013869184055573057512603788635470145328981347",
"1"
],
[
"10109932964756104512054045207253535333686585863745296080906925765480296575285",
"4174640428253153601540284363759502713687021920150940723252842152556151210349",
"1"
],
[
"18049428534741480832385046397049175120355008065781483226058177421025493210952",
"591730261265040164434889324846001338201068482543108348317417391345612814922",
"1"
]
],
[
[
"9877211178693075145402462781884120278654771727348087433632224794894486095150",
"19972682062587174829535281061580296764150591339640180868104711395548066529340",
"1"
],
[
"6324578424031095537345184040149690238371517387586958921377481904541316423724",
"15513931720576048544404512239839508014664224085062729779520992909505663748296",
"1"
],
[
"11371337652479737143800707796204655130812036287859296372695832558127430723628",
"11757275188600040111649009832378343123994225623498773406233261322165903848967",
"1"
],
[
"13282496583564708104981015168203451877588903263486398132954741568835583461335",
"1746144324840370907926720490289700342734912534857331743685374514401176014195",
"1"
],
[
"7993952462467372951144011615584426050192046712674662254138390197508963352374",
"5156942148925224345709309361345680948125600198010285179548841917923439945819",
"1"
]
],
[
[
"19918517214839406678907482305035208173510172567546071380302965459737278553528",
"7151186077716310064777520690144511885696297127165278362082219441732663131220",
"1"
],
[
"690581125971423619528508316402701520070153774868732534279095503611995849608",
"21271996888576045810415843612869789314680408477068973024786458305950370465558",
"1"
],
[
"16461282535702132833442937829027913110152135149151199860671943445720775371319",
"2814052162479976678403678512565563275428791320557060777323643795017729081887",
"1"
],
[
"4319780315499060392574138782191013129592543766464046592208884866569377437627",
"13920930439395002698339449999482247728129484070642079851312682993555105218086",
"1"
],
[
"3554830803181375418665292545416227334138838284686406179598687755626325482686",
"5951609174746846070367113593675211691311013364421437923470787371738135276998",
"1"
]
],
[
[
"9494885690931955877467315318223108618392113101843890678090902614660136056680",
"11783514256715757384821021009301806722951917744219075907912683963173706887379",
"1"
],
[
"7562082660623781416745328104576133910743071878837764423695105915778139873834",
"17954307004260053757579194018551114133664721761483240877658498973152950708099",
"1"
],
[
"19338184851116432029108109461622579541195083625346674255186169347975445785058",
"38361206266360048012365562393026952048730052530888439195454086987795985927",
"1"
],
[
"21178537742782571863590222710872928190886000600239072595684369348717288330049",
"9786438258541172244884631831247223050494423968411444302812755467521949734320",
"1"
],
[
"11330504221972341797183339350494223413034293674225690456356444509688810101433",
"1490009915387901405464437253469086864085891770312035292355706249426866485365",
"1"
]
],
[
[
"21791720972262589799021600767292883644106575897307484548888696814333235336885",
"11092962469758788187888592619035811117815082357439060720677582048880121542623",
"1"
],
[
"9418924955930663972575130074928583215922927562059194231976193350658171304436",
"16113558481826020406162261319744796072664750077095575593106901121115073101408",
"1"
],
[
"20054934960262983176880675919444457578562219675808407582143519621873973120773",
"14877415271301547911435683263206245199959943680225555496786470669330176961657",
"1"
],
[
"4215199263810110748751715719957184804379752373072771007598572158043965517488",
"5225943468606602818132879686778547605180105897615251160509064537462109826521",
"1"
],
[
"6250242626034734280813142093008675407723196706248829741247204621913994561803",
"1472231555266678689888727724824566171966416459791722465278225775922487343641",
"1"
]
],
[
[
"3047486363455933831148688762823238723024952519326207356549121929667745957778",
"20241836359289449005887237560564358543646542598344362915541027571505243817211",
"1"
],
[
"5965631918800530319167124148627450454569264331058008407732200168631989208657",
"20463557477532480934514091877628554948892025887087712764683631108388998871350",
"1"
],
[
"16605042322692983282732511249912403956057999815658038166796858627082222971215",
"12219061498275616585164456833410962809536084885494309093787669879221959361956",
"1"
],
[
"1548998572074037722622224303222294716243074837074272552644853986075252666508",
"10393312002885367652301897874262367916506364670364584602554176742602334134772",
"1"
],
[
"16180907689593358346406392015123900260925622357393826746385511046141256905390",
"12267326749885120640972074479210537480053065569337817484467225562817467244765",
"1"
]
],
[
[
"19590996174696909242575628014943555633938195923520472786993379268302478708283",
"2673753072556442230312995111304911178679525806396134504594492458566941824354",
"1"
],
[
"13411253172375451489380472831999887223592471057462692619008484995624281735092",
"17181767455563581254432161119660408482332423481128600038352147258951772423229",
"1"
],
[
"19138864631164378176055647711995352935065134904103255748190268290992108588628",
"14282526277736365863821375748687709839392307698935143595732632710176778519757",
"1"
],
[
"20183773658676161990469276414858234178608794783112866811307579993999118293429",
"5223464433544489066271184294750886227362580875255044558831927430970236355539",
"1"
],
[
"12333466991139269670298178539679773509487545471126920233507132846828588847444",
"3787586478923104354547687861486563468235879611952775292288436085429794222238",
"1"
]
],
[
[
"18580370382199518848261939652153768394883698461842792002922164533882262019935",
"20516185953882700254387267244708111605796661864845495645678049276372075842359",
"1"
],
[
"20041291712709610738573661974551517833120775539593003477018637287434210072702",
"6326630253906616820412999166182553773360987412889775567442543181359104720511",
"1"
],
[
"13268971611130152315428629919012388924225656285593904211561391821918930327614",
"9247437189452353488017802041158840512956111558640958728149597697508914590433",
"1"
],
[
"6267384495557139339708615182113725421733376438932580472141549274050146739549",
"1832264154031452148715318442722960696977572389206897240030908464579133134237",
"1"
],
[
"16650684165487873559901140599157559153018449083939294496255590830891994564285",
"14140282729498011406186082176268025578697081678243955538935501306868500498994",
"1"
]
],
[
[
"4247947150009812467217672970806328247513830308400387953244764907353849211641",
"14500381439127180474801393438175928191199696177607750163263715436006533630877",
"1"
],
[
"21213779524495874664157797605662894019112036728653622806607467354233012380232",
"1429370857470083395421401524518861545167550347090873730934256398864585069083",
"1"
],
[
"12465277751642747637430517396067173985821959773399832969105187923427872239200",
"4377704428607835904642653580543541241155601291484645500691968624389522190030",
"1"
],
[
"11283027832501128633761619552392013253304972822086786857121687098087331014745",
"21463394238922953607096052056881931791797740737164052798044623278557203313720",
"1"
],
[
"19687293493101130967741578773742597470558958652351513582962108464055656171331",
"4445165696525061401582979300506082669540223774145877762689724631935313716632",
"1"
]
],
[
[
"3388767735894417381503201756905214431625081913405504580464345986403824999889",
"21014112837214011009096825602791072748195337199912773858499588477762724153070",
"1"
],
[
"10521317016331497094903116740581271122844131442882845700567581775404872949272",
"13201921794561774338466680421903602920184688290946713194187958007088351657367",
"1"
],
[
"16170260722059932609965743383032703380650557609693540121262881902248073364496",
"6004983491336500911294872035126141746032033211872472427212274143945425740617",
"1"
],
[
"10275615677574391293596971122111363003313434841806630200532546038183081960924",
"5955568702561336410725734958627459212680756023420452791680213386065159525989",
"1"
],
[
"19059081014385850734732058652137664919364805650872154944590269874395511868415",
"19202365837673729366500417038229950532560250566916189579621883380623278182155",
"1"
]
],
[
[
"7856986171681248404396064225772749784181602218562773063185003409958949630985",
"11707218736744382138692483591389641607570557654489363179025201039696228471230",
"1"
],
[
"2902255937308264958973169948617099471543255757887963647238093192858290079050",
"4092153880227661899721872164083575597602963673456107552146583620177664115673",
"1"
],
[
"18380478859138320895837407377103009470968863533040661874531861881638854174636",
"14502773952184441371657781525836310753176308880224816843041318743809785835984",
"1"
],
[
"2781117248053224106149213822307598926495461873135153638774638501111353469325",
"3500056595279027698683405880585654897391289317486204483344715855049598477604",
"1"
],
[
"8880120765926282932795149634761705738498809569874317407549203808931092257005",
"19080036326648068547894941015038877788526324720587349784852594495705578761000",
"1"
]
],
[
[
"18427701611614193839908361166447988195308352665132182219164437649866377475111",
"5299493942596042045861137432338955179078182570752746487573709678936617478454",
"1"
],
[
"4188155714164125069834512529839479682516489319499446390214266838952761728656",
"2720966082507704094346897998659841489771837229143573083003847010258396944787",
"1"
],
[
"13256461570028177373135283778770729308216900804505379897951455548375840027026",
"10722074030307391322177899534114921764931623271723882054692012663305322382747",
"1"
],
[
"9824147497244652955949696442395586567974424828238608972020527958186701134273",
"15755269950882650791869946186461432242513999576056199368058858215068920022191",
"1"
],
[
"21172488506061181949536573476893375313339715931330476837156243346077173297265",
"13892434487977776248366965108031841947713544939953824768291380177301871559945",
"1"
]
],
[
[
"1452272927738590248356371174422184656932731110936062990115610832462181634644",
"3608050114233210789542189629343107890943266759827387991788718454179833288695",
"1"
],
[
"14798240452388909327945424685903532333765637883272751382037716636327236955001",
"10773894897711848209682368488916121016695006898681985691467605219098835500201",
"1"
],
[
"17204267933132009093604099819536245144503489322639121825381131096467570698650",
"7704298975420304156332734115679983371345754866278811368869074990486717531131",
"1"
],
[
"8060465662017324080560848316478407038163145149983639907596180500095598669247",
"20475082166427284188002500222093571716651248980245637602667562336751029856573",
"1"
],
[
"7457566682692308112726332096733260585025339741083447785327706250123165087868",
"11904519443874922292602150685069370036383697877657723976244907400392778002614",
"1"
]
],
[
[
"12628427235010608529869146871556870477182704310235373946877240509680742038961",
"15093298104438768585559335868663959710321348106117735180051519837845319121254",
"1"
],
[
"6593907467779318957599440584793099005109789224774644007604434924706249001015",
"18549596630007199540674697114946251030815675677713256327810772799104711621483",
"1"
],
[
"6271101737045248834759003849256661059806617144229427987717476992610974162336",
"355748132218964841305454070022507122319085542484477110563322753565651576458",
"1"
],
[
"2116139772133141967317791473319540620104888687412078412336248003979594158546",
"4004400204967325849492155713520296687406035356901102254880522534085890616486",
"1"
],
[
"4206647028595764233995379982714022410660284578620723510907006350595207905228",
"19380634286337609988098517090003334645113675227742745065381519159322795845003",
"1"
]
],
[
[
"2592407181901686208061988776764501828311271519595797153264758207470081204331",
"11847594161160074962679125411562687287595382335410213641115001866587988494499",
"1"
],
[
"3346927026869562921166545684451290646273836362895645367665514203662899621366",
"15758185693543979820528128025093553492246135914029575732836221618882836493143",
"1"
],
[
"20528686657810499188368147206002308531447185877994439397529705707372170337045",
"18025396678079701612906003769476076600196287001844168390936182972248852818155",
"1"
],
[
"9799815250059685769827017947834627563597884023490186073806184882963949644596",
"4998495094322372762314630336611134866447406022687118703953312157819349892603",
"1"
],
[
"16176535527670849161173306151058200762642157343823553073439957507563856439772",
"21877331533292960470552563236986670222564955589137303622102707801351340670855",
"1"
]
]
]
}

View File

@@ -1,32 +0,0 @@
import { groth16 } from "snarkjs"
import hash from "./hash"
import { FullProof } from "./types"
import unpackProof from "./unpackProof"
import verificationKeys from "./verificationKeys.json"
/**
* Verifies a Semaphore proof.
* @param fullProof The SnarkJS Semaphore proof.
* @param treeDepth The Merkle tree depth.
* @returns True if the proof is valid, false otherwise.
*/
export default function verifyProof(
{ merkleTreeRoot, nullifierHash, externalNullifier, signal, proof }: FullProof,
treeDepth: number
): Promise<boolean> {
if (treeDepth < 16 || treeDepth > 32) {
throw new TypeError("The tree depth must be a number between 16 and 32")
}
const verificationKey = {
...verificationKeys,
vk_delta_2: verificationKeys.vk_delta_2[treeDepth - 16],
IC: verificationKeys.IC[treeDepth - 16]
}
return groth16.verify(
verificationKey,
[merkleTreeRoot, nullifierHash, hash(signal), hash(externalNullifier)],
unpackProof(proof)
)
}

View File

@@ -1,4 +0,0 @@
{
"extends": "./tsconfig.json",
"include": ["src", "src/verificationKeys.json"]
}

View File

@@ -1,14 +0,0 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"baseUrl": ".",
"rootDir": ".",
"outDir": "./dist",
"typeRoots": ["node_modules/@types", "src/@types"]
},
"include": ["src", "src/verificationKeys.json"],
"exclude": [
"node_modules",
"dist"
]
}

View File

@@ -38,9 +38,8 @@
"dependencies": {
"@cryptkeeperzk/types": "workspace:^",
"@cryptkeeperzk/zk": "workspace:^",
"@semaphore-protocol/group": "^3.10.1",
"@semaphore-protocol/identity": "^3.10.1",
"@semaphore-protocol/proof": "^3.10.1",
"@cryptkeeperzk/semaphore-identity": "^3.10.3",
"@cryptkeeperzk/semaphore-proof": "^3.10.3",
"@zk-kit/incremental-merkle-tree": "^1.1.0",
"bigint-conversion": "^2.4.1",
"lodash": "^4.17.21",

View File

@@ -34,8 +34,8 @@
"githook:precommit": "lint-staged && pnpm run types"
},
"dependencies": {
"@cryptkeeperzk/rln-proof": "workspace:^",
"@semaphore-protocol/proof": "^3.10.1",
"@cryptkeeperzk/rlnjs": "^3.1.10",
"@cryptkeeperzk/semaphore-proof": "^3.10.3",
"@zk-kit/incremental-merkle-tree": "^1.1.0"
},
"devDependencies": {

View File

@@ -1,8 +1,8 @@
import { MerkleProof } from "@cryptkeeperzk/rln-proof";
import { MerkleProof } from "@cryptkeeperzk/rlnjs";
import { ZkCircuit, ZkInputs } from "./zkProof";
export type { RLNFullProof, VerificationKey, RLNSNARKProof } from "@cryptkeeperzk/rln-proof";
export type { RLNFullProof, VerificationKey, RLNSNARKProof } from "@cryptkeeperzk/rlnjs";
/**
* Represents the arguments required for generating an RLN proof.
@@ -56,6 +56,6 @@ export interface IRlnProverInputs {
userMessageLimit: bigint;
messageId: bigint;
merkleProof: MerkleProof;
messageHash: bigint;
x: bigint;
epoch: bigint;
}

View File

@@ -1,4 +1,4 @@
import { FullProof } from "@semaphore-protocol/proof";
import { FullProof } from "@cryptkeeperzk/semaphore-proof";
import { ZkCircuit, ZkInputs } from "./zkProof";

View File

@@ -34,11 +34,11 @@
"githook:prepush": "pnpm run test:coverage"
},
"dependencies": {
"@cryptkeeperzk/rln-proof": "workspace:^",
"@cryptkeeperzk/semaphore-proof": "workspace:^",
"@cryptkeeperzk/rlnjs": "^3.1.10",
"@cryptkeeperzk/semaphore-group": "^3.10.3",
"@cryptkeeperzk/semaphore-identity": "^3.10.3",
"@cryptkeeperzk/semaphore-proof": "^3.10.3",
"@cryptkeeperzk/types": "workspace:^",
"@semaphore-protocol/group": "^3.10.1",
"@semaphore-protocol/identity": "^3.10.1",
"@zk-kit/incremental-merkle-tree": "^1.1.0",
"bigint-conversion": "^2.4.1",
"lodash": "^4.17.21",

View File

@@ -1,5 +1,5 @@
import { Identity } from "@cryptkeeperzk/semaphore-identity";
import { ICreateIdentityArgs, StrategiesMap } from "@cryptkeeperzk/types";
import { Identity } from "@semaphore-protocol/identity";
import { ZkIdentitySemaphore } from "../protocols";

View File

@@ -1,4 +1,4 @@
import { Identity } from "@semaphore-protocol/identity";
import { Identity } from "@cryptkeeperzk/semaphore-identity";
import type { SerializedIdentity, IdentityMetadata } from "@cryptkeeperzk/types";

View File

@@ -1,4 +1,4 @@
import { Identity } from "@semaphore-protocol/identity";
import { Identity } from "@cryptkeeperzk/semaphore-identity";
import type { IdentityMetadata } from "@cryptkeeperzk/types";

View File

@@ -1,4 +1,4 @@
import { RLNProver, RLNSNARKProof } from "@cryptkeeperzk/rln-proof";
import { RLNProver, RLNSNARKProof } from "@cryptkeeperzk/rlnjs";
import { ZkIdentitySemaphore } from "@src/identity";
@@ -49,7 +49,7 @@ export class RLNProofService implements IZkProof<IRlnProofRequest, RLNSNARKProof
userMessageLimit,
messageId: BigInt(messageId),
merkleProof,
messageHash,
x: messageHash,
epoch: epochBigInt,
};

View File

@@ -1,5 +1,5 @@
import { Identity } from "@cryptkeeperzk/semaphore-identity";
import { IdentityMetadata, IRlnProofRequest, ISemaphoreProofRequest } from "@cryptkeeperzk/types";
import { Identity } from "@semaphore-protocol/identity";
import { MerkleProof } from "@zk-kit/incremental-merkle-tree";
import { ZkIdentitySemaphore } from "@src/identity";
@@ -9,7 +9,7 @@ import { RLNProofService } from "../RLNProof";
import { SemaphoreProofService } from "../SemaphoreProof";
import { getMerkleProof } from "../utils";
jest.mock("@cryptkeeperzk/rln-proof", (): unknown => ({
jest.mock("@cryptkeeperzk/rlnjs", (): unknown => ({
RLNProver: jest.fn(() => ({
generateProof: mockRlnGenerateProof, // Mock the generateProof function to resolve with emptyFullProof
})),

View File

@@ -1,4 +1,4 @@
import { Identity } from "@semaphore-protocol/identity";
import { Identity } from "@cryptkeeperzk/semaphore-identity";
import { MerkleProof } from "@zk-kit/incremental-merkle-tree";
import { poseidon1, poseidon2 } from "poseidon-lite";

View File

@@ -1,4 +1,4 @@
import { Proof, RLNFullProof, RLNPublicSignals } from "@cryptkeeperzk/rln-proof";
import { Proof, RLNFullProof, RLNPublicSignals } from "@cryptkeeperzk/rlnjs";
export const mockRlnGenerateProof = jest.fn();
export const mockSemaphoreGenerateProof = jest.fn();

View File

@@ -1,4 +1,4 @@
import { Group, BigNumberish } from "@semaphore-protocol/group";
import { Group, BigNumberish } from "@cryptkeeperzk/semaphore-group";
import { MerkleProof } from "@zk-kit/incremental-merkle-tree";
import { bigintToHex, hexToBigint } from "bigint-conversion";
// TODO: I think we should have a service for providing Cryptography pure related functions

View File

@@ -1,4 +1,4 @@
import { RLNSNARKProof } from "@cryptkeeperzk/rln-proof";
import { RLNSNARKProof } from "@cryptkeeperzk/rlnjs";
import { ZkIdentitySemaphore } from "@src/identity";

3252
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,3 @@
packages:
- "packages/*"
- "packages/proofs/*"