From aece0e3dc39a55145c101e8d404f4ab7dd784610 Mon Sep 17 00:00:00 2001 From: Sasha Date: Mon, 29 Jan 2024 02:04:29 +0100 Subject: [PATCH 1/8] featimprove start, types, import/export, add checks for netwrok, shortcuts for decoder encoder --- src/index.ts | 2 + src/keystore/index.ts | 2 + src/keystore/keystore.ts | 17 ++--- src/keystore/types.ts | 7 +++ src/metamask.ts | 27 ++++---- src/rln.ts | 131 ++++++++++++++++++++++++++++++++++++--- src/rln_contract.ts | 47 +++++++------- 7 files changed, 175 insertions(+), 58 deletions(-) diff --git a/src/index.ts b/src/index.ts index ae89bec..d0eed69 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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, }; diff --git a/src/keystore/index.ts b/src/keystore/index.ts index fc85b09..5dc3b56 100644 --- a/src/keystore/index.ts +++ b/src/keystore/index.ts @@ -1,3 +1,5 @@ import { Keystore } from "./keystore.js"; +import type { KeystoreEntity } from "./types.js"; export { Keystore }; +export type { KeystoreEntity }; diff --git a/src/keystore/keystore.ts b/src/keystore/keystore.ts index 4232aad..accd33f 100644 --- a/src/keystore/keystore.ts +++ b/src/keystore/keystore.ts @@ -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 { const membershipHash: MembershipHash = Keystore.computeMembershipHash( @@ -138,7 +133,7 @@ export class Keystore { public async readCredential( membershipHash: MembershipHash, password: Password - ): Promise { + ): Promise { 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, diff --git a/src/keystore/types.ts b/src/keystore/types.ts index 4873f97..b792920 100644 --- a/src/keystore/types.ts +++ b/src/keystore/types.ts @@ -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; +}; diff --git a/src/metamask.ts b/src/metamask.ts index e171205..f35423d 100644 --- a/src/metamask.ts +++ b/src/metamask.ts @@ -1,15 +1,16 @@ import { ethers } from "ethers"; -export const extractMetaMaskAccount = - async (): Promise => { - const ethereum = (window as any).ethereum; - - 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"); - }; +export const extractMetaMaskSigner = async (): Promise => { + const ethereum = (window as any).ethereum; + + if (!ethereum) { + throw Error( + "Missing or invalid Ethereum provider. Please install a Web3 wallet such as MetaMask." + ); + } + + await ethereum.request({ method: "eth_requestAccounts" }); + const provider = new ethers.providers.Web3Provider(ethereum, "any"); + + return provider.getSigner(); +}; diff --git a/src/rln.ts b/src/rln.ts index f96bdc4..4f87a39 100644 --- a/src/rln.ts +++ b/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 get signer(): undefined | ethers.Signer { + return this._signer; + } + public async start(options: StartRLNOptions = {}): Promise { - const provider = options.provider || (await extractMetaMaskAccount()); - const registryAddress = options.registryAddress || SEPOLIA_CONTRACT.address; + 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 { + 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; + } - this._contract = await RLNContract.init(this, { + 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 { + 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 { + return createRLNDecoder({ + rlnInstance: this, + decoder: createDecoder(contentTopic), }); } diff --git a/src/rln_contract.ts b/src/rln_contract.ts index 6a53fa9..6fc82e5 100644 --- a/src/rln_contract.ts +++ b/src/rln_contract.ts @@ -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 { 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 { 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 { - const identityCredential = - await rlnInstance.generateSeededIdentityCredential(signature); - - return this.registerWithKey(identityCredential); - } - - public async registerWithKey( - credential: IdentityCredential - ): Promise { + public async registerWithIdentity( + identity: IdentityCredential + ): Promise { 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, + }, }; } From 73e3b50ce8f8de44e8bb4e3857c8d0a776053db4 Mon Sep 17 00:00:00 2001 From: Sasha Date: Mon, 29 Jan 2024 10:30:33 +0100 Subject: [PATCH 2/8] fix type issue --- src/rln.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/rln.ts b/src/rln.ts index 4f87a39..a7c6d93 100644 --- a/src/rln.ts +++ b/src/rln.ts @@ -271,14 +271,14 @@ export class RLNInstance { throw Error("RLN Contract is not initialized."); } - if (!options.identity || !options.signature) { - throw Error("Missing signature or identity to register membership."); - } + let identity = "identity" in options && options.identity; - let identity = options.identity; + if ("signature" in options) { + identity = await this.generateSeededIdentityCredential(options.signature); + } - if (options.signature) { - identity = await this.generateSeededIdentityCredential(signature); + if (!identity) { + throw Error("Missing signature or identity to register membership."); } return this.contract.registerWithIdentity(identity); From c8f50ea916bb508809e6a1ed17be4b2bd8b787ff Mon Sep 17 00:00:00 2001 From: Sasha Date: Mon, 29 Jan 2024 10:34:59 +0100 Subject: [PATCH 3/8] update tests --- src/rln_contract.spec.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/rln_contract.spec.ts b/src/rln_contract.spec.ts index 7116e9b..5af6cfa 100644 --- a/src/rln_contract.spec.ts +++ b/src/rln_contract.spec.ts @@ -16,7 +16,7 @@ describe("RLN Contract abstraction", () => { const voidSigner = new ethers.VoidSigner(rln.SEPOLIA_CONTRACT.address); const rlnContract = new rln.RLNContract(rlnInstance, { registryAddress: rln.SEPOLIA_CONTRACT.address, - provider: voidSigner, + signer: voidSigner, }); rlnContract["storageContract"] = { @@ -32,7 +32,7 @@ describe("RLN Contract abstraction", () => { chai.expect(insertMemberSpy).to.have.been.called(); }); - it("should register a member by signature", async () => { + it("should register a member", async () => { const mockSignature = "0xdeb8a6b00a8e404deb1f52d3aa72ed7f60a2ff4484c737eedaef18a0aacb2dfb4d5d74ac39bb71fa358cf2eb390565a35b026cc6272f2010d4351e17670311c21c"; @@ -40,7 +40,7 @@ describe("RLN Contract abstraction", () => { const voidSigner = new ethers.VoidSigner(rln.SEPOLIA_CONTRACT.address); const rlnContract = new rln.RLNContract(rlnInstance, { registryAddress: rln.SEPOLIA_CONTRACT.address, - provider: voidSigner, + signer: voidSigner, }); rlnContract["storageIndex"] = 1; @@ -57,7 +57,9 @@ describe("RLN Contract abstraction", () => { "register(uint16,uint256)" ); - await rlnContract.registerWithSignature(rlnInstance, mockSignature); + const identity = + rlnInstance.generateSeededIdentityCredential(mockSignature); + await rlnContract.registerWithIdentity(identity); chai.expect(contractSpy).to.have.been.called(); }); From a545530e292d9e08387809b2daddb7a5be491178 Mon Sep 17 00:00:00 2001 From: Sasha Date: Mon, 29 Jan 2024 13:56:40 +0100 Subject: [PATCH 4/8] provide ability to use Keystore as a seed for credentials --- src/keystore/index.ts | 4 +-- src/keystore/keystore.ts | 16 ++++++---- src/keystore/types.ts | 17 ++++++++++ src/rln.ts | 67 +++++++++++++++++++++++++++++++++++----- 4 files changed, 88 insertions(+), 16 deletions(-) diff --git a/src/keystore/index.ts b/src/keystore/index.ts index 5dc3b56..e5d4abc 100644 --- a/src/keystore/index.ts +++ b/src/keystore/index.ts @@ -1,5 +1,5 @@ import { Keystore } from "./keystore.js"; -import type { KeystoreEntity } from "./types.js"; +import type { DecryptedCredentials, EncryptedCredentials } from "./types.js"; export { Keystore }; -export type { KeystoreEntity }; +export type { EncryptedCredentials, DecryptedCredentials }; diff --git a/src/keystore/keystore.ts b/src/keystore/keystore.ts index accd33f..5be4712 100644 --- a/src/keystore/keystore.ts +++ b/src/keystore/keystore.ts @@ -76,7 +76,9 @@ export class Keystore { 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 { const obj = JSON.parse(str); @@ -87,7 +89,7 @@ export class Keystore { return new Keystore(obj); } catch (err) { console.error("Cannot create Keystore from string:", err); - return null; + return; } } @@ -133,11 +135,11 @@ export class Keystore { public async readCredential( membershipHash: MembershipHash, password: Password - ): Promise { + ): Promise { const nwakuCredential = this.data.credentials[membershipHash]; if (!nwakuCredential) { - return null; + return; } const eipKeystore = Keystore.fromCredentialToEip(nwakuCredential); @@ -230,7 +232,9 @@ export class Keystore { }; } - private static fromBytesToIdentity(bytes: Uint8Array): null | KeystoreEntity { + private static fromBytesToIdentity( + bytes: Uint8Array + ): undefined | KeystoreEntity { try { const str = bytesToUtf8(bytes); const obj = JSON.parse(str); @@ -264,7 +268,7 @@ export class Keystore { }; } catch (err) { console.error("Cannot parse bytes to Nwaku Credentials:", err); - return null; + return; } } diff --git a/src/keystore/types.ts b/src/keystore/types.ts index b792920..417eb83 100644 --- a/src/keystore/types.ts +++ b/src/keystore/types.ts @@ -17,3 +17,20 @@ 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; +}; diff --git a/src/rln.ts b/src/rln.ts index a7c6d93..8bb4efa 100644 --- a/src/rln.ts +++ b/src/rln.ts @@ -14,7 +14,12 @@ 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 type { KeystoreEntity } from "./keystore/index.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 { RLNContract } from "./rln_contract.js"; @@ -184,7 +189,7 @@ type StartRLNOptions = { * 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; + credentials?: EncryptedCredentials | DecryptedCredentials; }; type RegisterMembershipOptions = @@ -197,7 +202,9 @@ export class RLNInstance { private _contract: undefined | RLNContract; private _signer: undefined | ethers.Signer; - private _credentials: undefined | KeystoreEntity; + + private _keystore: undefined | Keystore; + private _credentials: undefined | DecryptedCredentials; constructor( private zkRLN: number, @@ -212,6 +219,10 @@ export class RLNInstance { return this._signer; } + public get keystore(): undefined | Keystore { + return this._keystore; + } + public async start(options: StartRLNOptions = {}): Promise { if (this.started || this.starting) { return; @@ -220,11 +231,11 @@ export class RLNInstance { this.starting = true; try { - const { signer, credentials, registryAddress } = - await this.determineStartOptions(options); + const { signer, registryAddress } = await this.determineStartOptions( + options + ); this._signer = signer!; - this._credentials = credentials; this._contract = await RLNContract.init(this, { registryAddress: registryAddress!, signer: signer!, @@ -238,9 +249,13 @@ export class RLNInstance { private async determineStartOptions( options: StartRLNOptions ): Promise { - let chainId = options.credentials?.membership.chainId; + const credentials = await this.decryptCredentialsIfNeeded( + options.credentials + ); + + let chainId = credentials?.membership.chainId; const registryAddress = - options.credentials?.membership.address || + credentials?.membership.address || options.registryAddress || SEPOLIA_CONTRACT.address; @@ -264,6 +279,33 @@ export class RLNInstance { }; } + private async decryptCredentialsIfNeeded( + credentials?: EncryptedCredentials | DecryptedCredentials + ): Promise { + 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 { @@ -284,6 +326,15 @@ export class RLNInstance { 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 { + this._credentials = await this.keystore?.readCredential(id, password); + } + public createEncoder(options: WakuEncoderOptions): RLNEncoder { if (!this._credentials) { throw Error( From 62fec796f2aca15977fb21b2f2f28538836c006c Mon Sep 17 00:00:00 2001 From: Sasha Date: Mon, 29 Jan 2024 18:45:35 +0100 Subject: [PATCH 5/8] fix types --- src/rln.ts | 2 +- src/rln_contract.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rln.ts b/src/rln.ts index 8bb4efa..3f66ce0 100644 --- a/src/rln.ts +++ b/src/rln.ts @@ -308,7 +308,7 @@ export class RLNInstance { public async registerMembership( options: RegisterMembershipOptions - ): Promise { + ): Promise { if (!this.contract) { throw Error("RLN Contract is not initialized."); } diff --git a/src/rln_contract.ts b/src/rln_contract.ts index 6fc82e5..5570d77 100644 --- a/src/rln_contract.ts +++ b/src/rln_contract.ts @@ -3,7 +3,7 @@ import { ethers } from "ethers"; import { zeroPadLE } from "./byte_utils.js"; import { RLN_REGISTRY_ABI, RLN_STORAGE_ABI } from "./constants.js"; -import type { KeystoreEntity } from "./keystore/index.js"; +import type { DecryptedCredentials } from "./keystore/index.js"; import { type IdentityCredential, RLNInstance } from "./rln.js"; import { MerkleRootTracker } from "./root_tracker.js"; @@ -210,7 +210,7 @@ export class RLNContract { public async registerWithIdentity( identity: IdentityCredential - ): Promise { + ): Promise { if (this.storageIndex === undefined) { throw Error( "Cannot register credential, no storage contract index found." From e5920fc0616801a7e463b3fca804a93dd163e2cb Mon Sep 17 00:00:00 2001 From: Sasha Date: Mon, 29 Jan 2024 19:10:49 +0100 Subject: [PATCH 6/8] up test --- src/keystore/keystore.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/keystore/keystore.spec.ts b/src/keystore/keystore.spec.ts index cd28060..896759d 100644 --- a/src/keystore/keystore.spec.ts +++ b/src/keystore/keystore.spec.ts @@ -172,8 +172,8 @@ describe("Keystore", () => { }); it("should fail to create store from invalid string", () => { - expect(Keystore.fromString("/asdq}")).to.eq(null); - expect(Keystore.fromString('{ "name": "it" }')).to.eq(null); + expect(Keystore.fromString("/asdq}")).to.eq(undefined); + expect(Keystore.fromString('{ "name": "it" }')).to.eq(undefined); }); it("shoud create store from valid string", async () => { @@ -308,6 +308,6 @@ describe("Keystore", () => { const store = Keystore.fromObject(NWAKU_KEYSTORE as any); const result = await store.readCredential("wrong-hash", "wrong-password"); - expect(result).to.eq(null); + expect(result).to.eq(undefined); }); }); From eb119aa7635f8c4d4fa6882104f11d55ed83b214 Mon Sep 17 00:00:00 2001 From: Sasha Date: Mon, 29 Jan 2024 19:40:30 +0100 Subject: [PATCH 7/8] initialize keystore by default --- src/rln.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/rln.ts b/src/rln.ts index 3f66ce0..d3e99a1 100644 --- a/src/rln.ts +++ b/src/rln.ts @@ -203,7 +203,7 @@ export class RLNInstance { private _contract: undefined | RLNContract; private _signer: undefined | ethers.Signer; - private _keystore: undefined | Keystore; + private _keystore = Keystore.create(); private _credentials: undefined | DecryptedCredentials; constructor( @@ -219,7 +219,7 @@ export class RLNInstance { return this._signer; } - public get keystore(): undefined | Keystore { + public get keystore(): Keystore { return this._keystore; } From 17e48d12da10c2c74c626a98e7da20169ac5e4f8 Mon Sep 17 00:00:00 2001 From: Sasha Date: Tue, 30 Jan 2024 00:27:20 +0100 Subject: [PATCH 8/8] add keys operation to Keystore --- src/keystore/keystore.ts | 8 ++++++++ src/rln.ts | 8 ++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/keystore/keystore.ts b/src/keystore/keystore.ts index 5be4712..51714d4 100644 --- a/src/keystore/keystore.ts +++ b/src/keystore/keystore.ts @@ -164,6 +164,14 @@ export class Keystore { 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 { if (!isKeystoreValid(obj)) { return false; diff --git a/src/rln.ts b/src/rln.ts index d3e99a1..314c48d 100644 --- a/src/rln.ts +++ b/src/rln.ts @@ -203,7 +203,7 @@ export class RLNInstance { private _contract: undefined | RLNContract; private _signer: undefined | ethers.Signer; - private _keystore = Keystore.create(); + private keystore = Keystore.create(); private _credentials: undefined | DecryptedCredentials; constructor( @@ -219,10 +219,6 @@ export class RLNInstance { return this._signer; } - public get keystore(): Keystore { - return this._keystore; - } - public async start(options: StartRLNOptions = {}): Promise { if (this.started || this.starting) { return; @@ -297,7 +293,7 @@ export class RLNInstance { throw Error("Failed to start RLN: cannot read Keystore provided."); } - this._keystore = keystore; + this.keystore = keystore; this._credentials = await keystore.readCredential( credentials.id, credentials.password