chore(KarmaRLN): copy contracts from Rate-Limiting-Nullifier/rln-contract

This commit is contained in:
Ricardo Guilherme Schmidt
2025-05-21 06:47:19 -03:00
parent 1304846f2c
commit 20d72cbcb2
3 changed files with 393 additions and 0 deletions

11
src/rln/IVerifier.sol Normal file
View File

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

205
src/rln/KarmaRLN.sol Normal file
View File

@@ -0,0 +1,205 @@
// SPDX-License-Identifier: Apache-2.0 OR MIT
pragma solidity 0.8.26;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IVerifier} from "./IVerifier.sol";
/// @title Rate-Limiting Nullifier registry contract
/// @dev This contract allows you to register RLN commitment and withdraw/slash.
contract RLN {
using SafeERC20 for IERC20;
/// @dev User metadata struct.
/// @param userAddress: address of depositor;
/// @param messageLimit: user's message limit (stakeAmount / MINIMAL_DEPOSIT).
struct User {
address userAddress;
uint256 messageLimit;
uint256 index;
}
/// @dev Withdrawal time-lock struct
/// @param blockNumber: number of block when a withdraw was initialized;
/// @param messageLimit: amount of tokens to freeze/release;
/// @param receiver: address of receiver.
struct Withdrawal {
uint256 blockNumber;
uint256 amount;
address receiver;
}
/// @dev Minimal membership deposit (stake amount) value - cost of 1 message.
uint256 public immutable MINIMAL_DEPOSIT;
/// @dev Maximal rate.
uint256 public immutable MAXIMAL_RATE;
/// @dev Registry set size (1 << DEPTH).
uint256 public immutable SET_SIZE;
/// @dev Address of the fee receiver.
address public immutable FEE_RECEIVER;
/// @dev Fee percentage.
uint8 public immutable FEE_PERCENTAGE;
/// @dev Freeze period - number of blocks for which the withdrawal of money is frozen.
uint256 public immutable FREEZE_PERIOD;
/// @dev Current index where identityCommitment will be stored.
uint256 public identityCommitmentIndex;
/// @dev Registry set. The keys are `identityCommitment`s.
/// The values are addresses of accounts that call `register` transaction.
mapping(uint256 => User) public members;
/// @dev Withdrawals logic.
mapping(uint256 => Withdrawal) public withdrawals;
/// @dev ERC20 Token used for staking.
IERC20 public immutable token;
/// @dev Groth16 verifier.
IVerifier public immutable verifier;
/// @dev Emmited when a new member registered.
/// @param identityCommitment: `identityCommitment`;
/// @param messageLimit: user's message limit;
/// @param index: idCommitmentIndex value.
event MemberRegistered(uint256 identityCommitment, uint256 messageLimit, uint256 index);
/// @dev Emmited when a member was withdrawn.
/// @param index: index of `identityCommitment`;
event MemberWithdrawn(uint256 index);
/// @dev Emmited when a member was slashed.
/// @param index: index of `identityCommitment`;
/// @param slasher: address of slasher (msg.sender).
event MemberSlashed(uint256 index, address slasher);
/// @param minimalDeposit: minimal membership deposit;
/// @param maximalRate: maximal rate;
/// @param depth: depth of the merkle tree;
/// @param feePercentage: fee percentage;
/// @param feeReceiver: address of the fee receiver;
/// @param freezePeriod: amount of blocks for withdrawal time-lock;
/// @param _token: address of the ERC20 contract;
/// @param _verifier: address of the Groth16 Verifier.
constructor(
uint256 minimalDeposit,
uint256 maximalRate,
uint256 depth,
uint8 feePercentage,
address feeReceiver,
uint256 freezePeriod,
address _token,
address _verifier
) {
require(feeReceiver != address(0), "RLN, constructor: fee receiver cannot be 0x0 address");
MINIMAL_DEPOSIT = minimalDeposit;
MAXIMAL_RATE = maximalRate;
SET_SIZE = 1 << depth;
FEE_PERCENTAGE = feePercentage;
FEE_RECEIVER = feeReceiver;
FREEZE_PERIOD = freezePeriod;
token = IERC20(_token);
verifier = IVerifier(_verifier);
}
/// @dev Adds `identityCommitment` to the registry set and takes the necessary stake amount.
///
/// NOTE: The set must not be full.
///
/// @param identityCommitment: `identityCommitment`;
/// @param amount: stake amount.
function register(uint256 identityCommitment, uint256 amount) external {
uint256 index = identityCommitmentIndex;
require(index < SET_SIZE, "RLN, register: set is full");
require(amount >= MINIMAL_DEPOSIT, "RLN, register: amount is lower than minimal deposit");
require(amount % MINIMAL_DEPOSIT == 0, "RLN, register: amount should be a multiple of minimal deposit");
require(members[identityCommitment].userAddress == address(0), "RLN, register: idCommitment already registered");
uint256 messageLimit = amount / MINIMAL_DEPOSIT;
require(messageLimit <= MAXIMAL_RATE, "RLN, register: message limit cannot be more than MAXIMAL_RATE");
token.safeTransferFrom(msg.sender, address(this), amount);
members[identityCommitment] = User(msg.sender, messageLimit, index);
emit MemberRegistered(identityCommitment, messageLimit, index);
unchecked {
identityCommitmentIndex = index + 1;
}
}
/// @dev Request for withdraw and freeze the stake to prevent self-slashing. Stake can be
/// released after FREEZE_PERIOD blocks.
/// @param identityCommitment: `identityCommitment`;
/// @param proof: snarkjs's format generated proof (without public inputs) packed consequently.
function withdraw(uint256 identityCommitment, uint256[8] calldata proof) external {
User memory member = members[identityCommitment];
require(member.userAddress != address(0), "RLN, withdraw: member doesn't exist");
require(withdrawals[identityCommitment].blockNumber == 0, "RLN, release: such withdrawal exists");
require(_verifyProof(identityCommitment, member.userAddress, proof), "RLN, withdraw: invalid proof");
uint256 withdrawAmount = member.messageLimit * MINIMAL_DEPOSIT;
withdrawals[identityCommitment] = Withdrawal(block.number, withdrawAmount, member.userAddress);
emit MemberWithdrawn(member.index);
}
/// @dev Releases stake amount.
/// @param identityCommitment: `identityCommitment` of withdrawn user.
function release(uint256 identityCommitment) external {
Withdrawal memory withdrawal = withdrawals[identityCommitment];
require(withdrawal.blockNumber != 0, "RLN, release: no such withdrawals");
require(block.number - withdrawal.blockNumber > FREEZE_PERIOD, "RLN, release: cannot release yet");
delete withdrawals[identityCommitment];
delete members[identityCommitment];
token.safeTransfer(withdrawal.receiver, withdrawal.amount);
}
/// @dev Slashes identity with identityCommitment.
/// @param identityCommitment: `identityCommitment`;
/// @param receiver: stake receiver;
/// @param proof: snarkjs's format generated proof (without public inputs) packed consequently.
function slash(uint256 identityCommitment, address receiver, uint256[8] calldata proof) external {
require(receiver != address(0), "RLN, slash: empty receiver address");
User memory member = members[identityCommitment];
require(member.userAddress != address(0), "RLN, slash: member doesn't exist");
require(member.userAddress != receiver, "RLN, slash: self-slashing is prohibited");
require(_verifyProof(identityCommitment, receiver, proof), "RLN, slash: invalid proof");
delete members[identityCommitment];
delete withdrawals[identityCommitment];
uint256 withdrawAmount = member.messageLimit * MINIMAL_DEPOSIT;
uint256 feeAmount = (FEE_PERCENTAGE * withdrawAmount) / 100;
token.safeTransfer(receiver, withdrawAmount - feeAmount);
token.safeTransfer(FEE_RECEIVER, feeAmount);
emit MemberSlashed(member.index, receiver);
}
/// @dev Groth16 proof verification
function _verifyProof(uint256 identityCommitment, 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]],
[identityCommitment, uint256(uint160(receiver))]
);
}
}

177
src/rln/Verifier.sol Normal file
View File

@@ -0,0 +1,177 @@
// SPDX-License-Identifier: GPL-3.0
/*
Copyright 2021 0KIMS association.
This file is generated with [snarkJS](https://github.com/iden3/snarkjs).
snarkJS is a free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
snarkJS is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with snarkJS. If not, see <https://www.gnu.org/licenses/>.
*/
pragma solidity 0.8.26;
contract Groth16Verifier {
// Scalar field size
uint256 constant r = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
// Base field size
uint256 constant q = 21888242871839275222246405745257275088696311157297823662689037894645226208583;
// Verification Key data
uint256 constant alphax = 20491192805390485299153009773594534940189261866228447918068658471970481763042;
uint256 constant alphay = 9383485363053290200918347156157836566562967994039712273449902621266178545958;
uint256 constant betax1 = 4252822878758300859123897981450591353533073413197771768651442665752259397132;
uint256 constant betax2 = 6375614351688725206403948262868962793625744043794305715222011528459656738731;
uint256 constant betay1 = 21847035105528745403288232691147584728191162732299865338377159692350059136679;
uint256 constant betay2 = 10505242626370262277552901082094356697409835680220590971873171140371331206856;
uint256 constant gammax1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634;
uint256 constant gammax2 = 10857046999023057135944570762232829481370756359578518086990519993285655852781;
uint256 constant gammay1 = 4082367875863433681332203403145435568316851327593401208105741076214120093531;
uint256 constant gammay2 = 8495653923123431417604973247489272438418190587263600148770280649306958101930;
uint256 constant deltax1 = 11551021181461167826461759140322976337427023987127500996455457136635486400849;
uint256 constant deltax2 = 16440331697014556916876897072677669680121386347171004539353252116876383229648;
uint256 constant deltay1 = 16935666883311644237478257793713075046951222500094443222621864730929684383196;
uint256 constant deltay2 = 15542122065551809636611024015159689732218122471819623718488423844152861811271;
uint256 constant IC0x = 19490069286251317200471893224761952280235157078692599655063040494106083015102;
uint256 constant IC0y = 15613730057977833735664106983317680013118142165231654768046521650638333652991;
uint256 constant IC1x = 1563543155852853229359605494188815884199915022658219002707722789976065966419;
uint256 constant IC1y = 858819375930654753672617171465307097688802650498051619587167586479724200799;
uint256 constant IC2x = 3808889614445935800597561392085733302718838702771107544944545050886958022904;
uint256 constant IC2y = 13293649293049947010793838294353767499934999769633605908974566715226392122400;
// Memory data
uint16 constant pVk = 0;
uint16 constant pPairing = 128;
uint16 constant pLastMem = 896;
function verifyProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[2] calldata _pubSignals) public view returns (bool) {
assembly {
function checkField(v) {
if iszero(lt(v, q)) {
mstore(0, 0)
return(0, 0x20)
}
}
// G1 function to multiply a G1 value(x,y) to value in an address
function g1_mulAccC(pR, x, y, s) {
let success
let mIn := mload(0x40)
mstore(mIn, x)
mstore(add(mIn, 32), y)
mstore(add(mIn, 64), s)
success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64)
if iszero(success) {
mstore(0, 0)
return(0, 0x20)
}
mstore(add(mIn, 64), mload(pR))
mstore(add(mIn, 96), mload(add(pR, 32)))
success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64)
if iszero(success) {
mstore(0, 0)
return(0, 0x20)
}
}
function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk {
let _pPairing := add(pMem, pPairing)
let _pVk := add(pMem, pVk)
mstore(_pVk, IC0x)
mstore(add(_pVk, 32), IC0y)
// Compute the linear combination vk_x
g1_mulAccC(_pVk, IC1x, IC1y, calldataload(add(pubSignals, 0)))
g1_mulAccC(_pVk, IC2x, IC2y, calldataload(add(pubSignals, 32)))
// -A
mstore(_pPairing, calldataload(pA))
mstore(add(_pPairing, 32), mod(sub(q, calldataload(add(pA, 32))), q))
// B
mstore(add(_pPairing, 64), calldataload(pB))
mstore(add(_pPairing, 96), calldataload(add(pB, 32)))
mstore(add(_pPairing, 128), calldataload(add(pB, 64)))
mstore(add(_pPairing, 160), calldataload(add(pB, 96)))
// alpha1
mstore(add(_pPairing, 192), alphax)
mstore(add(_pPairing, 224), alphay)
// beta2
mstore(add(_pPairing, 256), betax1)
mstore(add(_pPairing, 288), betax2)
mstore(add(_pPairing, 320), betay1)
mstore(add(_pPairing, 352), betay2)
// vk_x
mstore(add(_pPairing, 384), mload(add(pMem, pVk)))
mstore(add(_pPairing, 416), mload(add(pMem, add(pVk, 32))))
// gamma2
mstore(add(_pPairing, 448), gammax1)
mstore(add(_pPairing, 480), gammax2)
mstore(add(_pPairing, 512), gammay1)
mstore(add(_pPairing, 544), gammay2)
// C
mstore(add(_pPairing, 576), calldataload(pC))
mstore(add(_pPairing, 608), calldataload(add(pC, 32)))
// delta2
mstore(add(_pPairing, 640), deltax1)
mstore(add(_pPairing, 672), deltax2)
mstore(add(_pPairing, 704), deltay1)
mstore(add(_pPairing, 736), deltay2)
let success := staticcall(sub(gas(), 2000), 8, _pPairing, 768, _pPairing, 0x20)
isOk := and(success, mload(_pPairing))
}
let pMem := mload(0x40)
mstore(0x40, add(pMem, pLastMem))
// Validate that all evaluations ∈ F
checkField(calldataload(add(_pubSignals, 0)))
checkField(calldataload(add(_pubSignals, 32)))
checkField(calldataload(add(_pubSignals, 64)))
// Validate all evaluations
let isValid := checkPairing(_pA, _pB, _pC, _pubSignals, pMem)
mstore(0, isValid)
return(0, 0x20)
}
}
}