provide ability to use Keystore as a seed for credentials

This commit is contained in:
Sasha 2024-01-29 13:56:40 +01:00
parent c8f50ea916
commit a545530e29
No known key found for this signature in database
4 changed files with 88 additions and 16 deletions

View File

@ -1,5 +1,5 @@
import { Keystore } from "./keystore.js"; import { Keystore } from "./keystore.js";
import type { KeystoreEntity } from "./types.js"; import type { DecryptedCredentials, EncryptedCredentials } from "./types.js";
export { Keystore }; export { Keystore };
export type { KeystoreEntity }; export type { EncryptedCredentials, DecryptedCredentials };

View File

@ -76,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);
@ -87,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;
} }
} }
@ -133,11 +135,11 @@ export class Keystore {
public async readCredential( public async readCredential(
membershipHash: MembershipHash, membershipHash: MembershipHash,
password: Password password: Password
): Promise<null | KeystoreEntity> { ): 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);
@ -230,7 +232,9 @@ export class Keystore {
}; };
} }
private static fromBytesToIdentity(bytes: Uint8Array): null | KeystoreEntity { private static fromBytesToIdentity(
bytes: Uint8Array
): undefined | KeystoreEntity {
try { try {
const str = bytesToUtf8(bytes); const str = bytesToUtf8(bytes);
const obj = JSON.parse(str); const obj = JSON.parse(str);
@ -264,7 +268,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;
} }
} }

View File

@ -17,3 +17,20 @@ export type KeystoreEntity = {
identity: IdentityCredential; identity: IdentityCredential;
membership: MembershipInfo; 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;
};

View File

@ -14,7 +14,12 @@ import type { RLNDecoder, RLNEncoder } from "./codec.js";
import { createRLNDecoder, createRLNEncoder } 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 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 { 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";
@ -184,7 +189,7 @@ type StartRLNOptions = {
* Credentials to use for generating proofs and connecting to the contract and network. * 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. * If provided used for validating the network chainId and connecting to registry contract.
*/ */
credentials?: KeystoreEntity; credentials?: EncryptedCredentials | DecryptedCredentials;
}; };
type RegisterMembershipOptions = type RegisterMembershipOptions =
@ -197,7 +202,9 @@ export class RLNInstance {
private _contract: undefined | RLNContract; private _contract: undefined | RLNContract;
private _signer: undefined | ethers.Signer; private _signer: undefined | ethers.Signer;
private _credentials: undefined | KeystoreEntity;
private _keystore: undefined | Keystore;
private _credentials: undefined | DecryptedCredentials;
constructor( constructor(
private zkRLN: number, private zkRLN: number,
@ -212,6 +219,10 @@ export class RLNInstance {
return this._signer; return this._signer;
} }
public get keystore(): undefined | Keystore {
return this._keystore;
}
public async start(options: StartRLNOptions = {}): Promise<void> { public async start(options: StartRLNOptions = {}): Promise<void> {
if (this.started || this.starting) { if (this.started || this.starting) {
return; return;
@ -220,11 +231,11 @@ export class RLNInstance {
this.starting = true; this.starting = true;
try { try {
const { signer, credentials, registryAddress } = const { signer, registryAddress } = await this.determineStartOptions(
await this.determineStartOptions(options); options
);
this._signer = signer!; this._signer = signer!;
this._credentials = credentials;
this._contract = await RLNContract.init(this, { this._contract = await RLNContract.init(this, {
registryAddress: registryAddress!, registryAddress: registryAddress!,
signer: signer!, signer: signer!,
@ -238,9 +249,13 @@ export class RLNInstance {
private async determineStartOptions( private async determineStartOptions(
options: StartRLNOptions options: StartRLNOptions
): Promise<StartRLNOptions> { ): Promise<StartRLNOptions> {
let chainId = options.credentials?.membership.chainId; const credentials = await this.decryptCredentialsIfNeeded(
options.credentials
);
let chainId = credentials?.membership.chainId;
const registryAddress = const registryAddress =
options.credentials?.membership.address || credentials?.membership.address ||
options.registryAddress || options.registryAddress ||
SEPOLIA_CONTRACT.address; SEPOLIA_CONTRACT.address;
@ -264,6 +279,33 @@ export class RLNInstance {
}; };
} }
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( public async registerMembership(
options: RegisterMembershipOptions options: RegisterMembershipOptions
): Promise<undefined | KeystoreEntity> { ): Promise<undefined | KeystoreEntity> {
@ -284,6 +326,15 @@ export class RLNInstance {
return this.contract.registerWithIdentity(identity); 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 { public createEncoder(options: WakuEncoderOptions): RLNEncoder {
if (!this._credentials) { if (!this._credentials) {
throw Error( throw Error(