chore: create verifier and integrate

This commit is contained in:
rymnc 2023-05-26 13:31:05 +05:30
parent 18783ba67e
commit fc606d98b2
No known key found for this signature in database
GPG Key ID: AAA088D5C68ECD34
10 changed files with 660 additions and 67 deletions

9
contracts/IVerifier.sol Normal file
View File

@ -0,0 +1,9 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
pragma solidity 0.8.15;
interface IVerifier {
function verifyProof(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c, uint256[2] memory input)
external
view
returns (bool);
}

View File

@ -3,6 +3,10 @@
pragma solidity 0.8.15; pragma solidity 0.8.15;
import {IPoseidonHasher} from "./PoseidonHasher.sol"; import {IPoseidonHasher} from "./PoseidonHasher.sol";
import {IVerifier} from "./IVerifier.sol";
import "forge-std/console.sol";
/// The tree is full /// The tree is full
error FullTree(); error FullTree();
@ -30,6 +34,9 @@ error InsufficientWithdrawalBalance();
/// Contract has insufficient balance to return /// Contract has insufficient balance to return
error InsufficientContractBalance(); error InsufficientContractBalance();
/// Invalid proof
error InvalidProof();
contract RLN { contract RLN {
/// @notice The deposit amount required to register as a member /// @notice The deposit amount required to register as a member
uint256 public immutable MEMBERSHIP_DEPOSIT; uint256 public immutable MEMBERSHIP_DEPOSIT;
@ -57,6 +64,9 @@ contract RLN {
/// @notice The Poseidon hasher contract /// @notice The Poseidon hasher contract
IPoseidonHasher public immutable poseidonHasher; IPoseidonHasher public immutable poseidonHasher;
/// @notice The groth16 verifier contract
IVerifier public immutable verifier;
/// Emitted when a new member is added to the set /// Emitted when a new member is added to the set
/// @param idCommitment The idCommitment of the member /// @param idCommitment The idCommitment of the member
/// @param index The index of the member in the set /// @param index The index of the member in the set
@ -70,12 +80,14 @@ contract RLN {
constructor( constructor(
uint256 membershipDeposit, uint256 membershipDeposit,
uint256 depth, uint256 depth,
address _poseidonHasher address _poseidonHasher,
address _verifier
) { ) {
MEMBERSHIP_DEPOSIT = membershipDeposit; MEMBERSHIP_DEPOSIT = membershipDeposit;
DEPTH = depth; DEPTH = depth;
SET_SIZE = 1 << depth; SET_SIZE = 1 << depth;
poseidonHasher = IPoseidonHasher(_poseidonHasher); poseidonHasher = IPoseidonHasher(_poseidonHasher);
verifier = IVerifier(_verifier);
} }
/// Allows a user to register as a member /// Allows a user to register as a member
@ -100,27 +112,28 @@ contract RLN {
idCommitmentIndex += 1; idCommitmentIndex += 1;
} }
/// Allows a user to slash a member /// @dev Allows a user to slash a member
/// @param secret The idSecretHash of the member /// @param idCommitment The idCommitment of the member
function slash(uint256 secret, address payable receiver) external { function slash(uint256 idCommitment, address payable receiver, uint256[8] calldata proof) external {
_slash(secret, receiver); _slash(idCommitment, receiver, proof);
} }
/// Slashes a member by removing them from the set, and transferring their /// @dev Slashes a member by removing them from the set, and adding their
/// stake to the receiver /// stake to the receiver's available withdrawal balance
/// @param secret The idSecretHash of the member /// @param idCommitment The idCommitment of the member
/// @param receiver The address to receive the funds /// @param receiver The address to receive the funds
function _slash(uint256 secret, address payable receiver) internal { function _slash(uint256 idCommitment, address payable receiver, uint256[8] calldata proof) internal {
if (receiver == address(this) || receiver == address(0)) if (receiver == address(this) || receiver == address(0))
revert InvalidReceiverAddress(receiver); revert InvalidReceiverAddress(receiver);
// derive idCommitment
uint256 idCommitment = hash(secret);
// check if member is registered
if (members[idCommitment] == 0) revert MemberNotRegistered(idCommitment); if (members[idCommitment] == 0) revert MemberNotRegistered(idCommitment);
// check if member is registered
if (stakedAmounts[idCommitment] == 0) if (stakedAmounts[idCommitment] == 0)
revert MemberHasNoStake(idCommitment); revert MemberHasNoStake(idCommitment);
if(!_verifyProof(idCommitment, receiver, proof))
revert InvalidProof();
uint256 amountToTransfer = stakedAmounts[idCommitment]; uint256 amountToTransfer = stakedAmounts[idCommitment];
// delete member // delete member
@ -153,4 +166,18 @@ contract RLN {
function hash(uint256 input) internal view returns (uint256) { function hash(uint256 input) internal view returns (uint256) {
return poseidonHasher.hash(input); return poseidonHasher.hash(input);
} }
/// @dev Groth16 proof verification
function _verifyProof(uint256 idCommitment, address receiver, uint256[8] calldata proof)
internal
view
returns (bool)
{
return verifier.verifyProof(
[proof[0], proof[1]],
[[proof[2], proof[3]], [proof[4], proof[5]]],
[proof[6], proof[7]],
[idCommitment, uint256(uint160(receiver))]
);
}
} }

303
contracts/RlnVerifier.sol Normal file
View File

@ -0,0 +1,303 @@
// File: https://github.com/Rate-Limiting-Nullifier/rln-contract-v1/blob/main/src/RLNVerifier.sol
// Copyright 2017 Christian Reitwiessner
// 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.
//
// 2019 OKIMS
// ported to solidity 0.6
// fixed linter warnings
// added requiere error messages
//
//
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.6.11;
library Pairing {
struct G1Point {
uint256 X;
uint256 Y;
}
// Encoding of field elements is: X[0] * z + X[1]
struct G2Point {
uint256[2] X;
uint256[2] Y;
}
/// @return the generator of G1
function P1() internal pure returns (G1Point memory) {
return G1Point(1, 2);
}
/// @return the generator of G2
function P2() internal pure returns (G2Point memory) {
// Original code point
return G2Point(
[
11559732032986387107991004021392285783925812861821192530917403151452391805634,
10857046999023057135944570762232829481370756359578518086990519993285655852781
],
[
4082367875863433681332203403145435568316851327593401208105741076214120093531,
8495653923123431417604973247489272438418190587263600148770280649306958101930
]
);
/*
// Changed by Jordi point
return G2Point(
[10857046999023057135944570762232829481370756359578518086990519993285655852781,
11559732032986387107991004021392285783925812861821192530917403151452391805634],
[8495653923123431417604973247489272438418190587263600148770280649306958101930,
4082367875863433681332203403145435568316851327593401208105741076214120093531]
);*/
}
/// @return r the negation of p, i.e. p.addition(p.negate()) should be zero.
function negate(G1Point memory p) internal pure returns (G1Point memory r) {
// The prime q in the base field F_q for G1
uint256 q = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
if (p.X == 0 && p.Y == 0) {
return G1Point(0, 0);
}
return G1Point(p.X, q - (p.Y % q));
}
/// @return r the sum of two points of G1
function addition(G1Point memory p1, G1Point memory p2) internal view returns (G1Point memory r) {
uint256[4] memory input;
input[0] = p1.X;
input[1] = p1.Y;
input[2] = p2.X;
input[3] = p2.Y;
bool success;
// solium-disable-next-line security/no-inline-assembly
assembly {
success := staticcall(sub(gas(), 2000), 6, input, 0xc0, r, 0x60)
// Use "invalid" to make gas estimation work
switch success
case 0 { invalid() }
}
require(success, "pairing-add-failed");
}
/// @return r the product of a point on G1 and a scalar, i.e.
/// p == p.scalar_mul(1) and p.addition(p) == p.scalar_mul(2) for all points p.
function scalar_mul(G1Point memory p, uint256 s) internal view returns (G1Point memory r) {
uint256[3] memory input;
input[0] = p.X;
input[1] = p.Y;
input[2] = s;
bool success;
// solium-disable-next-line security/no-inline-assembly
assembly {
success := staticcall(sub(gas(), 2000), 7, input, 0x80, r, 0x60)
// Use "invalid" to make gas estimation work
switch success
case 0 { invalid() }
}
require(success, "pairing-mul-failed");
}
/// @return the result of computing the pairing check
/// e(p1[0], p2[0]) * .... * e(p1[n], p2[n]) == 1
/// For example pairing([P1(), P1().negate()], [P2(), P2()]) should
/// return true.
function pairing(G1Point[] memory p1, G2Point[] memory p2) internal view returns (bool) {
require(p1.length == p2.length, "pairing-lengths-failed");
uint256 elements = p1.length;
uint256 inputSize = elements * 6;
uint256[] memory input = new uint[](inputSize);
for (uint256 i = 0; i < elements; i++) {
input[i * 6 + 0] = p1[i].X;
input[i * 6 + 1] = p1[i].Y;
input[i * 6 + 2] = p2[i].X[0];
input[i * 6 + 3] = p2[i].X[1];
input[i * 6 + 4] = p2[i].Y[0];
input[i * 6 + 5] = p2[i].Y[1];
}
uint256[1] memory out;
bool success;
// solium-disable-next-line security/no-inline-assembly
assembly {
success := staticcall(sub(gas(), 2000), 8, add(input, 0x20), mul(inputSize, 0x20), out, 0x20)
// Use "invalid" to make gas estimation work
switch success
case 0 { invalid() }
}
require(success, "pairing-opcode-failed");
return out[0] != 0;
}
/// Convenience method for a pairing check for two pairs.
function pairingProd2(G1Point memory a1, G2Point memory a2, G1Point memory b1, G2Point memory b2)
internal
view
returns (bool)
{
G1Point[] memory p1 = new G1Point[](2);
G2Point[] memory p2 = new G2Point[](2);
p1[0] = a1;
p1[1] = b1;
p2[0] = a2;
p2[1] = b2;
return pairing(p1, p2);
}
/// Convenience method for a pairing check for three pairs.
function pairingProd3(
G1Point memory a1,
G2Point memory a2,
G1Point memory b1,
G2Point memory b2,
G1Point memory c1,
G2Point memory c2
) internal view returns (bool) {
G1Point[] memory p1 = new G1Point[](3);
G2Point[] memory p2 = new G2Point[](3);
p1[0] = a1;
p1[1] = b1;
p1[2] = c1;
p2[0] = a2;
p2[1] = b2;
p2[2] = c2;
return pairing(p1, p2);
}
/// Convenience method for a pairing check for four pairs.
function pairingProd4(
G1Point memory a1,
G2Point memory a2,
G1Point memory b1,
G2Point memory b2,
G1Point memory c1,
G2Point memory c2,
G1Point memory d1,
G2Point memory d2
) internal view returns (bool) {
G1Point[] memory p1 = new G1Point[](4);
G2Point[] memory p2 = new G2Point[](4);
p1[0] = a1;
p1[1] = b1;
p1[2] = c1;
p1[3] = d1;
p2[0] = a2;
p2[1] = b2;
p2[2] = c2;
p2[3] = d2;
return pairing(p1, p2);
}
}
contract Verifier {
using Pairing for *;
struct VerifyingKey {
Pairing.G1Point alfa1;
Pairing.G2Point beta2;
Pairing.G2Point gamma2;
Pairing.G2Point delta2;
Pairing.G1Point[] IC;
}
struct Proof {
Pairing.G1Point A;
Pairing.G2Point B;
Pairing.G1Point C;
}
function verifyingKey() internal pure returns (VerifyingKey memory vk) {
vk.alfa1 = Pairing.G1Point(
20491192805390485299153009773594534940189261866228447918068658471970481763042,
9383485363053290200918347156157836566562967994039712273449902621266178545958
);
vk.beta2 = Pairing.G2Point(
[
4252822878758300859123897981450591353533073413197771768651442665752259397132,
6375614351688725206403948262868962793625744043794305715222011528459656738731
],
[
21847035105528745403288232691147584728191162732299865338377159692350059136679,
10505242626370262277552901082094356697409835680220590971873171140371331206856
]
);
vk.gamma2 = Pairing.G2Point(
[
11559732032986387107991004021392285783925812861821192530917403151452391805634,
10857046999023057135944570762232829481370756359578518086990519993285655852781
],
[
4082367875863433681332203403145435568316851327593401208105741076214120093531,
8495653923123431417604973247489272438418190587263600148770280649306958101930
]
);
vk.delta2 = Pairing.G2Point(
[
12423666958566268737444308034237892912702648013927558883280319245968679130649,
15986964528637281931410749976607406939789163617014270799373312764775965360012
],
[
8394023076056524902583796202128496802110914536948580183128578071394816660799,
4964607673011101982600772762445991192038811950832626693345350322823626470007
]
);
vk.IC = new Pairing.G1Point[](3);
vk.IC[0] = Pairing.G1Point(
1655549413518972190198478012616802994254462093161203201613599472264958303841,
21742734017792296281216385119397138748114275727065024271646515586404591497876
);
vk.IC[1] = Pairing.G1Point(
16497930821522159474595176304955625435616718625609462506360632944366974274906,
10404924572941018678793755094259635830045501866471999610240845041996101882275
);
vk.IC[2] = Pairing.G1Point(
9567910551099174794221497568036631681620409346997815381833929247558241020796,
17282591858786007768931802126325866705896012606427630592145070155065868649172
);
}
function verify(uint256[] memory input, Proof memory proof) internal view returns (uint256) {
uint256 snark_scalar_field = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
VerifyingKey memory vk = verifyingKey();
require(input.length + 1 == vk.IC.length, "verifier-bad-input");
// Compute the linear combination vk_x
Pairing.G1Point memory vk_x = Pairing.G1Point(0, 0);
for (uint256 i = 0; i < input.length; i++) {
require(input[i] < snark_scalar_field, "verifier-gte-snark-scalar-field");
vk_x = Pairing.addition(vk_x, Pairing.scalar_mul(vk.IC[i + 1], input[i]));
}
vk_x = Pairing.addition(vk_x, vk.IC[0]);
if (
!Pairing.pairingProd4(
Pairing.negate(proof.A), proof.B, vk.alfa1, vk.beta2, vk_x, vk.gamma2, proof.C, vk.delta2
)
) return 1;
return 0;
}
/// @return r bool true if proof is valid
function verifyProof(uint256[2] memory a, uint256[2][2] memory b, uint256[2] memory c, uint256[2] memory input)
public
view
returns (bool r)
{
Proof memory proof;
proof.A = Pairing.G1Point(a[0], a[1]);
proof.B = Pairing.G2Point([b[0][0], b[0][1]], [b[1][0], b[1][1]]);
proof.C = Pairing.G1Point(c[0], c[1]);
uint256[] memory inputValues = new uint[](input.length);
for (uint256 i = 0; i < input.length; i++) {
inputValues[i] = input[i];
}
if (verify(inputValues, proof) == 0) {
return true;
} else {
return false;
}
}
}

View File

@ -0,0 +1,16 @@
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { DeployFunction } from "hardhat-deploy/types";
const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const { deployments, getUnnamedAccounts } = hre;
const { deploy } = deployments;
const [deployer] = await getUnnamedAccounts();
await deploy("Verifier", {
from: deployer,
log: true,
});
};
export default func;
func.tags = ["RlnVerifier"];

View File

@ -9,13 +9,14 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const poseidonHasherAddress = (await deployments.get("PoseidonHasher")) const poseidonHasherAddress = (await deployments.get("PoseidonHasher"))
.address; .address;
const rlnVerifierAddress = (await deployments.get("Verifier")).address;
await deploy("RLN", { await deploy("RLN", {
from: deployer, from: deployer,
log: true, log: true,
args: [1000000000000000, 20, poseidonHasherAddress], args: [1000000000000000, 20, poseidonHasherAddress, rlnVerifierAddress],
}); });
}; };
export default func; export default func;
func.tags = ["RLN"]; func.tags = ["RLN"];
func.dependencies = ["PoseidonHasher"]; func.dependencies = ["PoseidonHasher", "RlnVerifier"];

View File

@ -1,5 +1,177 @@
# Solidity API # Solidity API
## Pairing
### G1Point
```solidity
struct G1Point {
uint256 X;
uint256 Y;
}
```
### G2Point
```solidity
struct G2Point {
uint256[2] X;
uint256[2] Y;
}
```
### P1
```solidity
function P1() internal pure returns (struct Pairing.G1Point)
```
#### Return Values
| Name | Type | Description |
| ---- | ---------------------- | ------------------- |
| [0] | struct Pairing.G1Point | the generator of G1 |
### P2
```solidity
function P2() internal pure returns (struct Pairing.G2Point)
```
#### Return Values
| Name | Type | Description |
| ---- | ---------------------- | ------------------- |
| [0] | struct Pairing.G2Point | the generator of G2 |
### negate
```solidity
function negate(struct Pairing.G1Point p) internal pure returns (struct Pairing.G1Point r)
```
#### Return Values
| Name | Type | Description |
| ---- | ---------------------- | -------------------------------------------------------------- |
| r | struct Pairing.G1Point | the negation of p, i.e. p.addition(p.negate()) should be zero. |
### addition
```solidity
function addition(struct Pairing.G1Point p1, struct Pairing.G1Point p2) internal view returns (struct Pairing.G1Point r)
```
#### Return Values
| Name | Type | Description |
| ---- | ---------------------- | --------------------------- |
| r | struct Pairing.G1Point | the sum of two points of G1 |
### scalar_mul
```solidity
function scalar_mul(struct Pairing.G1Point p, uint256 s) internal view returns (struct Pairing.G1Point r)
```
#### Return Values
| Name | Type | Description |
| ---- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| r | struct Pairing.G1Point | the product of a point on G1 and a scalar, i.e. p == p.scalar_mul(1) and p.addition(p) == p.scalar_mul(2) for all points p. |
### pairing
```solidity
function pairing(struct Pairing.G1Point[] p1, struct Pairing.G2Point[] p2) internal view returns (bool)
```
#### Return Values
| Name | Type | Description |
| ---- | ---- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [0] | bool | the result of computing the pairing check e(p1[0], p2[0]) _ .... _ e(p1[n], p2[n]) == 1 For example pairing([P1(), P1().negate()], [P2(), P2()]) should return true. |
### pairingProd2
```solidity
function pairingProd2(struct Pairing.G1Point a1, struct Pairing.G2Point a2, struct Pairing.G1Point b1, struct Pairing.G2Point b2) internal view returns (bool)
```
Convenience method for a pairing check for two pairs.
### pairingProd3
```solidity
function pairingProd3(struct Pairing.G1Point a1, struct Pairing.G2Point a2, struct Pairing.G1Point b1, struct Pairing.G2Point b2, struct Pairing.G1Point c1, struct Pairing.G2Point c2) internal view returns (bool)
```
Convenience method for a pairing check for three pairs.
### pairingProd4
```solidity
function pairingProd4(struct Pairing.G1Point a1, struct Pairing.G2Point a2, struct Pairing.G1Point b1, struct Pairing.G2Point b2, struct Pairing.G1Point c1, struct Pairing.G2Point c2, struct Pairing.G1Point d1, struct Pairing.G2Point d2) internal view returns (bool)
```
Convenience method for a pairing check for four pairs.
## Verifier
### VerifyingKey
```solidity
struct VerifyingKey {
struct Pairing.G1Point alfa1;
struct Pairing.G2Point beta2;
struct Pairing.G2Point gamma2;
struct Pairing.G2Point delta2;
struct Pairing.G1Point[] IC;
}
```
### Proof
```solidity
struct Proof {
struct Pairing.G1Point A;
struct Pairing.G2Point B;
struct Pairing.G1Point C;
}
```
### verifyingKey
```solidity
function verifyingKey() internal pure returns (struct Verifier.VerifyingKey vk)
```
### verify
```solidity
function verify(uint256[] input, struct Verifier.Proof proof) internal view returns (uint256)
```
### verifyProof
```solidity
function verifyProof(uint256[2] a, uint256[2][2] b, uint256[2] c, uint256[2] input) public view returns (bool r)
```
#### Return Values
| Name | Type | Description |
| ---- | ---- | --------------------------- |
| r | bool | bool true if proof is valid |
## IVerifier
### verifyProof
```solidity
function verifyProof(uint256[2] a, uint256[2][2] b, uint256[2] c, uint256[2] input) external view returns (bool)
```
## IPoseidonHasher ## IPoseidonHasher
### hash ### hash
@ -907,6 +1079,14 @@ error InsufficientContractBalance()
Contract has insufficient balance to return Contract has insufficient balance to return
## InvalidProof
```solidity
error InvalidProof()
```
Invalid proof
## RLN ## RLN
### MEMBERSHIP_DEPOSIT ### MEMBERSHIP_DEPOSIT
@ -975,6 +1155,14 @@ contract IPoseidonHasher poseidonHasher
The Poseidon hasher contract The Poseidon hasher contract
### verifier
```solidity
contract IVerifier verifier
```
The groth16 verifier contract
### MemberRegistered ### MemberRegistered
```solidity ```solidity
@ -1008,7 +1196,7 @@ Emitted when a member is removed from the set
### constructor ### constructor
```solidity ```solidity
constructor(uint256 membershipDeposit, uint256 depth, address _poseidonHasher) public constructor(uint256 membershipDeposit, uint256 depth, address _poseidonHasher, address _verifier) public
``` ```
### register ### register
@ -1043,33 +1231,35 @@ Registers a member
### slash ### slash
```solidity ```solidity
function slash(uint256 secret, address payable receiver) external function slash(uint256 idCommitment, address payable receiver, uint256[8] proof) external
``` ```
Allows a user to slash a member _Allows a user to slash a member_
#### Parameters #### Parameters
| Name | Type | Description | | Name | Type | Description |
| -------- | --------------- | ------------------------------ | | ------------ | --------------- | ------------------------------ |
| secret | uint256 | The idSecretHash of the member | | idCommitment | uint256 | The idCommitment of the member |
| receiver | address payable | | | receiver | address payable | |
| proof | uint256[8] | |
### \_slash ### \_slash
```solidity ```solidity
function _slash(uint256 secret, address payable receiver) internal function _slash(uint256 idCommitment, address payable receiver, uint256[8] proof) internal
``` ```
Slashes a member by removing them from the set, and transferring their _Slashes a member by removing them from the set, and adding their
stake to the receiver stake to the receiver's available withdrawal balance_
#### Parameters #### Parameters
| Name | Type | Description | | Name | Type | Description |
| -------- | --------------- | -------------------------------- | | ------------ | --------------- | -------------------------------- |
| secret | uint256 | The idSecretHash of the member | | idCommitment | uint256 | The idCommitment of the member |
| receiver | address payable | The address to receive the funds | | receiver | address payable | The address to receive the funds |
| proof | uint256[8] | |
### withdraw ### withdraw
@ -1093,3 +1283,11 @@ NOTE: The variant of Poseidon we use accepts only 1 input, assume n=2, and the s
| Name | Type | Description | | Name | Type | Description |
| ----- | ------- | ----------------- | | ----- | ------- | ----------------- |
| input | uint256 | The value to hash | | input | uint256 | The value to hash |
### \_verifyProof
```solidity
function _verifyProof(uint256 idCommitment, address receiver, uint256[8] proof) internal view returns (bool)
```
_Groth16 proof verification_

View File

@ -47,6 +47,9 @@ const config: HardhatUserConfig = {
{ {
version: "0.8.15", version: "0.8.15",
}, },
{
version: "0.6.11",
},
], ],
}, },
networks: getNetworkConfig(), networks: getNetworkConfig(),

View File

@ -3,24 +3,32 @@ pragma solidity ^0.8.15;
import "../contracts/PoseidonHasher.sol"; import "../contracts/PoseidonHasher.sol";
import "../contracts/Rln.sol"; import "../contracts/Rln.sol";
import "./Verifier.sol";
import "forge-std/Test.sol"; import "forge-std/Test.sol";
import "forge-std/StdCheats.sol"; import "forge-std/StdCheats.sol";
import "forge-std/console.sol"; import "forge-std/console.sol";
contract RLNTest is Test { contract RLNTest is Test {
using stdStorage for StdStorage; using stdStorage for StdStorage;
RLN public rln; RLN public rln;
PoseidonHasher public poseidon; PoseidonHasher public poseidon;
TrueVerifier public trueVerifier;
FalseVerifier public falseVerifier;
uint256 public constant MEMBERSHIP_DEPOSIT = 1000000000000000; uint256 public constant MEMBERSHIP_DEPOSIT = 1000000000000000;
uint256 public constant DEPTH = 20; uint256 public constant DEPTH = 20;
uint256 public constant SET_SIZE = 1048576; uint256 public constant SET_SIZE = 1048576;
uint256[8] public zeroedProof = [0, 0, 0, 0, 0, 0, 0, 0];
/// @dev Setup the testing environment. /// @dev Setup the testing environment.
function setUp() public { function setUp() public {
poseidon = new PoseidonHasher(); poseidon = new PoseidonHasher();
rln = new RLN(MEMBERSHIP_DEPOSIT, DEPTH, address(poseidon)); trueVerifier = new TrueVerifier();
falseVerifier = new FalseVerifier();
rln = new RLN(MEMBERSHIP_DEPOSIT, DEPTH, address(poseidon), address(trueVerifier));
} }
/// @dev Ensure that you can hash a value. /// @dev Ensure that you can hash a value.
@ -67,7 +75,8 @@ contract RLNTest is Test {
RLN tempRln = new RLN( RLN tempRln = new RLN(
MEMBERSHIP_DEPOSIT, MEMBERSHIP_DEPOSIT,
2, 2,
address(rln.poseidonHasher()) address(rln.poseidonHasher()),
address(rln.verifier())
); );
uint256 setSize = tempRln.SET_SIZE() - 1; uint256 setSize = tempRln.SET_SIZE() - 1;
for (uint256 i = 0; i < setSize; i++) { for (uint256 i = 0; i < setSize; i++) {
@ -78,19 +87,18 @@ contract RLNTest is Test {
tempRln.register{value: MEMBERSHIP_DEPOSIT}(idCommitmentSeed + setSize); tempRln.register{value: MEMBERSHIP_DEPOSIT}(idCommitmentSeed + setSize);
} }
function test__ValidSlash(uint256 idSecretHash, address payable to) public { function test__ValidSlash(uint256 idCommitment, address payable to) public {
// avoid precompiles, etc // avoid precompiles, etc
// TODO: wrap both of these in a single function // TODO: wrap both of these in a single function
assumePayable(to); assumePayable(to);
assumeNoPrecompiles(to); assumeNoPrecompiles(to);
vm.assume(to != address(0)); vm.assume(to != address(0));
uint256 idCommitment = poseidon.hash(idSecretHash);
rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment); rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment);
assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT); assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT);
uint256 balanceBefore = to.balance; uint256 balanceBefore = to.balance;
rln.slash(idSecretHash, to); rln.slash(idCommitment, to, zeroedProof);
vm.prank(to); vm.prank(to);
rln.withdraw(); rln.withdraw();
assertEq(rln.stakedAmounts(idCommitment), 0); assertEq(rln.stakedAmounts(idCommitment), 0);
@ -99,19 +107,18 @@ contract RLNTest is Test {
} }
function test__InvalidSlash__ToZeroAddress() public { function test__InvalidSlash__ToZeroAddress() public {
uint256 idSecretHash = 19014214495641488759237505126948346942972912379615652741039992445865937985820; uint256 idCommitment = 9014214495641488759237505126948346942972912379615652741039992445865937985820;
uint256 idCommitment = poseidon.hash(idSecretHash);
rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment); rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment);
assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT); assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT);
vm.expectRevert( vm.expectRevert(
abi.encodeWithSelector(InvalidReceiverAddress.selector, address(0)) abi.encodeWithSelector(InvalidReceiverAddress.selector, address(0))
); );
rln.slash(idSecretHash, payable(address(0))); rln.slash(idCommitment, payable(address(0)), zeroedProof);
} }
function test__InvalidSlash__ToRlnAddress() public { function test__InvalidSlash__ToRlnAddress() public {
uint256 idSecretHash = 19014214495641488759237505126948346942972912379615652741039992445865937985820; uint256 idCommitment = 19014214495641488759237505126948346942972912379615652741039992445865937985820;
uint256 idCommitment = poseidon.hash(idSecretHash);
rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment); rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment);
assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT); assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT);
vm.expectRevert( vm.expectRevert(
@ -120,34 +127,32 @@ contract RLNTest is Test {
address(rln) address(rln)
) )
); );
rln.slash(idSecretHash, payable(address(rln))); rln.slash(idCommitment, payable(address(rln)), zeroedProof);
} }
function test__InvalidSlash__InvalidIdCommitment( function test__InvalidSlash__InvalidIdCommitment(
uint256 idSecretHash uint256 idCommitment
) public { ) public {
uint256 idCommitment = poseidon.hash(idSecretHash);
vm.expectRevert( vm.expectRevert(
abi.encodeWithSelector(MemberNotRegistered.selector, idCommitment) abi.encodeWithSelector(MemberNotRegistered.selector, idCommitment)
); );
rln.slash(idSecretHash, payable(address(this))); rln.slash(idCommitment, payable(address(this)), zeroedProof);
} }
// this shouldn't be possible, but just in case // this shouldn't be possible, but just in case
function test__InvalidSlash__NoStake( function test__InvalidSlash__NoStake(
uint256 idSecretHash, uint256 idCommitment,
address payable to address payable to
) public { ) public {
// avoid precompiles, etc // avoid precompiles, etc
assumePayable(to); assumePayable(to);
assumeNoPrecompiles(to); assumeNoPrecompiles(to);
vm.assume(to != address(0)); vm.assume(to != address(0));
uint256 idCommitment = poseidon.hash(idSecretHash);
rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment); rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment);
assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT); assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT);
rln.slash(idSecretHash, to); rln.slash(idCommitment, to, zeroedProof);
assertEq(rln.stakedAmounts(idCommitment), 0); assertEq(rln.stakedAmounts(idCommitment), 0);
assertEq(rln.members(idCommitment), 0); assertEq(rln.members(idCommitment), 0);
@ -162,7 +167,23 @@ contract RLNTest is Test {
vm.expectRevert( vm.expectRevert(
abi.encodeWithSelector(MemberHasNoStake.selector, idCommitment) abi.encodeWithSelector(MemberHasNoStake.selector, idCommitment)
); );
rln.slash(idSecretHash, to); rln.slash(idCommitment, to, zeroedProof);
}
function test__InvalidSlash__InvalidProof() public {
uint256 idCommitment = 19014214495641488759237505126948346942972912379615652741039992445865937985820;
RLN tempRln = new RLN(
MEMBERSHIP_DEPOSIT,
2,
address(rln.poseidonHasher()),
address(falseVerifier)
);
tempRln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment);
vm.expectRevert(InvalidProof.selector);
tempRln.slash(idCommitment, payable(address(this)), zeroedProof);
} }
function test__InvalidWithdraw__InsufficientWithdrawalBalance() public { function test__InvalidWithdraw__InsufficientWithdrawalBalance() public {
@ -171,11 +192,10 @@ contract RLNTest is Test {
} }
function test__InvalidWithdraw__InsufficientContractBalance() public { function test__InvalidWithdraw__InsufficientContractBalance() public {
uint256 idSecretHash = 19014214495641488759237505126948346942972912379615652741039992445865937985820; uint256 idCommitment = 19014214495641488759237505126948346942972912379615652741039992445865937985820;
uint256 idCommitment = poseidon.hash(idSecretHash);
rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment); rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment);
assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT); assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT);
rln.slash(idSecretHash, payable(address(this))); rln.slash(idCommitment, payable(address(this)), zeroedProof);
assertEq(rln.stakedAmounts(idCommitment), 0); assertEq(rln.stakedAmounts(idCommitment), 0);
assertEq(rln.members(idCommitment), 0); assertEq(rln.members(idCommitment), 0);
@ -188,12 +208,11 @@ contract RLNTest is Test {
assumePayable(to); assumePayable(to);
assumeNoPrecompiles(to); assumeNoPrecompiles(to);
uint256 idSecretHash = 19014214495641488759237505126948346942972912379615652741039992445865937985820; uint256 idCommitment = 19014214495641488759237505126948346942972912379615652741039992445865937985820;
uint256 idCommitment = poseidon.hash(idSecretHash);
rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment); rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment);
assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT); assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT);
rln.slash(idSecretHash, to); rln.slash(idCommitment, to, zeroedProof);
assertEq(rln.stakedAmounts(idCommitment), 0); assertEq(rln.stakedAmounts(idCommitment), 0);
assertEq(rln.members(idCommitment), 0); assertEq(rln.members(idCommitment), 0);

26
test/Verifier.sol Normal file
View File

@ -0,0 +1,26 @@
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.15;
import {IVerifier} from "../contracts/IVerifier.sol";
contract TrueVerifier is IVerifier {
function verifyProof(
uint256[2] memory a,
uint256[2][2] memory b,
uint256[2] memory c,
uint256[2] memory input
) external view returns (bool) {
return true;
}
}
contract FalseVerifier is IVerifier {
function verifyProof(
uint256[2] memory a,
uint256[2][2] memory b,
uint256[2] memory c,
uint256[2] memory input
) external view returns (bool) {
return false;
}
}

View File

@ -34,9 +34,7 @@ describe("RLN", () => {
const price = await rln.MEMBERSHIP_DEPOSIT(); const price = await rln.MEMBERSHIP_DEPOSIT();
// A valid pair of (id_secret, id_commitment) generated in rust // A valid id_commitment generated in zerokit
const idSecret =
"0x2a09a9fd93c590c26b91effbb2499f07e8f7aa12e2b4940a3aed2411cb65e11c";
const idCommitment = const idCommitment =
"0x0c3ac305f6a4fe9bfeb3eba978bc876e2a99208b8b56c80160cfb54ba8f02368"; "0x0c3ac305f6a4fe9bfeb3eba978bc876e2a99208b8b56c80160cfb54ba8f02368";
@ -47,20 +45,13 @@ describe("RLN", () => {
// We slash the id_commitment // We slash the id_commitment
const receiverAddress = "0x000000000000000000000000000000000000dead"; const receiverAddress = "0x000000000000000000000000000000000000dead";
const slashTx = await rln["slash(uint256,address)"]( const slashTx = rln["slash(uint256,address,uint256[8])"](
idSecret, idCommitment,
receiverAddress receiverAddress,
[0, 0, 0, 0, 0, 0, 0, 0]
); );
const slashTxReceipt = await slashTx.wait(); await expect(slashTx).to.be.revertedWith("InvalidProof()");
const slashedIdCommitment = slashTxReceipt.events[0].args.idCommitment;
// We ensure the registered id_commitment is the one we passed and that the index is the same
expect(
slashedIdCommitment.toHexString() === idCommitment,
"slashed commitment doesn't match registered commitment"
);
}); });
it("should not allow multiple registrations with same pubkey", async () => { it("should not allow multiple registrations with same pubkey", async () => {