mirror of
https://github.com/logos-messaging/js-rln.git
synced 2026-01-08 00:23:12 +00:00
feat!: add new operations and improve start (#93)
* featimprove start, types, import/export, add checks for netwrok, shortcuts for decoder encoder * fix type issue * update tests * provide ability to use Keystore as a seed for credentials * fix types * up test * initialize keystore by default * add keys operation to Keystore
This commit is contained in:
parent
bafbe01e52
commit
77ba0a6d5d
@ -6,6 +6,7 @@ import {
|
|||||||
} from "./constants.js";
|
} from "./constants.js";
|
||||||
import { createRLN } from "./create.js";
|
import { createRLN } from "./create.js";
|
||||||
import { Keystore } from "./keystore/index.js";
|
import { Keystore } from "./keystore/index.js";
|
||||||
|
import { extractMetaMaskSigner } from "./metamask.js";
|
||||||
import {
|
import {
|
||||||
IdentityCredential,
|
IdentityCredential,
|
||||||
Proof,
|
Proof,
|
||||||
@ -29,4 +30,5 @@ export {
|
|||||||
RLN_STORAGE_ABI,
|
RLN_STORAGE_ABI,
|
||||||
RLN_REGISTRY_ABI,
|
RLN_REGISTRY_ABI,
|
||||||
SEPOLIA_CONTRACT,
|
SEPOLIA_CONTRACT,
|
||||||
|
extractMetaMaskSigner,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
import { Keystore } from "./keystore.js";
|
import { Keystore } from "./keystore.js";
|
||||||
|
import type { DecryptedCredentials, EncryptedCredentials } from "./types.js";
|
||||||
|
|
||||||
export { Keystore };
|
export { Keystore };
|
||||||
|
export type { EncryptedCredentials, DecryptedCredentials };
|
||||||
|
|||||||
@ -172,8 +172,8 @@ describe("Keystore", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should fail to create store from invalid string", () => {
|
it("should fail to create store from invalid string", () => {
|
||||||
expect(Keystore.fromString("/asdq}")).to.eq(null);
|
expect(Keystore.fromString("/asdq}")).to.eq(undefined);
|
||||||
expect(Keystore.fromString('{ "name": "it" }')).to.eq(null);
|
expect(Keystore.fromString('{ "name": "it" }')).to.eq(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shoud create store from valid string", async () => {
|
it("shoud create store from valid string", async () => {
|
||||||
@ -308,6 +308,6 @@ describe("Keystore", () => {
|
|||||||
const store = Keystore.fromObject(NWAKU_KEYSTORE as any);
|
const store = Keystore.fromObject(NWAKU_KEYSTORE as any);
|
||||||
|
|
||||||
const result = await store.readCredential("wrong-hash", "wrong-password");
|
const result = await store.readCredential("wrong-hash", "wrong-password");
|
||||||
expect(result).to.eq(null);
|
expect(result).to.eq(undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -14,12 +14,12 @@ import _ from "lodash";
|
|||||||
import { v4 as uuidV4 } from "uuid";
|
import { v4 as uuidV4 } from "uuid";
|
||||||
|
|
||||||
import { buildBigIntFromUint8Array } from "../byte_utils.js";
|
import { buildBigIntFromUint8Array } from "../byte_utils.js";
|
||||||
import type { IdentityCredential } from "../rln.js";
|
|
||||||
|
|
||||||
import { decryptEipKeystore, keccak256Checksum } from "./cipher.js";
|
import { decryptEipKeystore, keccak256Checksum } from "./cipher.js";
|
||||||
import { isCredentialValid, isKeystoreValid } from "./schema_validator.js";
|
import { isCredentialValid, isKeystoreValid } from "./schema_validator.js";
|
||||||
import type {
|
import type {
|
||||||
Keccak256Hash,
|
Keccak256Hash,
|
||||||
|
KeystoreEntity,
|
||||||
MembershipHash,
|
MembershipHash,
|
||||||
MembershipInfo,
|
MembershipInfo,
|
||||||
Password,
|
Password,
|
||||||
@ -57,11 +57,6 @@ type KeystoreCreateOptions = {
|
|||||||
appIdentifier?: string;
|
appIdentifier?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type IdentityOptions = {
|
|
||||||
identity: IdentityCredential;
|
|
||||||
membership: MembershipInfo;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class Keystore {
|
export class Keystore {
|
||||||
private data: NwakuKeystore;
|
private data: NwakuKeystore;
|
||||||
|
|
||||||
@ -81,7 +76,9 @@ export class Keystore {
|
|||||||
return new Keystore(options);
|
return new Keystore(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static fromString(str: string): Keystore | null {
|
// should be valid JSON string that contains Keystore file
|
||||||
|
// https://github.com/waku-org/nwaku/blob/f05528d4be3d3c876a8b07f9bb7dfaae8aa8ec6e/waku/waku_keystore/keyfile.nim#L376
|
||||||
|
public static fromString(str: string): undefined | Keystore {
|
||||||
try {
|
try {
|
||||||
const obj = JSON.parse(str);
|
const obj = JSON.parse(str);
|
||||||
|
|
||||||
@ -92,7 +89,7 @@ export class Keystore {
|
|||||||
return new Keystore(obj);
|
return new Keystore(obj);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Cannot create Keystore from string:", err);
|
console.error("Cannot create Keystore from string:", err);
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +102,7 @@ export class Keystore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async addCredential(
|
public async addCredential(
|
||||||
options: IdentityOptions,
|
options: KeystoreEntity,
|
||||||
password: Password
|
password: Password
|
||||||
): Promise<MembershipHash> {
|
): Promise<MembershipHash> {
|
||||||
const membershipHash: MembershipHash = Keystore.computeMembershipHash(
|
const membershipHash: MembershipHash = Keystore.computeMembershipHash(
|
||||||
@ -138,11 +135,11 @@ export class Keystore {
|
|||||||
public async readCredential(
|
public async readCredential(
|
||||||
membershipHash: MembershipHash,
|
membershipHash: MembershipHash,
|
||||||
password: Password
|
password: Password
|
||||||
): Promise<null | IdentityOptions> {
|
): Promise<undefined | KeystoreEntity> {
|
||||||
const nwakuCredential = this.data.credentials[membershipHash];
|
const nwakuCredential = this.data.credentials[membershipHash];
|
||||||
|
|
||||||
if (!nwakuCredential) {
|
if (!nwakuCredential) {
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const eipKeystore = Keystore.fromCredentialToEip(nwakuCredential);
|
const eipKeystore = Keystore.fromCredentialToEip(nwakuCredential);
|
||||||
@ -167,6 +164,14 @@ export class Keystore {
|
|||||||
return this.data;
|
return this.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read array of hashes of current credentials
|
||||||
|
* @returns array of keys of credentials in current Keystore
|
||||||
|
*/
|
||||||
|
public keys(): string[] {
|
||||||
|
return Object.keys(this.toObject().credentials || {});
|
||||||
|
}
|
||||||
|
|
||||||
private static isValidNwakuStore(obj: unknown): boolean {
|
private static isValidNwakuStore(obj: unknown): boolean {
|
||||||
if (!isKeystoreValid(obj)) {
|
if (!isKeystoreValid(obj)) {
|
||||||
return false;
|
return false;
|
||||||
@ -237,7 +242,7 @@ export class Keystore {
|
|||||||
|
|
||||||
private static fromBytesToIdentity(
|
private static fromBytesToIdentity(
|
||||||
bytes: Uint8Array
|
bytes: Uint8Array
|
||||||
): null | IdentityOptions {
|
): undefined | KeystoreEntity {
|
||||||
try {
|
try {
|
||||||
const str = bytesToUtf8(bytes);
|
const str = bytesToUtf8(bytes);
|
||||||
const obj = JSON.parse(str);
|
const obj = JSON.parse(str);
|
||||||
@ -271,7 +276,7 @@ export class Keystore {
|
|||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Cannot parse bytes to Nwaku Credentials:", err);
|
console.error("Cannot parse bytes to Nwaku Credentials:", err);
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -302,7 +307,7 @@ export class Keystore {
|
|||||||
|
|
||||||
// follows nwaku implementation
|
// follows nwaku implementation
|
||||||
// https://github.com/waku-org/nwaku/blob/f05528d4be3d3c876a8b07f9bb7dfaae8aa8ec6e/waku/waku_keystore/protocol_types.nim#L98
|
// 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(
|
return utf8ToBytes(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
treeIndex: options.membership.treeIndex,
|
treeIndex: options.membership.treeIndex,
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import type { IdentityCredential } from "../rln.js";
|
||||||
|
|
||||||
export type MembershipHash = string;
|
export type MembershipHash = string;
|
||||||
export type Sha256Hash = string;
|
export type Sha256Hash = string;
|
||||||
export type Keccak256Hash = string;
|
export type Keccak256Hash = string;
|
||||||
@ -10,3 +12,25 @@ export type MembershipInfo = {
|
|||||||
address: string;
|
address: string;
|
||||||
treeIndex: number;
|
treeIndex: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type KeystoreEntity = {
|
||||||
|
identity: IdentityCredential;
|
||||||
|
membership: MembershipInfo;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DecryptedCredentials = KeystoreEntity;
|
||||||
|
|
||||||
|
export type EncryptedCredentials = {
|
||||||
|
/**
|
||||||
|
* Valid JSON string that contains Keystore
|
||||||
|
*/
|
||||||
|
keystore: string;
|
||||||
|
/**
|
||||||
|
* ID of credentials from provided Keystore to use
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
/**
|
||||||
|
* Password to decrypt credentials provided
|
||||||
|
*/
|
||||||
|
password: Password;
|
||||||
|
};
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { ethers } from "ethers";
|
import { ethers } from "ethers";
|
||||||
|
|
||||||
export const extractMetaMaskAccount =
|
export const extractMetaMaskSigner = async (): Promise<ethers.Signer> => {
|
||||||
async (): Promise<ethers.providers.Web3Provider> => {
|
|
||||||
const ethereum = (window as any).ethereum;
|
const ethereum = (window as any).ethereum;
|
||||||
|
|
||||||
if (!ethereum) {
|
if (!ethereum) {
|
||||||
@ -11,5 +10,7 @@ export const extractMetaMaskAccount =
|
|||||||
}
|
}
|
||||||
|
|
||||||
await ethereum.request({ method: "eth_requestAccounts" });
|
await ethereum.request({ method: "eth_requestAccounts" });
|
||||||
return new ethers.providers.Web3Provider(ethereum, "any");
|
const provider = new ethers.providers.Web3Provider(ethereum, "any");
|
||||||
|
|
||||||
|
return provider.getSigner();
|
||||||
};
|
};
|
||||||
|
|||||||
178
src/rln.ts
178
src/rln.ts
@ -1,12 +1,26 @@
|
|||||||
|
import { createDecoder, createEncoder } from "@waku/core";
|
||||||
import type { IRateLimitProof } from "@waku/interfaces";
|
import type { IRateLimitProof } from "@waku/interfaces";
|
||||||
|
import type {
|
||||||
|
ContentTopic,
|
||||||
|
IDecodedMessage,
|
||||||
|
EncoderOptions as WakuEncoderOptions,
|
||||||
|
} from "@waku/interfaces";
|
||||||
import init from "@waku/zerokit-rln-wasm";
|
import init from "@waku/zerokit-rln-wasm";
|
||||||
import * as zerokitRLN from "@waku/zerokit-rln-wasm";
|
import * as zerokitRLN from "@waku/zerokit-rln-wasm";
|
||||||
import { ethers } from "ethers";
|
import { ethers } from "ethers";
|
||||||
|
|
||||||
import { buildBigIntFromUint8Array, writeUIntLE } from "./byte_utils.js";
|
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 { SEPOLIA_CONTRACT } from "./constants.js";
|
||||||
import { dateToEpoch, epochIntToBytes } from "./epoch.js";
|
import { dateToEpoch, epochIntToBytes } from "./epoch.js";
|
||||||
import { extractMetaMaskAccount } from "./metamask.js";
|
import { Keystore } from "./keystore/index.js";
|
||||||
|
import type {
|
||||||
|
DecryptedCredentials,
|
||||||
|
EncryptedCredentials,
|
||||||
|
} from "./keystore/index.js";
|
||||||
|
import { Password } from "./keystore/types.js";
|
||||||
|
import { extractMetaMaskSigner } from "./metamask.js";
|
||||||
import verificationKey from "./resources/verification_key.js";
|
import verificationKey from "./resources/verification_key.js";
|
||||||
import { RLNContract } from "./rln_contract.js";
|
import { RLNContract } from "./rln_contract.js";
|
||||||
import * as wc from "./witness_calculator.js";
|
import * as wc from "./witness_calculator.js";
|
||||||
@ -164,34 +178,180 @@ export function sha256(input: Uint8Array): Uint8Array {
|
|||||||
|
|
||||||
type StartRLNOptions = {
|
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.
|
* If not set - will use default SEPOLIA_CONTRACT address.
|
||||||
*/
|
*/
|
||||||
registryAddress?: string;
|
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?: EncryptedCredentials | DecryptedCredentials;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type RegisterMembershipOptions =
|
||||||
|
| { signature: string }
|
||||||
|
| { identity: IdentityCredential };
|
||||||
|
|
||||||
export class RLNInstance {
|
export class RLNInstance {
|
||||||
private _contract: null | RLNContract = null;
|
private started = false;
|
||||||
|
private starting = false;
|
||||||
|
|
||||||
|
private _contract: undefined | RLNContract;
|
||||||
|
private _signer: undefined | ethers.Signer;
|
||||||
|
|
||||||
|
private keystore = Keystore.create();
|
||||||
|
private _credentials: undefined | DecryptedCredentials;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private zkRLN: number,
|
private zkRLN: number,
|
||||||
private witnessCalculator: WitnessCalculator
|
private witnessCalculator: WitnessCalculator
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public get contract(): null | RLNContract {
|
public get contract(): undefined | RLNContract {
|
||||||
return this._contract;
|
return this._contract;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async start(options: StartRLNOptions = {}): Promise<void> {
|
public get signer(): undefined | ethers.Signer {
|
||||||
const provider = options.provider || (await extractMetaMaskAccount());
|
return this._signer;
|
||||||
const registryAddress = options.registryAddress || SEPOLIA_CONTRACT.address;
|
}
|
||||||
|
|
||||||
|
public async start(options: StartRLNOptions = {}): Promise<void> {
|
||||||
|
if (this.started || this.starting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.starting = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { signer, registryAddress } = await this.determineStartOptions(
|
||||||
|
options
|
||||||
|
);
|
||||||
|
|
||||||
|
this._signer = signer!;
|
||||||
this._contract = await RLNContract.init(this, {
|
this._contract = await RLNContract.init(this, {
|
||||||
|
registryAddress: registryAddress!,
|
||||||
|
signer: signer!,
|
||||||
|
});
|
||||||
|
this.started = true;
|
||||||
|
} finally {
|
||||||
|
this.starting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async determineStartOptions(
|
||||||
|
options: StartRLNOptions
|
||||||
|
): Promise<StartRLNOptions> {
|
||||||
|
const credentials = await this.decryptCredentialsIfNeeded(
|
||||||
|
options.credentials
|
||||||
|
);
|
||||||
|
|
||||||
|
let chainId = credentials?.membership.chainId;
|
||||||
|
const registryAddress =
|
||||||
|
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,
|
registryAddress,
|
||||||
provider,
|
credentials: options.credentials,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async decryptCredentialsIfNeeded(
|
||||||
|
credentials?: EncryptedCredentials | DecryptedCredentials
|
||||||
|
): Promise<undefined | DecryptedCredentials> {
|
||||||
|
if (!credentials) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("identity" in credentials) {
|
||||||
|
this._credentials = credentials;
|
||||||
|
return credentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keystore = Keystore.fromString(credentials.keystore);
|
||||||
|
|
||||||
|
if (!keystore) {
|
||||||
|
throw Error("Failed to start RLN: cannot read Keystore provided.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.keystore = keystore;
|
||||||
|
this._credentials = await keystore.readCredential(
|
||||||
|
credentials.id,
|
||||||
|
credentials.password
|
||||||
|
);
|
||||||
|
|
||||||
|
return this._credentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async registerMembership(
|
||||||
|
options: RegisterMembershipOptions
|
||||||
|
): Promise<undefined | DecryptedCredentials> {
|
||||||
|
if (!this.contract) {
|
||||||
|
throw Error("RLN Contract is not initialized.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let identity = "identity" in options && options.identity;
|
||||||
|
|
||||||
|
if ("signature" in options) {
|
||||||
|
identity = await this.generateSeededIdentityCredential(options.signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!identity) {
|
||||||
|
throw Error("Missing signature or identity to register membership.");
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
public async useCredentials(id: string, password: Password): Promise<void> {
|
||||||
|
this._credentials = await this.keystore?.readCredential(id, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@ describe("RLN Contract abstraction", () => {
|
|||||||
const voidSigner = new ethers.VoidSigner(rln.SEPOLIA_CONTRACT.address);
|
const voidSigner = new ethers.VoidSigner(rln.SEPOLIA_CONTRACT.address);
|
||||||
const rlnContract = new rln.RLNContract(rlnInstance, {
|
const rlnContract = new rln.RLNContract(rlnInstance, {
|
||||||
registryAddress: rln.SEPOLIA_CONTRACT.address,
|
registryAddress: rln.SEPOLIA_CONTRACT.address,
|
||||||
provider: voidSigner,
|
signer: voidSigner,
|
||||||
});
|
});
|
||||||
|
|
||||||
rlnContract["storageContract"] = {
|
rlnContract["storageContract"] = {
|
||||||
@ -32,7 +32,7 @@ describe("RLN Contract abstraction", () => {
|
|||||||
chai.expect(insertMemberSpy).to.have.been.called();
|
chai.expect(insertMemberSpy).to.have.been.called();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should register a member by signature", async () => {
|
it("should register a member", async () => {
|
||||||
const mockSignature =
|
const mockSignature =
|
||||||
"0xdeb8a6b00a8e404deb1f52d3aa72ed7f60a2ff4484c737eedaef18a0aacb2dfb4d5d74ac39bb71fa358cf2eb390565a35b026cc6272f2010d4351e17670311c21c";
|
"0xdeb8a6b00a8e404deb1f52d3aa72ed7f60a2ff4484c737eedaef18a0aacb2dfb4d5d74ac39bb71fa358cf2eb390565a35b026cc6272f2010d4351e17670311c21c";
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ describe("RLN Contract abstraction", () => {
|
|||||||
const voidSigner = new ethers.VoidSigner(rln.SEPOLIA_CONTRACT.address);
|
const voidSigner = new ethers.VoidSigner(rln.SEPOLIA_CONTRACT.address);
|
||||||
const rlnContract = new rln.RLNContract(rlnInstance, {
|
const rlnContract = new rln.RLNContract(rlnInstance, {
|
||||||
registryAddress: rln.SEPOLIA_CONTRACT.address,
|
registryAddress: rln.SEPOLIA_CONTRACT.address,
|
||||||
provider: voidSigner,
|
signer: voidSigner,
|
||||||
});
|
});
|
||||||
|
|
||||||
rlnContract["storageIndex"] = 1;
|
rlnContract["storageIndex"] = 1;
|
||||||
@ -57,7 +57,9 @@ describe("RLN Contract abstraction", () => {
|
|||||||
"register(uint16,uint256)"
|
"register(uint16,uint256)"
|
||||||
);
|
);
|
||||||
|
|
||||||
await rlnContract.registerWithSignature(rlnInstance, mockSignature);
|
const identity =
|
||||||
|
rlnInstance.generateSeededIdentityCredential(mockSignature);
|
||||||
|
await rlnContract.registerWithIdentity(identity);
|
||||||
|
|
||||||
chai.expect(contractSpy).to.have.been.called();
|
chai.expect(contractSpy).to.have.been.called();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,7 +3,8 @@ import { ethers } from "ethers";
|
|||||||
|
|
||||||
import { zeroPadLE } from "./byte_utils.js";
|
import { zeroPadLE } from "./byte_utils.js";
|
||||||
import { RLN_REGISTRY_ABI, RLN_STORAGE_ABI } from "./constants.js";
|
import { RLN_REGISTRY_ABI, RLN_STORAGE_ABI } from "./constants.js";
|
||||||
import { IdentityCredential, RLNInstance } from "./rln.js";
|
import type { DecryptedCredentials } from "./keystore/index.js";
|
||||||
|
import { type IdentityCredential, RLNInstance } from "./rln.js";
|
||||||
import { MerkleRootTracker } from "./root_tracker.js";
|
import { MerkleRootTracker } from "./root_tracker.js";
|
||||||
|
|
||||||
type Member = {
|
type Member = {
|
||||||
@ -11,10 +12,10 @@ type Member = {
|
|||||||
index: ethers.BigNumber;
|
index: ethers.BigNumber;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Provider = ethers.Signer | ethers.providers.Provider;
|
type Signer = ethers.Signer;
|
||||||
|
|
||||||
type RLNContractOptions = {
|
type RLNContractOptions = {
|
||||||
provider: Provider;
|
signer: Signer;
|
||||||
registryAddress: string;
|
registryAddress: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -47,7 +48,7 @@ export class RLNContract {
|
|||||||
): Promise<RLNContract> {
|
): Promise<RLNContract> {
|
||||||
const rlnContract = new RLNContract(rlnInstance, options);
|
const rlnContract = new RLNContract(rlnInstance, options);
|
||||||
|
|
||||||
await rlnContract.initStorageContract(options.provider);
|
await rlnContract.initStorageContract(options.signer);
|
||||||
await rlnContract.fetchMembers(rlnInstance);
|
await rlnContract.fetchMembers(rlnInstance);
|
||||||
rlnContract.subscribeToMembers(rlnInstance);
|
rlnContract.subscribeToMembers(rlnInstance);
|
||||||
|
|
||||||
@ -56,20 +57,20 @@ export class RLNContract {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
rlnInstance: RLNInstance,
|
rlnInstance: RLNInstance,
|
||||||
{ registryAddress, provider }: RLNContractOptions
|
{ registryAddress, signer }: RLNContractOptions
|
||||||
) {
|
) {
|
||||||
const initialRoot = rlnInstance.getMerkleRoot();
|
const initialRoot = rlnInstance.getMerkleRoot();
|
||||||
|
|
||||||
this.registryContract = new ethers.Contract(
|
this.registryContract = new ethers.Contract(
|
||||||
registryAddress,
|
registryAddress,
|
||||||
RLN_REGISTRY_ABI,
|
RLN_REGISTRY_ABI,
|
||||||
provider
|
signer
|
||||||
);
|
);
|
||||||
this.merkleRootTracker = new MerkleRootTracker(5, initialRoot);
|
this.merkleRootTracker = new MerkleRootTracker(5, initialRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async initStorageContract(
|
private async initStorageContract(
|
||||||
provider: Provider,
|
signer: Signer,
|
||||||
options: RLNStorageOptions = {}
|
options: RLNStorageOptions = {}
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const storageIndex = options?.storageIndex
|
const storageIndex = options?.storageIndex
|
||||||
@ -85,7 +86,7 @@ export class RLNContract {
|
|||||||
this.storageContract = new ethers.Contract(
|
this.storageContract = new ethers.Contract(
|
||||||
storageAddress,
|
storageAddress,
|
||||||
RLN_STORAGE_ABI,
|
RLN_STORAGE_ABI,
|
||||||
provider
|
signer
|
||||||
);
|
);
|
||||||
this._membersFilter = this.storageContract.filters.MemberRegistered();
|
this._membersFilter = this.storageContract.filters.MemberRegistered();
|
||||||
|
|
||||||
@ -207,19 +208,9 @@ export class RLNContract {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async registerWithSignature(
|
public async registerWithIdentity(
|
||||||
rlnInstance: RLNInstance,
|
identity: IdentityCredential
|
||||||
signature: string
|
): Promise<DecryptedCredentials | undefined> {
|
||||||
): Promise<Member | undefined> {
|
|
||||||
const identityCredential =
|
|
||||||
await rlnInstance.generateSeededIdentityCredential(signature);
|
|
||||||
|
|
||||||
return this.registerWithKey(identityCredential);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async registerWithKey(
|
|
||||||
credential: IdentityCredential
|
|
||||||
): Promise<Member | undefined> {
|
|
||||||
if (this.storageIndex === undefined) {
|
if (this.storageIndex === undefined) {
|
||||||
throw Error(
|
throw Error(
|
||||||
"Cannot register credential, no storage contract index found."
|
"Cannot register credential, no storage contract index found."
|
||||||
@ -228,7 +219,7 @@ export class RLNContract {
|
|||||||
const txRegisterResponse: ethers.ContractTransaction =
|
const txRegisterResponse: ethers.ContractTransaction =
|
||||||
await this.registryContract["register(uint16,uint256)"](
|
await this.registryContract["register(uint16,uint256)"](
|
||||||
this.storageIndex,
|
this.storageIndex,
|
||||||
credential.IDCommitmentBigInt,
|
identity.IDCommitmentBigInt,
|
||||||
{ gasLimit: 100000 }
|
{ gasLimit: 100000 }
|
||||||
);
|
);
|
||||||
const txRegisterReceipt = await txRegisterResponse.wait();
|
const txRegisterReceipt = await txRegisterResponse.wait();
|
||||||
@ -245,9 +236,17 @@ export class RLNContract {
|
|||||||
memberRegistered.data
|
memberRegistered.data
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const network = await this.registryContract.provider.getNetwork();
|
||||||
|
const address = this.registryContract.address;
|
||||||
|
const membershipId = decodedData.index.toNumber();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
idCommitment: decodedData.idCommitment,
|
identity,
|
||||||
index: decodedData.index,
|
membership: {
|
||||||
|
address,
|
||||||
|
treeIndex: membershipId,
|
||||||
|
chainId: network.chainId,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user