mirror of
https://github.com/vacp2p/staking-reward-streamer.git
synced 2026-01-08 20:48:00 -05:00
chore(KarmaRLN): copy contracts from Rate-Limiting-Nullifier/rln-contract
This commit is contained in:
11
src/rln/IVerifier.sol
Normal file
11
src/rln/IVerifier.sol
Normal 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
205
src/rln/KarmaRLN.sol
Normal 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
177
src/rln/Verifier.sol
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user