@waku/rln
Version:
RLN (Rate Limiting Nullifier) implementation for Waku
176 lines (173 loc) • 7.47 kB
JavaScript
import '../../../node_modules/multiformats/dist/src/bases/base10.js';
import '../../../node_modules/multiformats/dist/src/bases/base16.js';
import '../../../node_modules/multiformats/dist/src/bases/base2.js';
import '../../../node_modules/multiformats/dist/src/bases/base256emoji.js';
import '../../../node_modules/multiformats/dist/src/bases/base32.js';
import '../../../node_modules/multiformats/dist/src/bases/base36.js';
import '../../../node_modules/multiformats/dist/src/bases/base58.js';
import '../../../node_modules/multiformats/dist/src/bases/base64.js';
import '../../../node_modules/multiformats/dist/src/bases/base8.js';
import '../../../node_modules/multiformats/dist/src/bases/identity.js';
import '../../../node_modules/multiformats/dist/src/codecs/json.js';
import { Logger } from '../../utils/dist/logger.js';
import { RLN_CONTRACT } from './contract/constants.js';
import { RLNBaseContract } from './contract/rln_base_contract.js';
import { Keystore } from './keystore/keystore.js';
import { extractMetaMaskSigner } from './utils/metamask.js';
import './utils/epoch.js';
const log = new Logger("rln:credentials");
/**
* Manages credentials for RLN
* It is used to register membership and generate identity credentials
*/
class RLNCredentialsManager {
started = false;
starting = false;
contract;
signer;
keystore = Keystore.create();
credentials;
zerokit;
constructor(zerokit) {
log.info("RLNCredentialsManager initialized");
this.zerokit = zerokit;
}
get provider() {
return this.contract?.provider;
}
async start(options = {}) {
if (this.started || this.starting) {
log.info("RLNCredentialsManager already started or starting");
return;
}
log.info("Starting RLNCredentialsManager");
this.starting = true;
try {
const { credentials, keystore } = await RLNCredentialsManager.decryptCredentialsIfNeeded(options.credentials);
if (credentials) {
log.info("Credentials successfully decrypted");
}
const { signer, address, rateLimit } = await this.determineStartOptions(options, credentials);
log.info(`Using contract address: ${address}`);
if (keystore) {
this.keystore = keystore;
log.info("Using provided keystore");
}
this.credentials = credentials;
this.signer = signer;
this.contract = await RLNBaseContract.create({
address: address,
signer: signer,
rateLimit: rateLimit ?? this.zerokit.rateLimit
});
log.info("RLNCredentialsManager successfully started");
this.started = true;
}
catch (error) {
log.error("Failed to start RLNCredentialsManager", error);
throw error;
}
finally {
this.starting = false;
}
}
async registerMembership(options) {
if (!this.contract) {
log.error("RLN Contract is not initialized");
throw Error("RLN Contract is not initialized.");
}
log.info("Registering membership");
let identity = "identity" in options && options.identity;
if ("signature" in options) {
log.info("Using Zerokit to generate identity");
identity = this.zerokit.generateSeededIdentityCredential(options.signature);
}
if (!identity) {
log.error("Missing signature or identity to register membership");
throw Error("Missing signature or identity to register membership.");
}
log.info("Registering identity with contract");
return this.contract.registerWithIdentity(identity);
}
/**
* Changes credentials in use by relying on provided Keystore earlier in rln.start
* @param id: string, hash of credentials to select from Keystore
* @param password: string or bytes to use to decrypt credentials from Keystore
*/
async useCredentials(id, password) {
log.info(`Attempting to use credentials with ID: ${id}`);
this.credentials = await this.keystore?.readCredential(id, password);
if (this.credentials) {
log.info("Successfully loaded credentials");
}
else {
log.warn("Failed to load credentials");
}
}
async determineStartOptions(options, credentials) {
let chainId = credentials?.membership.chainId;
const address = credentials?.membership.address ||
options.address ||
RLN_CONTRACT.address;
if (address === RLN_CONTRACT.address) {
chainId = RLN_CONTRACT.chainId.toString();
log.info(`Using Linea contract with chainId: ${chainId}`);
}
const signer = options.signer || (await extractMetaMaskSigner());
const currentChainId = await signer.getChainId();
log.info(`Current chain ID: ${currentChainId}`);
if (chainId && chainId !== currentChainId.toString()) {
log.error(`Chain ID mismatch: contract=${chainId}, current=${currentChainId}`);
throw Error(`Failed to start RLN contract, chain ID of contract is different from current one: contract-${chainId}, current network-${currentChainId}`);
}
return {
signer,
address
};
}
static async decryptCredentialsIfNeeded(credentials) {
if (!credentials) {
log.info("No credentials provided");
return {};
}
if ("identity" in credentials) {
log.info("Using already decrypted credentials");
return { credentials };
}
log.info("Attempting to decrypt credentials");
const keystore = Keystore.fromString(credentials.keystore);
if (!keystore) {
log.warn("Failed to create keystore from string");
return {};
}
try {
const decryptedCredentials = await keystore.readCredential(credentials.id, credentials.password);
log.info(`Successfully decrypted credentials with ID: ${credentials.id}`);
return {
keystore,
credentials: decryptedCredentials
};
}
catch (error) {
log.error("Failed to decrypt credentials", error);
throw error;
}
}
async verifyCredentialsAgainstContract(credentials) {
if (!this.contract) {
throw Error("Failed to verify chain coordinates: no contract initialized.");
}
const registryAddress = credentials.membership.address;
const currentRegistryAddress = this.contract.address;
if (registryAddress !== currentRegistryAddress) {
throw Error(`Failed to verify chain coordinates: credentials contract address=${registryAddress} is not equal to registryContract address=${currentRegistryAddress}`);
}
const chainId = credentials.membership.chainId;
const network = await this.contract.provider.getNetwork();
const currentChainId = network.chainId;
if (chainId !== currentChainId.toString()) {
throw Error(`Failed to verify chain coordinates: credentials chainID=${chainId} is not equal to registryContract chainID=${currentChainId}`);
}
}
}
export { RLNCredentialsManager };