mirror of
https://github.com/logos-messaging/js-rln.git
synced 2026-01-02 13:43:06 +00:00
featimprove start, types, import/export, add checks for netwrok, shortcuts for decoder encoder
This commit is contained in:
parent
bafbe01e52
commit
aece0e3dc3
@ -6,6 +6,7 @@ import {
|
||||
} from "./constants.js";
|
||||
import { createRLN } from "./create.js";
|
||||
import { Keystore } from "./keystore/index.js";
|
||||
import { extractMetaMaskSigner } from "./metamask.js";
|
||||
import {
|
||||
IdentityCredential,
|
||||
Proof,
|
||||
@ -29,4 +30,5 @@ export {
|
||||
RLN_STORAGE_ABI,
|
||||
RLN_REGISTRY_ABI,
|
||||
SEPOLIA_CONTRACT,
|
||||
extractMetaMaskSigner,
|
||||
};
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { Keystore } from "./keystore.js";
|
||||
import type { KeystoreEntity } from "./types.js";
|
||||
|
||||
export { Keystore };
|
||||
export type { KeystoreEntity };
|
||||
|
||||
@ -14,12 +14,12 @@ import _ from "lodash";
|
||||
import { v4 as uuidV4 } from "uuid";
|
||||
|
||||
import { buildBigIntFromUint8Array } from "../byte_utils.js";
|
||||
import type { IdentityCredential } from "../rln.js";
|
||||
|
||||
import { decryptEipKeystore, keccak256Checksum } from "./cipher.js";
|
||||
import { isCredentialValid, isKeystoreValid } from "./schema_validator.js";
|
||||
import type {
|
||||
Keccak256Hash,
|
||||
KeystoreEntity,
|
||||
MembershipHash,
|
||||
MembershipInfo,
|
||||
Password,
|
||||
@ -57,11 +57,6 @@ type KeystoreCreateOptions = {
|
||||
appIdentifier?: string;
|
||||
};
|
||||
|
||||
type IdentityOptions = {
|
||||
identity: IdentityCredential;
|
||||
membership: MembershipInfo;
|
||||
};
|
||||
|
||||
export class Keystore {
|
||||
private data: NwakuKeystore;
|
||||
|
||||
@ -105,7 +100,7 @@ export class Keystore {
|
||||
}
|
||||
|
||||
public async addCredential(
|
||||
options: IdentityOptions,
|
||||
options: KeystoreEntity,
|
||||
password: Password
|
||||
): Promise<MembershipHash> {
|
||||
const membershipHash: MembershipHash = Keystore.computeMembershipHash(
|
||||
@ -138,7 +133,7 @@ export class Keystore {
|
||||
public async readCredential(
|
||||
membershipHash: MembershipHash,
|
||||
password: Password
|
||||
): Promise<null | IdentityOptions> {
|
||||
): Promise<null | KeystoreEntity> {
|
||||
const nwakuCredential = this.data.credentials[membershipHash];
|
||||
|
||||
if (!nwakuCredential) {
|
||||
@ -235,9 +230,7 @@ export class Keystore {
|
||||
};
|
||||
}
|
||||
|
||||
private static fromBytesToIdentity(
|
||||
bytes: Uint8Array
|
||||
): null | IdentityOptions {
|
||||
private static fromBytesToIdentity(bytes: Uint8Array): null | KeystoreEntity {
|
||||
try {
|
||||
const str = bytesToUtf8(bytes);
|
||||
const obj = JSON.parse(str);
|
||||
@ -302,7 +295,7 @@ export class Keystore {
|
||||
|
||||
// follows nwaku implementation
|
||||
// https://github.com/waku-org/nwaku/blob/f05528d4be3d3c876a8b07f9bb7dfaae8aa8ec6e/waku/waku_keystore/protocol_types.nim#L98
|
||||
private static fromIdentityToBytes(options: IdentityOptions): Uint8Array {
|
||||
private static fromIdentityToBytes(options: KeystoreEntity): Uint8Array {
|
||||
return utf8ToBytes(
|
||||
JSON.stringify({
|
||||
treeIndex: options.membership.treeIndex,
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import type { IdentityCredential } from "../rln.js";
|
||||
|
||||
export type MembershipHash = string;
|
||||
export type Sha256Hash = string;
|
||||
export type Keccak256Hash = string;
|
||||
@ -10,3 +12,8 @@ export type MembershipInfo = {
|
||||
address: string;
|
||||
treeIndex: number;
|
||||
};
|
||||
|
||||
export type KeystoreEntity = {
|
||||
identity: IdentityCredential;
|
||||
membership: MembershipInfo;
|
||||
};
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
import { ethers } from "ethers";
|
||||
|
||||
export const extractMetaMaskAccount =
|
||||
async (): Promise<ethers.providers.Web3Provider> => {
|
||||
const ethereum = (window as any).ethereum;
|
||||
export const extractMetaMaskSigner = async (): Promise<ethers.Signer> => {
|
||||
const ethereum = (window as any).ethereum;
|
||||
|
||||
if (!ethereum) {
|
||||
throw Error(
|
||||
"Missing or invalid Ethereum provider. Please install a Web3 wallet such as MetaMask."
|
||||
);
|
||||
}
|
||||
if (!ethereum) {
|
||||
throw Error(
|
||||
"Missing or invalid Ethereum provider. Please install a Web3 wallet such as MetaMask."
|
||||
);
|
||||
}
|
||||
|
||||
await ethereum.request({ method: "eth_requestAccounts" });
|
||||
return new ethers.providers.Web3Provider(ethereum, "any");
|
||||
};
|
||||
await ethereum.request({ method: "eth_requestAccounts" });
|
||||
const provider = new ethers.providers.Web3Provider(ethereum, "any");
|
||||
|
||||
return provider.getSigner();
|
||||
};
|
||||
|
||||
133
src/rln.ts
133
src/rln.ts
@ -1,12 +1,21 @@
|
||||
import { createDecoder, createEncoder } from "@waku/core";
|
||||
import type { IRateLimitProof } from "@waku/interfaces";
|
||||
import type {
|
||||
ContentTopic,
|
||||
IDecodedMessage,
|
||||
EncoderOptions as WakuEncoderOptions,
|
||||
} from "@waku/interfaces";
|
||||
import init from "@waku/zerokit-rln-wasm";
|
||||
import * as zerokitRLN from "@waku/zerokit-rln-wasm";
|
||||
import { ethers } from "ethers";
|
||||
|
||||
import { buildBigIntFromUint8Array, writeUIntLE } from "./byte_utils.js";
|
||||
import type { RLNDecoder, RLNEncoder } from "./codec.js";
|
||||
import { createRLNDecoder, createRLNEncoder } from "./codec.js";
|
||||
import { SEPOLIA_CONTRACT } from "./constants.js";
|
||||
import { dateToEpoch, epochIntToBytes } from "./epoch.js";
|
||||
import { extractMetaMaskAccount } from "./metamask.js";
|
||||
import type { KeystoreEntity } from "./keystore/index.js";
|
||||
import { extractMetaMaskSigner } from "./metamask.js";
|
||||
import verificationKey from "./resources/verification_key.js";
|
||||
import { RLNContract } from "./rln_contract.js";
|
||||
import * as wc from "./witness_calculator.js";
|
||||
@ -164,34 +173,138 @@ export function sha256(input: Uint8Array): Uint8Array {
|
||||
|
||||
type StartRLNOptions = {
|
||||
/**
|
||||
* If not set - will extract MetaMask account and get provider from it.
|
||||
* If not set - will extract MetaMask account and get signer from it.
|
||||
*/
|
||||
provider?: ethers.providers.Provider;
|
||||
signer?: ethers.Signer;
|
||||
/**
|
||||
* If not set - will use default SEPOLIA_CONTRACT address.
|
||||
*/
|
||||
registryAddress?: string;
|
||||
/**
|
||||
* Credentials to use for generating proofs and connecting to the contract and network.
|
||||
* If provided used for validating the network chainId and connecting to registry contract.
|
||||
*/
|
||||
credentials?: KeystoreEntity;
|
||||
};
|
||||
|
||||
type RegisterMembershipOptions =
|
||||
| { signature: string }
|
||||
| { identity: IdentityCredential };
|
||||
|
||||
export class RLNInstance {
|
||||
private _contract: null | RLNContract = null;
|
||||
private started = false;
|
||||
private starting = false;
|
||||
|
||||
private _contract: undefined | RLNContract;
|
||||
private _signer: undefined | ethers.Signer;
|
||||
private _credentials: undefined | KeystoreEntity;
|
||||
|
||||
constructor(
|
||||
private zkRLN: number,
|
||||
private witnessCalculator: WitnessCalculator
|
||||
) {}
|
||||
|
||||
public get contract(): null | RLNContract {
|
||||
public get contract(): undefined | RLNContract {
|
||||
return this._contract;
|
||||
}
|
||||
|
||||
public async start(options: StartRLNOptions = {}): Promise<void> {
|
||||
const provider = options.provider || (await extractMetaMaskAccount());
|
||||
const registryAddress = options.registryAddress || SEPOLIA_CONTRACT.address;
|
||||
public get signer(): undefined | ethers.Signer {
|
||||
return this._signer;
|
||||
}
|
||||
|
||||
this._contract = await RLNContract.init(this, {
|
||||
public async start(options: StartRLNOptions = {}): Promise<void> {
|
||||
if (this.started || this.starting) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.starting = true;
|
||||
|
||||
try {
|
||||
const { signer, credentials, registryAddress } =
|
||||
await this.determineStartOptions(options);
|
||||
|
||||
this._signer = signer!;
|
||||
this._credentials = credentials;
|
||||
this._contract = await RLNContract.init(this, {
|
||||
registryAddress: registryAddress!,
|
||||
signer: signer!,
|
||||
});
|
||||
this.started = true;
|
||||
} finally {
|
||||
this.starting = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async determineStartOptions(
|
||||
options: StartRLNOptions
|
||||
): Promise<StartRLNOptions> {
|
||||
let chainId = options.credentials?.membership.chainId;
|
||||
const registryAddress =
|
||||
options.credentials?.membership.address ||
|
||||
options.registryAddress ||
|
||||
SEPOLIA_CONTRACT.address;
|
||||
|
||||
if (registryAddress === SEPOLIA_CONTRACT.address) {
|
||||
chainId = SEPOLIA_CONTRACT.chainId;
|
||||
}
|
||||
|
||||
const signer = options.signer || (await extractMetaMaskSigner());
|
||||
const currentChainId = await signer.getChainId();
|
||||
|
||||
if (chainId && chainId !== currentChainId) {
|
||||
throw Error(
|
||||
`Failed to start RLN contract, chain ID of contract is different from current one: contract-${chainId}, current network-${currentChainId}`
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
signer,
|
||||
registryAddress,
|
||||
provider,
|
||||
credentials: options.credentials,
|
||||
};
|
||||
}
|
||||
|
||||
public async registerMembership(
|
||||
options: RegisterMembershipOptions
|
||||
): Promise<undefined | KeystoreEntity> {
|
||||
if (!this.contract) {
|
||||
throw Error("RLN Contract is not initialized.");
|
||||
}
|
||||
|
||||
if (!options.identity || !options.signature) {
|
||||
throw Error("Missing signature or identity to register membership.");
|
||||
}
|
||||
|
||||
let identity = options.identity;
|
||||
|
||||
if (options.signature) {
|
||||
identity = await this.generateSeededIdentityCredential(signature);
|
||||
}
|
||||
|
||||
return this.contract.registerWithIdentity(identity);
|
||||
}
|
||||
|
||||
public createEncoder(options: WakuEncoderOptions): RLNEncoder {
|
||||
if (!this._credentials) {
|
||||
throw Error(
|
||||
"Failed to create Encoder: missing RLN credentials. Use createRLNEncoder directly."
|
||||
);
|
||||
}
|
||||
|
||||
return createRLNEncoder({
|
||||
encoder: createEncoder(options),
|
||||
rlnInstance: this,
|
||||
index: this._credentials.membership.treeIndex,
|
||||
credential: this._credentials.identity,
|
||||
});
|
||||
}
|
||||
|
||||
public createDecoder(
|
||||
contentTopic: ContentTopic
|
||||
): RLNDecoder<IDecodedMessage> {
|
||||
return createRLNDecoder({
|
||||
rlnInstance: this,
|
||||
decoder: createDecoder(contentTopic),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,8 @@ import { ethers } from "ethers";
|
||||
|
||||
import { zeroPadLE } from "./byte_utils.js";
|
||||
import { RLN_REGISTRY_ABI, RLN_STORAGE_ABI } from "./constants.js";
|
||||
import { IdentityCredential, RLNInstance } from "./rln.js";
|
||||
import type { KeystoreEntity } from "./keystore/index.js";
|
||||
import { type IdentityCredential, RLNInstance } from "./rln.js";
|
||||
import { MerkleRootTracker } from "./root_tracker.js";
|
||||
|
||||
type Member = {
|
||||
@ -11,10 +12,10 @@ type Member = {
|
||||
index: ethers.BigNumber;
|
||||
};
|
||||
|
||||
type Provider = ethers.Signer | ethers.providers.Provider;
|
||||
type Signer = ethers.Signer;
|
||||
|
||||
type RLNContractOptions = {
|
||||
provider: Provider;
|
||||
signer: Signer;
|
||||
registryAddress: string;
|
||||
};
|
||||
|
||||
@ -47,7 +48,7 @@ export class RLNContract {
|
||||
): Promise<RLNContract> {
|
||||
const rlnContract = new RLNContract(rlnInstance, options);
|
||||
|
||||
await rlnContract.initStorageContract(options.provider);
|
||||
await rlnContract.initStorageContract(options.signer);
|
||||
await rlnContract.fetchMembers(rlnInstance);
|
||||
rlnContract.subscribeToMembers(rlnInstance);
|
||||
|
||||
@ -56,20 +57,20 @@ export class RLNContract {
|
||||
|
||||
constructor(
|
||||
rlnInstance: RLNInstance,
|
||||
{ registryAddress, provider }: RLNContractOptions
|
||||
{ registryAddress, signer }: RLNContractOptions
|
||||
) {
|
||||
const initialRoot = rlnInstance.getMerkleRoot();
|
||||
|
||||
this.registryContract = new ethers.Contract(
|
||||
registryAddress,
|
||||
RLN_REGISTRY_ABI,
|
||||
provider
|
||||
signer
|
||||
);
|
||||
this.merkleRootTracker = new MerkleRootTracker(5, initialRoot);
|
||||
}
|
||||
|
||||
private async initStorageContract(
|
||||
provider: Provider,
|
||||
signer: Signer,
|
||||
options: RLNStorageOptions = {}
|
||||
): Promise<void> {
|
||||
const storageIndex = options?.storageIndex
|
||||
@ -85,7 +86,7 @@ export class RLNContract {
|
||||
this.storageContract = new ethers.Contract(
|
||||
storageAddress,
|
||||
RLN_STORAGE_ABI,
|
||||
provider
|
||||
signer
|
||||
);
|
||||
this._membersFilter = this.storageContract.filters.MemberRegistered();
|
||||
|
||||
@ -207,19 +208,9 @@ export class RLNContract {
|
||||
);
|
||||
}
|
||||
|
||||
public async registerWithSignature(
|
||||
rlnInstance: RLNInstance,
|
||||
signature: string
|
||||
): Promise<Member | undefined> {
|
||||
const identityCredential =
|
||||
await rlnInstance.generateSeededIdentityCredential(signature);
|
||||
|
||||
return this.registerWithKey(identityCredential);
|
||||
}
|
||||
|
||||
public async registerWithKey(
|
||||
credential: IdentityCredential
|
||||
): Promise<Member | undefined> {
|
||||
public async registerWithIdentity(
|
||||
identity: IdentityCredential
|
||||
): Promise<KeystoreEntity | undefined> {
|
||||
if (this.storageIndex === undefined) {
|
||||
throw Error(
|
||||
"Cannot register credential, no storage contract index found."
|
||||
@ -228,7 +219,7 @@ export class RLNContract {
|
||||
const txRegisterResponse: ethers.ContractTransaction =
|
||||
await this.registryContract["register(uint16,uint256)"](
|
||||
this.storageIndex,
|
||||
credential.IDCommitmentBigInt,
|
||||
identity.IDCommitmentBigInt,
|
||||
{ gasLimit: 100000 }
|
||||
);
|
||||
const txRegisterReceipt = await txRegisterResponse.wait();
|
||||
@ -245,9 +236,17 @@ export class RLNContract {
|
||||
memberRegistered.data
|
||||
);
|
||||
|
||||
const network = await this.registryContract.provider.getNetwork();
|
||||
const address = this.registryContract.address;
|
||||
const membershipId = decodedData.index.toNumber();
|
||||
|
||||
return {
|
||||
idCommitment: decodedData.idCommitment,
|
||||
index: decodedData.index,
|
||||
identity,
|
||||
membership: {
|
||||
address,
|
||||
treeIndex: membershipId,
|
||||
chainId: network.chainId,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user