@cryptkeeperzk/rlnjs
Version:
Client library for generating and using RLN ZK proofs, is a forked repo from main Rate-Limiting-Nullifier/rlnjs to make it work with Cryptkeeper Browser Extension using `@cryptkeeperzk/snarkjs` and `@cryptkeeperzk/ffjavascript`
895 lines (881 loc) • 85 kB
JavaScript
/**
* @module @cryptkeeperzk/rlnjs
* @version 3.2.1
* @file Client library for generating and using RLN ZK proofs, is a forked repo from main Rate-Limiting-Nullifier/rlnjs to make it work with Cryptkeeper Browser Extension using `@cryptkeeperzk/snarkjs` and `@cryptkeeperzk/ffjavascript`
* @copyright Ethereum Foundation 2022
* @license MIT
* @see [Github]{@link https://github.com/Rate-Limiting-Nullifier/rlnjs}
*/
'use strict';
var identity = require('@semaphore-protocol/identity');
var bytes = require('@ethersproject/bytes');
var strings = require('@ethersproject/strings');
var keccak256 = require('@ethersproject/keccak256');
var ffjavascript = require('@cryptkeeperzk/ffjavascript');
var poseidon = require('poseidon-lite');
var group = require('@semaphore-protocol/group');
var ethers = require('ethers');
var snarkjs = require('@cryptkeeperzk/snarkjs');
var axios = require('axios');
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
function __generator(thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
}
/*
This is the "Baby Jubjub" curve described here:
https://iden3-docs.readthedocs.io/en/latest/_downloads/33717d75ab84e11313cc0d8a090b636f/Baby-Jubjub.pdf
*/
var SNARK_FIELD_SIZE = BigInt('21888242871839275222246405745257275088548364400416034343698204186575808495617');
// Creates the finite field
var Fq = new ffjavascript.ZqField(SNARK_FIELD_SIZE);
var DEFAULT_MERKLE_TREE_DEPTH = 20;
function calculateIdentitySecret(identity) {
return poseidon([
identity.getNullifier(),
identity.getTrapdoor(),
]);
}
function calculateExternalNullifier(epoch, rlnIdentifier) {
return poseidon([epoch, rlnIdentifier]);
}
function calculateRateCommitment(identityCommitment, userMessageLimit) {
return poseidon([identityCommitment, userMessageLimit]);
}
/**
* Hashes a signal string with Keccak256.
* @param signal The RLN signal.
* @returns The signal hash.
*/
function calculateSignalHash(signal) {
var converted = bytes.hexlify(strings.toUtf8Bytes(signal));
return BigInt(keccak256.keccak256(converted)) >> BigInt(8);
}
/**
* Recovers secret from two shares
* @param x1 signal hash of first message
* @param x2 signal hash of second message
* @param y1 yshare of first message
* @param y2 yshare of second message
* @returns identity secret
*/
function shamirRecovery(x1, x2, y1, y2) {
var slope = Fq.div(Fq.sub(y2, y1), Fq.sub(x2, x1));
var privateKey = Fq.sub(y1, Fq.mul(slope, x1));
return Fq.normalize(privateKey);
}
/**
* Wrapper of RLN circuit for proof generation.
*/
var RLNProver = /** @class */ (function () {
function RLNProver(wasmFilePath, finalZkeyPath) {
this.wasmFilePath = wasmFilePath;
this.finalZkeyPath = finalZkeyPath;
}
/**
* Generates a RLN full proof.
* @param args The parameters for creating the proof.
* @returns The full SnarkJS proof.
*/
RLNProver.prototype.generateProof = function (args) {
return __awaiter(this, void 0, void 0, function () {
var witness, _a, proof, publicSignals, snarkProof;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
witness = {
identitySecret: args.identitySecret,
userMessageLimit: args.userMessageLimit,
messageId: args.messageId,
pathElements: args.merkleProof.siblings,
identityPathIndex: args.merkleProof.pathIndices,
x: args.x,
externalNullifier: calculateExternalNullifier(args.epoch, args.rlnIdentifier),
};
return [4 /*yield*/, snarkjs.groth16.fullProve(witness, this.wasmFilePath, this.finalZkeyPath, null)];
case 1:
_a = _b.sent(), proof = _a.proof, publicSignals = _a.publicSignals;
snarkProof = {
proof: proof,
publicSignals: {
y: publicSignals[0],
root: publicSignals[1],
nullifier: publicSignals[2],
x: publicSignals[3],
externalNullifier: publicSignals[4],
},
};
return [2 /*return*/, {
snarkProof: snarkProof,
epoch: args.epoch,
rlnIdentifier: args.rlnIdentifier,
}];
}
});
});
};
return RLNProver;
}());
/**
* Wrapper of RLN circuit for verification.
*/
var RLNVerifier = /** @class */ (function () {
function RLNVerifier(verificationKey) {
this.verificationKey = verificationKey;
}
/**
* Verifies a RLN full proof.
* @param rlnIdentifier unique identifier for a RLN app.
* @param fullProof The SnarkJS full proof.
* @returns True if the proof is valid, false otherwise.
* @throws Error if the proof is using different parameters.
*/
RLNVerifier.prototype.verifyProof = function (rlnIdentifier, rlnRullProof) {
return __awaiter(this, void 0, void 0, function () {
var expectedExternalNullifier, actualExternalNullifier, _a, proof, publicSignals;
return __generator(this, function (_b) {
expectedExternalNullifier = calculateExternalNullifier(BigInt(rlnRullProof.epoch), rlnIdentifier);
actualExternalNullifier = rlnRullProof.snarkProof.publicSignals.externalNullifier;
if (expectedExternalNullifier !== BigInt(actualExternalNullifier)) {
throw new Error("External nullifier does not match: expectedExternalNullifier=".concat(expectedExternalNullifier, ", ") +
"actualExternalNullifier=".concat(actualExternalNullifier, ", epoch=").concat(rlnRullProof.epoch, ", ") +
"this.rlnIdentifier=".concat(rlnIdentifier));
}
_a = rlnRullProof.snarkProof, proof = _a.proof, publicSignals = _a.publicSignals;
return [2 /*return*/, snarkjs.groth16.verify(this.verificationKey, [
publicSignals.y,
publicSignals.root,
publicSignals.nullifier,
publicSignals.x,
publicSignals.externalNullifier,
], proof)];
});
});
};
return RLNVerifier;
}());
/**
* Wrapper of Withdraw circuit for proof generation.
*/
var WithdrawProver = /** @class */ (function () {
function WithdrawProver(wasmFilePath, finalZkeyPath) {
this.wasmFilePath = wasmFilePath;
this.finalZkeyPath = finalZkeyPath;
}
WithdrawProver.prototype.generateProof = function (args) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, snarkjs.groth16.fullProve(args, this.wasmFilePath, this.finalZkeyPath, null)];
case 1: return [2 /*return*/, (_a.sent())];
}
});
});
};
return WithdrawProver;
}());
var ContractRLNRegistry = /** @class */ (function () {
function ContractRLNRegistry(args) {
this.treeDepth = args.treeDepth ? args.treeDepth : DEFAULT_MERKLE_TREE_DEPTH;
this.rlnContract = args.rlnContract;
this.rlnIdentifier = args.rlnIdentifier;
if (args.withdrawWasmFilePath !== undefined && args.withdrawFinalZkeyPath !== undefined) {
this.withdrawProver = new WithdrawProver(args.withdrawWasmFilePath, args.withdrawFinalZkeyPath);
}
}
ContractRLNRegistry.prototype.getSignerAddress = function () {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/, this.rlnContract.getSignerAddress()];
});
});
};
ContractRLNRegistry.prototype.isRegistered = function (identityCommitment) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/, this.rlnContract.isRegistered(identityCommitment)];
});
});
};
ContractRLNRegistry.prototype.getMessageLimit = function (identityCommitment) {
return __awaiter(this, void 0, void 0, function () {
var user;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.rlnContract.getUser(identityCommitment)];
case 1:
user = _a.sent();
if (user.userAddress === ethers.ethers.ZeroAddress) {
throw new Error('Identity commitment is not registered');
}
return [2 /*return*/, user.messageLimit];
}
});
});
};
ContractRLNRegistry.prototype.getRateCommitment = function (identityCommitment) {
return __awaiter(this, void 0, void 0, function () {
var messageLimit;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.getMessageLimit(identityCommitment)];
case 1:
messageLimit = _a.sent();
return [2 /*return*/, calculateRateCommitment(identityCommitment, messageLimit)];
}
});
});
};
ContractRLNRegistry.prototype.generateLatestGroup = function () {
return __awaiter(this, void 0, void 0, function () {
var group$1, events, _i, events_1, event_1, identityCommitment, messageLimit, rateCommitment, index;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
group$1 = new group.Group(this.rlnIdentifier, this.treeDepth);
return [4 /*yield*/, this.rlnContract.getLogs()];
case 1:
events = _a.sent();
for (_i = 0, events_1 = events; _i < events_1.length; _i++) {
event_1 = events_1[_i];
if (event_1.name === 'MemberRegistered') {
identityCommitment = BigInt(event_1.identityCommitment);
messageLimit = BigInt(event_1.messageLimit);
rateCommitment = calculateRateCommitment(identityCommitment, messageLimit);
group$1.addMember(rateCommitment);
}
else if (event_1.name === 'MemberWithdrawn' || event_1.name === 'MemberSlashed') {
index = event_1.index;
group$1.removeMember(Number(index));
}
}
return [2 /*return*/, group$1];
}
});
});
};
ContractRLNRegistry.prototype.getAllRateCommitments = function () {
return __awaiter(this, void 0, void 0, function () {
var group;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.generateLatestGroup()];
case 1:
group = _a.sent();
return [2 /*return*/, group.members.map(function (member) { return BigInt(member); })];
}
});
});
};
ContractRLNRegistry.prototype.getMerkleRoot = function () {
return __awaiter(this, void 0, void 0, function () {
var group;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.generateLatestGroup()];
case 1:
group = _a.sent();
return [2 /*return*/, BigInt(group.root)];
}
});
});
};
/**
* Creates a Merkle Proof.
* @param identityCommitment The leaf for which Merkle proof should be created.
* @returns The Merkle proof.
*/
ContractRLNRegistry.prototype.generateMerkleProof = function (identityCommitment) {
return __awaiter(this, void 0, void 0, function () {
var group, user, rateCommitment, index;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.generateLatestGroup()];
case 1:
group = _a.sent();
return [4 /*yield*/, this.rlnContract.getUser(identityCommitment)];
case 2:
user = _a.sent();
if (user.userAddress === ethers.ethers.ZeroAddress) {
throw new Error('Identity commitment is not registered');
}
rateCommitment = calculateRateCommitment(identityCommitment, user.messageLimit);
index = group.indexOf(rateCommitment);
if (index === -1) {
// Should only happen when a user was registered before `const user = ...` and then withdraw/slashed
// after `const user = ...`.
throw new Error('Rate commitment is not in the merkle tree');
}
return [2 /*return*/, group.generateMerkleProof(index)];
}
});
});
};
ContractRLNRegistry.prototype.register = function (identityCommitment, messageLimit) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.isRegistered(identityCommitment)];
case 1:
if (_a.sent()) {
throw new Error('Identity commitment is already registered');
}
return [4 /*yield*/, this.rlnContract.register(identityCommitment, messageLimit)];
case 2:
_a.sent();
return [2 /*return*/];
}
});
});
};
ContractRLNRegistry.prototype.withdraw = function (identitySecret) {
return __awaiter(this, void 0, void 0, function () {
var identityCommitment, user, userAddressBigInt, proof;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (this.withdrawProver === undefined) {
throw new Error('Withdraw prover is not initialized');
}
identityCommitment = poseidon([identitySecret]);
return [4 /*yield*/, this.rlnContract.getUser(identityCommitment)];
case 1:
user = _a.sent();
if (user.userAddress === ethers.ethers.ZeroAddress) {
throw new Error('Identity commitment is not registered');
}
userAddressBigInt = BigInt(user.userAddress);
return [4 /*yield*/, this.withdrawProver.generateProof({
identitySecret: identitySecret,
address: userAddressBigInt,
})];
case 2:
proof = _a.sent();
return [4 /*yield*/, this.rlnContract.withdraw(identityCommitment, proof.proof)];
case 3:
_a.sent();
return [2 /*return*/];
}
});
});
};
ContractRLNRegistry.prototype.releaseWithdrawal = function (identityCommitment) {
return __awaiter(this, void 0, void 0, function () {
var withdrawal;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.isRegistered(identityCommitment)];
case 1:
if (!(_a.sent())) {
throw new Error('Identity commitment is not registered');
}
return [4 /*yield*/, this.rlnContract.getWithdrawal(identityCommitment)];
case 2:
withdrawal = _a.sent();
if (withdrawal.blockNumber == BigInt(0)) {
throw new Error('Withdrawal is not initiated');
}
return [4 /*yield*/, this.rlnContract.release(identityCommitment)];
case 3:
_a.sent();
return [2 /*return*/];
}
});
});
};
ContractRLNRegistry.prototype.slash = function (identitySecret, receiver) {
return __awaiter(this, void 0, void 0, function () {
var identityCommitment, _a, receiverBigInt, proof;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
if (this.withdrawProver === undefined) {
throw new Error('Withdraw prover is not initialized');
}
identityCommitment = poseidon([identitySecret]);
if (!receiver) return [3 /*break*/, 1];
_a = receiver;
return [3 /*break*/, 3];
case 1: return [4 /*yield*/, this.rlnContract.getSignerAddress()];
case 2:
_a = _b.sent();
_b.label = 3;
case 3:
receiver = _a;
receiverBigInt = BigInt(receiver);
return [4 /*yield*/, this.withdrawProver.generateProof({
identitySecret: identitySecret,
address: receiverBigInt,
})];
case 4:
proof = _b.sent();
return [4 /*yield*/, this.rlnContract.slash(identityCommitment, receiver, proof.proof)];
case 5:
_b.sent();
return [2 /*return*/];
}
});
});
};
return ContractRLNRegistry;
}());
var MemoryRLNRegistry = /** @class */ (function () {
function MemoryRLNRegistry(rlnIdentifier, treeDepth) {
if (treeDepth === void 0) { treeDepth = DEFAULT_MERKLE_TREE_DEPTH; }
this.rlnIdentifier = rlnIdentifier;
this.treeDepth = treeDepth;
this.mapIsWithdrawing = new Map();
this.mapMessageLimit = new Map();
this.group = new group.Group(this.rlnIdentifier, this.treeDepth);
}
MemoryRLNRegistry.prototype.isRegistered = function (identityCommitment) {
return __awaiter(this, void 0, void 0, function () {
var messageLimit;
return __generator(this, function (_a) {
messageLimit = this.mapMessageLimit.get(identityCommitment.toString());
return [2 /*return*/, messageLimit !== undefined];
});
});
};
MemoryRLNRegistry.prototype.getMerkleRoot = function () {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/, BigInt(this.group.root)];
});
});
};
MemoryRLNRegistry.prototype.getMessageLimit = function (identityCommitment) {
return __awaiter(this, void 0, void 0, function () {
var messageLimit;
return __generator(this, function (_a) {
messageLimit = this.mapMessageLimit.get(identityCommitment.toString());
if (messageLimit === undefined) {
throw new Error('Identity commitment is not registered');
}
return [2 /*return*/, messageLimit];
});
});
};
MemoryRLNRegistry.prototype.getRateCommitment = function (identityCommitment) {
return __awaiter(this, void 0, void 0, function () {
var messageLimit;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.getMessageLimit(identityCommitment)];
case 1:
messageLimit = _a.sent();
return [2 /*return*/, calculateRateCommitment(identityCommitment, messageLimit)];
}
});
});
};
MemoryRLNRegistry.prototype.getAllRateCommitments = function () {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/, this.group.members.map(function (member) { return BigInt(member); })];
});
});
};
MemoryRLNRegistry.prototype.generateMerkleProof = function (identityCommitment) {
return __awaiter(this, void 0, void 0, function () {
var rateCommitment, index;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.getRateCommitment(identityCommitment)];
case 1:
rateCommitment = _a.sent();
index = this.group.indexOf(rateCommitment);
if (index === -1) {
// Sanity check
throw new Error('Rate commitment is not in the merkle tree. This should never happen.');
}
return [2 /*return*/, this.group.generateMerkleProof(index)];
}
});
});
};
MemoryRLNRegistry.prototype.register = function (identityCommitment, messageLimit) {
return __awaiter(this, void 0, void 0, function () {
var rateCommitment;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.isRegistered(identityCommitment)];
case 1:
if (_a.sent()) {
throw new Error('Identity commitment is already registered');
}
this.mapMessageLimit.set(identityCommitment.toString(), messageLimit);
return [4 /*yield*/, this.getRateCommitment(identityCommitment)];
case 2:
rateCommitment = _a.sent();
this.group.addMember(rateCommitment);
return [2 /*return*/];
}
});
});
};
MemoryRLNRegistry.prototype.withdraw = function (identitySecret) {
return __awaiter(this, void 0, void 0, function () {
var identityCommitment, isWithdrawing;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
identityCommitment = poseidon([identitySecret]);
return [4 /*yield*/, this.isRegistered(identityCommitment)];
case 1:
if (!(_a.sent())) {
throw new Error('Identity commitment is not registered');
}
isWithdrawing = this.mapIsWithdrawing.get(identityCommitment.toString());
if (isWithdrawing !== undefined) {
throw new Error('Identity is already withdrawing');
}
this.mapIsWithdrawing.set(identityCommitment.toString(), true);
return [2 /*return*/];
}
});
});
};
MemoryRLNRegistry.prototype.releaseWithdrawal = function (identityCommitment) {
return __awaiter(this, void 0, void 0, function () {
var rateCommitment, index, isWithdrawing;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.getRateCommitment(identityCommitment)];
case 1:
rateCommitment = _a.sent();
index = this.group.indexOf(rateCommitment);
if (index === -1) {
// Sanity check
throw new Error('Rate commitment is not in the merkle tree. This should never happen');
}
isWithdrawing = this.mapIsWithdrawing.get(identityCommitment.toString());
if (isWithdrawing === undefined) {
throw new Error('Identity is not withdrawing');
}
this.mapIsWithdrawing.delete(identityCommitment.toString());
this.mapMessageLimit.delete(identityCommitment.toString());
this.group.removeMember(index);
return [2 /*return*/];
}
});
});
};
MemoryRLNRegistry.prototype.slash = function (identitySecret, _) {
return __awaiter(this, void 0, void 0, function () {
var identityCommitment, rateCommitment, index;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
identityCommitment = poseidon([identitySecret]);
return [4 /*yield*/, this.getRateCommitment(identityCommitment)];
case 1:
rateCommitment = _a.sent();
index = this.group.indexOf(rateCommitment);
if (index === -1) {
// Sanity check
throw new Error('Rate commitment is not in the merkle tree. This should never happen');
}
this.mapIsWithdrawing.delete(identityCommitment.toString());
this.mapMessageLimit.delete(identityCommitment.toString());
this.group.removeMember(index);
return [2 /*return*/];
}
});
});
};
return MemoryRLNRegistry;
}());
exports.Status = void 0;
(function (Status) {
Status[Status["VALID"] = 0] = "VALID";
Status[Status["DUPLICATE"] = 1] = "DUPLICATE";
Status[Status["BREACH"] = 2] = "BREACH";
})(exports.Status || (exports.Status = {}));
var DEFAULT_CACHE_SIZE = 100;
/**
* Cache for storing proofs and automatically evaluating them for rate limit breaches
* in the memory.
*/
var MemoryCache = /** @class */ (function () {
/**
* @param cacheLength the maximum number of epochs to store in the cache, default is 100, set to 0 to automatic pruning
* @param cache the cache object to use, default is an empty object
*/
function MemoryCache(cacheLength) {
this.cacheLength = cacheLength ? cacheLength : DEFAULT_CACHE_SIZE;
this.cache = {};
this.epochs = [];
}
/**
* Add a proof to the cache and automatically evaluate it for rate limit breaches.
* @param proof CachedProof
* @returns an object with the status of the proof and the nullifier and secret if the proof is a breach
*/
MemoryCache.prototype.addProof = function (proof) {
// epoch, nullifier, x, y
// Since `BigInt` can't be used as key, use String instead
var epochString = String(proof.epoch);
var nullifier = String(proof.nullifier);
// Check if the proof status
var resCheckProof = this.checkProof(proof);
// Only add the proof to the cache automatically if it's not seen before.
if (resCheckProof.status === exports.Status.VALID || resCheckProof.status === exports.Status.BREACH) {
// Add proof to cache
this.cache[epochString][nullifier].push(proof);
}
return resCheckProof;
};
/**
* Check the proof if it is either valid, duplicate, or breaching.
* Does not add the proof to the cache to avoid side effects.
* @param proof CachedProof
* @returns an object with the status of the proof and the nullifier and secret if the proof is a breach
*/
MemoryCache.prototype.checkProof = function (proof) {
var epochString = String(proof.epoch);
var nullifier = String(proof.nullifier);
this.shiftEpochs(epochString);
// If nullifier doesn't exist for this epoch, create an empty array
this.cache[epochString][nullifier] = this.cache[epochString][nullifier] || [];
var proofs = this.cache[epochString][nullifier];
// Check if the proof already exists. It's O(n) but it's not a big deal since n is exactly the
// rate limit and it's usually small.
function isSameProof(proof1, proof2) {
return (BigInt(proof1.x) === BigInt(proof2.x) &&
BigInt(proof1.y) === BigInt(proof2.y) &&
BigInt(proof1.epoch) === BigInt(proof2.epoch) &&
BigInt(proof1.nullifier) === BigInt(proof2.nullifier));
}
// OK
if (proofs.length === 0) {
return { status: exports.Status.VALID, nullifier: nullifier, msg: 'Proof added to cache' };
// Exists proof with same epoch and nullifier. Possible breach or duplicate proof
}
else {
var sameProofs = this.cache[epochString][nullifier].filter(function (p) { return isSameProof(p, proof); });
if (sameProofs.length > 0) {
return { status: exports.Status.DUPLICATE, msg: 'Proof already exists' };
}
else {
var otherProof = proofs[0];
// Breach. Return secret
var _a = [BigInt(proof.x), BigInt(proof.y)], x1 = _a[0], y1 = _a[1];
var _b = [BigInt(otherProof.x), BigInt(otherProof.y)], x2 = _b[0], y2 = _b[1];
var secret = shamirRecovery(x1, x2, y1, y2);
return { status: exports.Status.BREACH, nullifier: nullifier, secret: secret, msg: 'Rate limit breach, secret attached' };
}
}
};
MemoryCache.prototype.shiftEpochs = function (epoch) {
if (this.cache[epoch]) {
// If epoch already exists, return
return;
}
else {
// If epoch doesn't exist, create it
this.cache[epoch] = {};
this.epochs.push(epoch);
if (this.cacheLength > 0 && this.epochs.length > this.cacheLength) {
this.removeEpoch(this.epochs[0]);
}
}
this.cache[epoch] = this.cache[epoch] || {};
};
MemoryCache.prototype.removeEpoch = function (epoch) {
delete this.cache[epoch];
this.epochs.shift();
};
return MemoryCache;
}());
var MemoryMessageIDCounter = /** @class */ (function () {
function MemoryMessageIDCounter(messageLimit) {
this._messageLimit = messageLimit;
this.epochToMessageID = {};
}
Object.defineProperty(MemoryMessageIDCounter.prototype, "messageLimit", {
get: function () {
return this._messageLimit;
},
enumerable: false,
configurable: true
});
MemoryMessageIDCounter.prototype.getMessageIDAndIncrement = function (epoch) {
return __awaiter(this, void 0, void 0, function () {
var epochStr, messageID;
return __generator(this, function (_a) {
epochStr = epoch.toString();
// Initialize the message id counter if it doesn't exist
if (this.epochToMessageID[epochStr] === undefined) {
this.epochToMessageID[epochStr] = BigInt(0);
}
messageID = this.epochToMessageID[epochStr];
if (messageID >= this.messageLimit) {
throw new Error("Message ID counter exceeded message limit ".concat(this.messageLimit));
}
this.epochToMessageID[epochStr] = messageID + BigInt(1);
return [2 /*return*/, messageID];
});
});
};
return MemoryMessageIDCounter;
}());
var erc20ABI = JSON.parse('[{"constant": true, "inputs": [], "name": "name", "outputs": [{"name": "", "type": "string"}], "payable": false, "stateMutability": "view", "type": "function"}, {"constant": false, "inputs": [{"name": "_spender", "type": "address"}, {"name": "_value", "type": "uint256"}], "name": "approve", "outputs": [{"name": "", "type": "bool"}], "payable": false, "stateMutability": "nonpayable", "type": "function"}, {"constant": true, "inputs": [], "name": "totalSupply", "outputs": [{"name": "", "type": "uint256"}], "payable": false, "stateMutability": "view", "type": "function"}, {"constant": false, "inputs": [{"name": "_from", "type": "address"}, {"name": "_to", "type": "address"}, {"name": "_value", "type": "uint256"}], "name": "transferFrom", "outputs": [{"name": "", "type": "bool"}], "payable": false, "stateMutability": "nonpayable", "type": "function"}, {"constant": true, "inputs": [], "name": "decimals", "outputs": [{"name": "", "type": "uint8"}], "payable": false, "stateMutability": "view", "type": "function"}, {"constant": true, "inputs": [{"name": "_owner", "type": "address"}], "name": "balanceOf", "outputs": [{"name": "balance", "type": "uint256"}], "payable": false, "stateMutability": "view", "type": "function"}, {"constant": true, "inputs": [], "name": "symbol", "outputs": [{"name": "", "type": "string"}], "payable": false, "stateMutability": "view", "type": "function"}, {"constant": false, "inputs": [{"name": "_to", "type": "address"}, {"name": "_value", "type": "uint256"}], "name": "transfer", "outputs": [{"name": "", "type": "bool"}], "payable": false, "stateMutability": "nonpayable", "type": "function"}, {"constant": true, "inputs": [{"name": "_owner", "type": "address"}, {"name": "_spender", "type": "address"}], "name": "allowance", "outputs": [{"name": "", "type": "uint256"}], "payable": false, "stateMutability": "view", "type": "function"}, {"payable": true, "stateMutability": "payable", "type": "fallback"}, {"anonymous": false, "inputs": [{"indexed": true, "name": "owner", "type": "address"}, {"indexed": true, "name": "spender", "type": "address"}, {"indexed": false, "name": "value", "type": "uint256"}], "name": "Approval", "type": "event"}, {"anonymous": false, "inputs": [{"indexed": true, "name": "from", "type": "address"}, {"indexed": true, "name": "to", "type": "address"}, {"indexed": false, "name": "value", "type": "uint256"}], "name": "Transfer", "type": "event"}]');
// Ref: https://github.com/Rate-Limiting-Nullifier/rln-contracts/blob/572bc396b6eef6627ecb2f3ef871370b4c0710f3/src/RLN.sol#L10
var rlnContractABI = JSON.parse('[{"inputs": [{"internalType": "uint256", "name": "minimalDeposit", "type": "uint256"}, {"internalType": "uint256", "name": "maximalRate", "type": "uint256"}, {"internalType": "uint256", "name": "depth", "type": "uint256"}, {"internalType": "uint8", "name": "feePercentage", "type": "uint8"}, {"internalType": "address", "name": "feeReceiver", "type": "address"}, {"internalType": "uint256", "name": "freezePeriod", "type": "uint256"}, {"internalType": "address", "name": "_token", "type": "address"}, {"internalType": "address", "name": "_verifier", "type": "address"}], "stateMutability": "nonpayable", "type": "constructor"}, {"anonymous": false, "inputs": [{"indexed": false, "internalType": "uint256", "name": "identityCommitment", "type": "uint256"}, {"indexed": false, "internalType": "uint256", "name": "messageLimit", "type": "uint256"}, {"indexed": false, "internalType": "uint256", "name": "index", "type": "uint256"}], "name": "MemberRegistered", "type": "event"}, {"anonymous": false, "inputs": [{"indexed": false, "internalType": "uint256", "name": "index", "type": "uint256"}, {"indexed": false, "internalType": "address", "name": "slasher", "type": "address"}], "name": "MemberSlashed", "type": "event"}, {"anonymous": false, "inputs": [{"indexed": false, "internalType": "uint256", "name": "index", "type": "uint256"}], "name": "MemberWithdrawn", "type": "event"}, {"inputs": [], "name": "FEE_PERCENTAGE", "outputs": [{"internalType": "uint8", "name": "", "type": "uint8"}], "stateMutability": "view", "type": "function"}, {"inputs": [], "name": "FEE_RECEIVER", "outputs": [{"internalType": "address", "name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"inputs": [], "name": "FREEZE_PERIOD", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"inputs": [], "name": "MAXIMAL_RATE", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"inputs": [], "name": "MINIMAL_DEPOSIT", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"inputs": [], "name": "SET_SIZE", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"inputs": [], "name": "identityCommitmentIndex", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"inputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "name": "members", "outputs": [{"internalType": "address", "name": "userAddress", "type": "address"}, {"internalType": "uint256", "name": "messageLimit", "type": "uint256"}, {"internalType": "uint256", "name": "index", "type": "uint256"}], "stateMutability": "view", "type": "function"}, {"inputs": [{"internalType": "uint256", "name": "identityCommitment", "type": "uint256"}, {"internalType": "uint256", "name": "amount", "type": "uint256"}], "name": "register", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"internalType": "uint256", "name": "identityCommitment", "type": "uint256"}], "name": "release", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"internalType": "uint256", "name": "identityCommitment", "type": "uint256"}, {"internalType": "address", "name": "receiver", "type": "address"}, {"internalType": "uint256[8]", "name": "proof", "type": "uint256[8]"}], "name": "slash", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [], "name": "token", "outputs": [{"internalType": "contract IERC20", "name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"inputs": [], "name": "verifier", "outputs": [{"internalType": "contract IVerifier", "name": "", "type": "address"}], "stateMutability": "view", "type": "function"}, {"inputs": [{"internalType": "uint256", "name": "identityCommitment", "type": "uint256"}, {"internalType": "uint256[8]", "name": "proof", "type": "uint256[8]"}], "name": "withdraw", "outputs": [], "stateMutability": "nonpayable", "type": "function"}, {"inputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "name": "withdrawals", "outputs": [{"internalType": "uint256", "name": "blockNumber", "type": "uint256"}, {"internalType": "uint256", "name": "amount", "type": "uint256"}, {"internalType": "address", "name": "receiver", "type": "address"}], "stateMutability": "view", "type": "function"}]');
function proofToArray(proof) {
// verifier.verifyProof(
// [proof[0], proof[1]],
// [[proof[2], proof[3]], [proof[4], proof[5]]],
// [proof[6], proof[7]],
// [identityCommitment, uint256(uint160(receiver))]
// );
return [
BigInt(proof.pi_a[0]),
BigInt(proof.pi_a[1]),
BigInt(proof.pi_b[0][0]),
BigInt(proof.pi_b[0][1]),
BigInt(proof.pi_b[1][0]),
BigInt(proof.pi_b[1][1]),
BigInt(proof.pi_c[0]),
BigInt(proof.pi_c[1]),
];
}
var RLNContract = /** @class */ (function () {
function RLNContract(args) {
this.provider = args.provider;
this.signer = args.signer;
this.rlnContract = new ethers.ethers.Contract(args.contractAddress, rlnContractABI, this.getContractRunner());
this.contractAtBlock = args.contractAtBlock;
}
RLNContract.prototype.getContractRunner = function () {
// If signer is given, use signer. Else, use provider.
return this.signer || this.provider;
};
RLNContract.prototype.getTokenAddress = function () {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/, this.rlnContract.token()];
});
});
};
RLNContract.prototype.getSignerAddress = function () {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
if (this.signer === undefined) {
throw new Error('Cannot get signer address if signer is not set');
}
return [2 /*return*/, this.signer.getAddress()];
});
});
};
RLNContract.prototype.getLogs = function () {
return __awaiter(this, void 0, void 0, function () {
var rlnContractAddress, currentBlockNumber, logs, events;
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.rlnContract.getAddress()];
case 1:
rlnContractAddress = _a.sent();
return [4 /*yield*/, this.provider.getBlockNumber()];
case 2:
currentBlockNumber = _a.sent();
if (currentBlockNumber < this.contractAtBlock) {
throw new Error('Current block number is lower than the block number at which the contract was deployed');
}
return [4 /*yield*/, this.provider.getLogs({
address: rlnContractAddress,
fromBlock: this.contractAtBlock,
toBlock: currentBlockNumber,
})];
case 3:
logs = _a.sent();
return [4 /*yield*/, Promise.all(logs.map(function (log) { return _this.handleLog(log); }))];
case 4:
events = _a.sent();
return [2 /*return*/, events.filter(function (x) { return x !== undefined; })];
}
});
});
};
RLNContract.prototype.handleLog = function (log) {
return __awaiter(this, void 0, void 0, function () {
var memberRegisteredFilter, memberWithdrawnFilter, memberSlashedFilter, memberRegisteredTopics, memberWithdrawnTopics, memberSlashedTopics, decoded, decoded, decoded;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
memberRegisteredFilter = this.rlnContract.filters.MemberRegistered();
memberWithdrawnFilter = this.rlnContract.filters.MemberWithdrawn();
memberSlashedFilter = this.rlnContract.filters.MemberSlashed();
return [4 /*yield*/, memberRegisteredFilter.getTopicFilter()];
case 1:
memberRegisteredTopics = _a.sent();
return [4 /*yield*/, memberWithdrawnFilter.getTopicFilter()];
case 2:
memberWithdrawnTopics = _a.sent();
return [4 /*yield*/, memberSlashedFilter.getTopicFilter()];
case 3:
memberSlashedTopics = _a.sent();
if (log.topics[0] === memberRegisteredTopics[0]) {
decoded = this.rlnContract.interface.decodeEventLog(memberRegisteredFilter.fragment, log.data);
return [2 /*return*/, {
name: 'MemberRegistered',
identityCommitment: decoded.identityCommitment,
messageLimit: decoded.messageLimit,
index: decoded.index,
}];
}