chore: create verifier and integrate
This commit is contained in:
parent
18783ba67e
commit
fc606d98b2
|
@ -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);
|
||||
}
|
|
@ -3,6 +3,10 @@
|
|||
pragma solidity 0.8.15;
|
||||
|
||||
import {IPoseidonHasher} from "./PoseidonHasher.sol";
|
||||
import {IVerifier} from "./IVerifier.sol";
|
||||
|
||||
import "forge-std/console.sol";
|
||||
|
||||
|
||||
/// The tree is full
|
||||
error FullTree();
|
||||
|
@ -30,6 +34,9 @@ error InsufficientWithdrawalBalance();
|
|||
/// Contract has insufficient balance to return
|
||||
error InsufficientContractBalance();
|
||||
|
||||
/// Invalid proof
|
||||
error InvalidProof();
|
||||
|
||||
contract RLN {
|
||||
/// @notice The deposit amount required to register as a member
|
||||
uint256 public immutable MEMBERSHIP_DEPOSIT;
|
||||
|
@ -57,6 +64,9 @@ contract RLN {
|
|||
/// @notice The Poseidon hasher contract
|
||||
IPoseidonHasher public immutable poseidonHasher;
|
||||
|
||||
/// @notice The groth16 verifier contract
|
||||
IVerifier public immutable verifier;
|
||||
|
||||
/// Emitted when a new member is added to the set
|
||||
/// @param idCommitment The idCommitment of the member
|
||||
/// @param index The index of the member in the set
|
||||
|
@ -70,12 +80,14 @@ contract RLN {
|
|||
constructor(
|
||||
uint256 membershipDeposit,
|
||||
uint256 depth,
|
||||
address _poseidonHasher
|
||||
address _poseidonHasher,
|
||||
address _verifier
|
||||
) {
|
||||
MEMBERSHIP_DEPOSIT = membershipDeposit;
|
||||
DEPTH = depth;
|
||||
SET_SIZE = 1 << depth;
|
||||
poseidonHasher = IPoseidonHasher(_poseidonHasher);
|
||||
verifier = IVerifier(_verifier);
|
||||
}
|
||||
|
||||
/// Allows a user to register as a member
|
||||
|
@ -100,27 +112,28 @@ contract RLN {
|
|||
idCommitmentIndex += 1;
|
||||
}
|
||||
|
||||
/// Allows a user to slash a member
|
||||
/// @param secret The idSecretHash of the member
|
||||
function slash(uint256 secret, address payable receiver) external {
|
||||
_slash(secret, receiver);
|
||||
/// @dev Allows a user to slash a member
|
||||
/// @param idCommitment The idCommitment of the member
|
||||
function slash(uint256 idCommitment, address payable receiver, uint256[8] calldata proof) external {
|
||||
_slash(idCommitment, receiver, proof);
|
||||
}
|
||||
|
||||
/// Slashes a member by removing them from the set, and transferring their
|
||||
/// stake to the receiver
|
||||
/// @param secret The idSecretHash of the member
|
||||
/// @dev Slashes a member by removing them from the set, and adding their
|
||||
/// stake to the receiver's available withdrawal balance
|
||||
/// @param idCommitment The idCommitment of the member
|
||||
/// @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))
|
||||
revert InvalidReceiverAddress(receiver);
|
||||
|
||||
// derive idCommitment
|
||||
uint256 idCommitment = hash(secret);
|
||||
// check if member is registered
|
||||
if (members[idCommitment] == 0) revert MemberNotRegistered(idCommitment);
|
||||
// check if member is registered
|
||||
if (stakedAmounts[idCommitment] == 0)
|
||||
revert MemberHasNoStake(idCommitment);
|
||||
|
||||
if(!_verifyProof(idCommitment, receiver, proof))
|
||||
revert InvalidProof();
|
||||
|
||||
uint256 amountToTransfer = stakedAmounts[idCommitment];
|
||||
|
||||
// delete member
|
||||
|
@ -153,4 +166,18 @@ contract RLN {
|
|||
function hash(uint256 input) internal view returns (uint256) {
|
||||
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))]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"];
|
|
@ -9,13 +9,14 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
|
|||
|
||||
const poseidonHasherAddress = (await deployments.get("PoseidonHasher"))
|
||||
.address;
|
||||
const rlnVerifierAddress = (await deployments.get("Verifier")).address;
|
||||
|
||||
await deploy("RLN", {
|
||||
from: deployer,
|
||||
log: true,
|
||||
args: [1000000000000000, 20, poseidonHasherAddress],
|
||||
args: [1000000000000000, 20, poseidonHasherAddress, rlnVerifierAddress],
|
||||
});
|
||||
};
|
||||
export default func;
|
||||
func.tags = ["RLN"];
|
||||
func.dependencies = ["PoseidonHasher"];
|
||||
func.dependencies = ["PoseidonHasher", "RlnVerifier"];
|
218
docs/index.md
218
docs/index.md
|
@ -1,5 +1,177 @@
|
|||
# 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
|
||||
|
||||
### hash
|
||||
|
@ -907,6 +1079,14 @@ error InsufficientContractBalance()
|
|||
|
||||
Contract has insufficient balance to return
|
||||
|
||||
## InvalidProof
|
||||
|
||||
```solidity
|
||||
error InvalidProof()
|
||||
```
|
||||
|
||||
Invalid proof
|
||||
|
||||
## RLN
|
||||
|
||||
### MEMBERSHIP_DEPOSIT
|
||||
|
@ -975,6 +1155,14 @@ contract IPoseidonHasher poseidonHasher
|
|||
|
||||
The Poseidon hasher contract
|
||||
|
||||
### verifier
|
||||
|
||||
```solidity
|
||||
contract IVerifier verifier
|
||||
```
|
||||
|
||||
The groth16 verifier contract
|
||||
|
||||
### MemberRegistered
|
||||
|
||||
```solidity
|
||||
|
@ -1008,7 +1196,7 @@ Emitted when a member is removed from the set
|
|||
### constructor
|
||||
|
||||
```solidity
|
||||
constructor(uint256 membershipDeposit, uint256 depth, address _poseidonHasher) public
|
||||
constructor(uint256 membershipDeposit, uint256 depth, address _poseidonHasher, address _verifier) public
|
||||
```
|
||||
|
||||
### register
|
||||
|
@ -1043,33 +1231,35 @@ Registers a member
|
|||
### slash
|
||||
|
||||
```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
|
||||
|
||||
| Name | Type | Description |
|
||||
| -------- | --------------- | ------------------------------ |
|
||||
| secret | uint256 | The idSecretHash of the member |
|
||||
| ------------ | --------------- | ------------------------------ |
|
||||
| idCommitment | uint256 | The idCommitment of the member |
|
||||
| receiver | address payable | |
|
||||
| proof | uint256[8] | |
|
||||
|
||||
### \_slash
|
||||
|
||||
```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
|
||||
stake to the receiver
|
||||
_Slashes a member by removing them from the set, and adding their
|
||||
stake to the receiver's available withdrawal balance_
|
||||
|
||||
#### Parameters
|
||||
|
||||
| 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 |
|
||||
| proof | uint256[8] | |
|
||||
|
||||
### 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 |
|
||||
| ----- | ------- | ----------------- |
|
||||
| input | uint256 | The value to hash |
|
||||
|
||||
### \_verifyProof
|
||||
|
||||
```solidity
|
||||
function _verifyProof(uint256 idCommitment, address receiver, uint256[8] proof) internal view returns (bool)
|
||||
```
|
||||
|
||||
_Groth16 proof verification_
|
||||
|
|
|
@ -47,6 +47,9 @@ const config: HardhatUserConfig = {
|
|||
{
|
||||
version: "0.8.15",
|
||||
},
|
||||
{
|
||||
version: "0.6.11",
|
||||
},
|
||||
],
|
||||
},
|
||||
networks: getNetworkConfig(),
|
||||
|
|
|
@ -3,24 +3,32 @@ pragma solidity ^0.8.15;
|
|||
|
||||
import "../contracts/PoseidonHasher.sol";
|
||||
import "../contracts/Rln.sol";
|
||||
import "./Verifier.sol";
|
||||
import "forge-std/Test.sol";
|
||||
import "forge-std/StdCheats.sol";
|
||||
import "forge-std/console.sol";
|
||||
|
||||
|
||||
contract RLNTest is Test {
|
||||
using stdStorage for StdStorage;
|
||||
|
||||
RLN public rln;
|
||||
PoseidonHasher public poseidon;
|
||||
TrueVerifier public trueVerifier;
|
||||
FalseVerifier public falseVerifier;
|
||||
|
||||
uint256 public constant MEMBERSHIP_DEPOSIT = 1000000000000000;
|
||||
uint256 public constant DEPTH = 20;
|
||||
uint256 public constant SET_SIZE = 1048576;
|
||||
uint256[8] public zeroedProof = [0, 0, 0, 0, 0, 0, 0, 0];
|
||||
|
||||
|
||||
/// @dev Setup the testing environment.
|
||||
function setUp() public {
|
||||
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.
|
||||
|
@ -67,7 +75,8 @@ contract RLNTest is Test {
|
|||
RLN tempRln = new RLN(
|
||||
MEMBERSHIP_DEPOSIT,
|
||||
2,
|
||||
address(rln.poseidonHasher())
|
||||
address(rln.poseidonHasher()),
|
||||
address(rln.verifier())
|
||||
);
|
||||
uint256 setSize = tempRln.SET_SIZE() - 1;
|
||||
for (uint256 i = 0; i < setSize; i++) {
|
||||
|
@ -78,19 +87,18 @@ contract RLNTest is Test {
|
|||
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
|
||||
// TODO: wrap both of these in a single function
|
||||
assumePayable(to);
|
||||
assumeNoPrecompiles(to);
|
||||
vm.assume(to != address(0));
|
||||
uint256 idCommitment = poseidon.hash(idSecretHash);
|
||||
|
||||
rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment);
|
||||
assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT);
|
||||
|
||||
uint256 balanceBefore = to.balance;
|
||||
rln.slash(idSecretHash, to);
|
||||
rln.slash(idCommitment, to, zeroedProof);
|
||||
vm.prank(to);
|
||||
rln.withdraw();
|
||||
assertEq(rln.stakedAmounts(idCommitment), 0);
|
||||
|
@ -99,19 +107,18 @@ contract RLNTest is Test {
|
|||
}
|
||||
|
||||
function test__InvalidSlash__ToZeroAddress() public {
|
||||
uint256 idSecretHash = 19014214495641488759237505126948346942972912379615652741039992445865937985820;
|
||||
uint256 idCommitment = poseidon.hash(idSecretHash);
|
||||
uint256 idCommitment = 9014214495641488759237505126948346942972912379615652741039992445865937985820;
|
||||
|
||||
rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment);
|
||||
assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT);
|
||||
vm.expectRevert(
|
||||
abi.encodeWithSelector(InvalidReceiverAddress.selector, address(0))
|
||||
);
|
||||
rln.slash(idSecretHash, payable(address(0)));
|
||||
rln.slash(idCommitment, payable(address(0)), zeroedProof);
|
||||
}
|
||||
|
||||
function test__InvalidSlash__ToRlnAddress() public {
|
||||
uint256 idSecretHash = 19014214495641488759237505126948346942972912379615652741039992445865937985820;
|
||||
uint256 idCommitment = poseidon.hash(idSecretHash);
|
||||
uint256 idCommitment = 19014214495641488759237505126948346942972912379615652741039992445865937985820;
|
||||
rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment);
|
||||
assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT);
|
||||
vm.expectRevert(
|
||||
|
@ -120,34 +127,32 @@ contract RLNTest is Test {
|
|||
address(rln)
|
||||
)
|
||||
);
|
||||
rln.slash(idSecretHash, payable(address(rln)));
|
||||
rln.slash(idCommitment, payable(address(rln)), zeroedProof);
|
||||
}
|
||||
|
||||
function test__InvalidSlash__InvalidIdCommitment(
|
||||
uint256 idSecretHash
|
||||
uint256 idCommitment
|
||||
) public {
|
||||
uint256 idCommitment = poseidon.hash(idSecretHash);
|
||||
vm.expectRevert(
|
||||
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
|
||||
function test__InvalidSlash__NoStake(
|
||||
uint256 idSecretHash,
|
||||
uint256 idCommitment,
|
||||
address payable to
|
||||
) public {
|
||||
// avoid precompiles, etc
|
||||
assumePayable(to);
|
||||
assumeNoPrecompiles(to);
|
||||
vm.assume(to != address(0));
|
||||
uint256 idCommitment = poseidon.hash(idSecretHash);
|
||||
|
||||
rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment);
|
||||
assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT);
|
||||
|
||||
rln.slash(idSecretHash, to);
|
||||
rln.slash(idCommitment, to, zeroedProof);
|
||||
assertEq(rln.stakedAmounts(idCommitment), 0);
|
||||
assertEq(rln.members(idCommitment), 0);
|
||||
|
||||
|
@ -162,7 +167,23 @@ contract RLNTest is Test {
|
|||
vm.expectRevert(
|
||||
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 {
|
||||
|
@ -171,11 +192,10 @@ contract RLNTest is Test {
|
|||
}
|
||||
|
||||
function test__InvalidWithdraw__InsufficientContractBalance() public {
|
||||
uint256 idSecretHash = 19014214495641488759237505126948346942972912379615652741039992445865937985820;
|
||||
uint256 idCommitment = poseidon.hash(idSecretHash);
|
||||
uint256 idCommitment = 19014214495641488759237505126948346942972912379615652741039992445865937985820;
|
||||
rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment);
|
||||
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.members(idCommitment), 0);
|
||||
|
||||
|
@ -188,12 +208,11 @@ contract RLNTest is Test {
|
|||
assumePayable(to);
|
||||
assumeNoPrecompiles(to);
|
||||
|
||||
uint256 idSecretHash = 19014214495641488759237505126948346942972912379615652741039992445865937985820;
|
||||
uint256 idCommitment = poseidon.hash(idSecretHash);
|
||||
uint256 idCommitment = 19014214495641488759237505126948346942972912379615652741039992445865937985820;
|
||||
|
||||
rln.register{value: MEMBERSHIP_DEPOSIT}(idCommitment);
|
||||
assertEq(rln.stakedAmounts(idCommitment), MEMBERSHIP_DEPOSIT);
|
||||
rln.slash(idSecretHash, to);
|
||||
rln.slash(idCommitment, to, zeroedProof);
|
||||
assertEq(rln.stakedAmounts(idCommitment), 0);
|
||||
assertEq(rln.members(idCommitment), 0);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -34,9 +34,7 @@ describe("RLN", () => {
|
|||
|
||||
const price = await rln.MEMBERSHIP_DEPOSIT();
|
||||
|
||||
// A valid pair of (id_secret, id_commitment) generated in rust
|
||||
const idSecret =
|
||||
"0x2a09a9fd93c590c26b91effbb2499f07e8f7aa12e2b4940a3aed2411cb65e11c";
|
||||
// A valid id_commitment generated in zerokit
|
||||
const idCommitment =
|
||||
"0x0c3ac305f6a4fe9bfeb3eba978bc876e2a99208b8b56c80160cfb54ba8f02368";
|
||||
|
||||
|
@ -47,20 +45,13 @@ describe("RLN", () => {
|
|||
|
||||
// We slash the id_commitment
|
||||
const receiverAddress = "0x000000000000000000000000000000000000dead";
|
||||
const slashTx = await rln["slash(uint256,address)"](
|
||||
idSecret,
|
||||
receiverAddress
|
||||
const slashTx = rln["slash(uint256,address,uint256[8])"](
|
||||
idCommitment,
|
||||
receiverAddress,
|
||||
[0, 0, 0, 0, 0, 0, 0, 0]
|
||||
);
|
||||
|
||||
const slashTxReceipt = await slashTx.wait();
|
||||
|
||||
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"
|
||||
);
|
||||
await expect(slashTx).to.be.revertedWith("InvalidProof()");
|
||||
});
|
||||
|
||||
it("should not allow multiple registrations with same pubkey", async () => {
|
||||
|
|
Loading…
Reference in New Issue