2024-01-30 22:28:12 +01:00
import { createDecoder , createEncoder } from "@waku/core" ;
import type {
ContentTopic ,
IDecodedMessage ,
2024-03-01 01:07:33 +01:00
EncoderOptions as WakuEncoderOptions
2024-01-30 22:28:12 +01:00
} from "@waku/interfaces" ;
2023-10-28 14:19:56 +02:00
import init from "@waku/zerokit-rln-wasm" ;
2023-07-03 10:30:03 -04:00
import * as zerokitRLN from "@waku/zerokit-rln-wasm" ;
2024-01-24 21:23:52 +01:00
import { ethers } from "ethers" ;
2022-09-23 12:33:03 -04:00
2024-02-17 00:22:47 +01:00
import {
createRLNDecoder ,
createRLNEncoder ,
type RLNDecoder ,
2024-03-01 01:07:33 +01:00
type RLNEncoder
2024-02-17 00:22:47 +01:00
} from "./codec.js" ;
import { RLNContract , SEPOLIA_CONTRACT } from "./contract/index.js" ;
import { IdentityCredential } from "./identity.js" ;
2024-01-30 22:28:12 +01:00
import { Keystore } from "./keystore/index.js" ;
import type {
DecryptedCredentials ,
2024-03-01 01:07:33 +01:00
EncryptedCredentials
2024-01-30 22:28:12 +01:00
} from "./keystore/index.js" ;
2024-02-13 22:19:35 +01:00
import { KeystoreEntity , Password } from "./keystore/types.js" ;
2022-09-26 10:51:04 +10:00
import verificationKey from "./resources/verification_key.js" ;
2024-02-17 00:22:47 +01:00
import * as wc from "./resources/witness_calculator.js" ;
import { WitnessCalculator } from "./resources/witness_calculator.js" ;
import { extractMetaMaskSigner } from "./utils/index.js" ;
import { Zerokit } from "./zerokit.js" ;
2022-09-24 13:59:13 -04:00
2022-09-27 22:41:25 +10:00
async function loadWitnessCalculator ( ) : Promise < WitnessCalculator > {
2022-09-24 13:59:13 -04:00
const url = new URL ( "./resources/rln.wasm" , import . meta . url ) ;
const response = await fetch ( url ) ;
return await wc . builder ( new Uint8Array ( await response . arrayBuffer ( ) ) , false ) ;
}
async function loadZkey ( ) : Promise < Uint8Array > {
const url = new URL ( "./resources/rln_final.zkey" , import . meta . url ) ;
const response = await fetch ( url ) ;
return new Uint8Array ( await response . arrayBuffer ( ) ) ;
}
2022-09-06 16:40:19 -04:00
/ * *
* Create an instance of RLN
* @returns RLNInstance
* /
export async function create ( ) : Promise < RLNInstance > {
2024-03-01 01:07:33 +01:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2023-10-28 14:19:56 +02:00
await ( init as any ) ? . ( ) ;
2022-09-23 12:33:03 -04:00
zerokitRLN . init_panic_hook ( ) ;
2024-02-17 00:22:47 +01:00
2022-09-24 13:59:13 -04:00
const witnessCalculator = await loadWitnessCalculator ( ) ;
const zkey = await loadZkey ( ) ;
2022-09-06 16:40:19 -04:00
2024-02-17 00:22:47 +01:00
const stringEncoder = new TextEncoder ( ) ;
const vkey = stringEncoder . encode ( JSON . stringify ( verificationKey ) ) ;
2022-09-22 11:55:14 -04:00
2024-02-17 00:22:47 +01:00
const DEPTH = 20 ;
const zkRLN = zerokitRLN . newRLN ( DEPTH , zkey , vkey ) ;
const zerokit = new Zerokit ( zkRLN , witnessCalculator ) ;
2023-05-08 18:10:26 -04:00
2024-02-17 00:22:47 +01:00
return new RLNInstance ( zerokit ) ;
2023-05-08 18:10:26 -04:00
}
2024-01-24 21:23:52 +01:00
type StartRLNOptions = {
/ * *
2024-01-30 22:28:12 +01:00
* If not set - will extract MetaMask account and get signer from it .
2024-01-24 21:23:52 +01:00
* /
2024-01-30 22:28:12 +01:00
signer? : ethers.Signer ;
2024-01-24 21:23:52 +01:00
/ * *
* If not set - will use default SEPOLIA_CONTRACT address .
* /
registryAddress? : string ;
2024-01-30 22:28:12 +01:00
/ * *
* 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 ;
2024-01-24 21:23:52 +01:00
} ;
2024-01-30 22:28:12 +01:00
type RegisterMembershipOptions =
| { signature : string }
| { identity : IdentityCredential } ;
2024-02-13 22:19:35 +01:00
type WakuRLNEncoderOptions = WakuEncoderOptions & {
credentials : EncryptedCredentials | DecryptedCredentials ;
} ;
2022-09-22 11:55:14 -04:00
export class RLNInstance {
2024-01-30 22:28:12 +01:00
private started = false ;
private starting = false ;
private _contract : undefined | RLNContract ;
private _signer : undefined | ethers . Signer ;
private keystore = Keystore . create ( ) ;
private _credentials : undefined | DecryptedCredentials ;
2024-01-24 21:23:52 +01:00
2024-02-17 00:22:47 +01:00
constructor ( public zerokit : Zerokit ) { }
2022-09-22 11:55:14 -04:00
2024-01-30 22:28:12 +01:00
public get contract ( ) : undefined | RLNContract {
2024-01-24 21:23:52 +01:00
return this . _contract ;
}
2024-01-30 22:28:12 +01:00
public get signer ( ) : undefined | ethers . Signer {
return this . _signer ;
}
2024-01-24 21:23:52 +01:00
public async start ( options : StartRLNOptions = { } ) : Promise < void > {
2024-01-30 22:28:12 +01:00
if ( this . started || this . starting ) {
return ;
}
this . starting = true ;
try {
2024-02-13 22:19:35 +01:00
const { credentials , keystore } =
await RLNInstance . decryptCredentialsIfNeeded ( options . credentials ) ;
2024-01-30 22:28:12 +01:00
const { signer , registryAddress } = await this . determineStartOptions (
2024-02-13 22:19:35 +01:00
options ,
credentials
2024-01-30 22:28:12 +01:00
) ;
2024-02-13 22:19:35 +01:00
if ( keystore ) {
this . keystore = keystore ;
}
this . _credentials = credentials ;
2024-01-30 22:28:12 +01:00
this . _signer = signer ! ;
this . _contract = await RLNContract . init ( this , {
registryAddress : registryAddress ! ,
2024-03-01 01:07:33 +01:00
signer : signer !
2024-01-30 22:28:12 +01:00
} ) ;
this . started = true ;
} finally {
this . starting = false ;
}
}
2024-01-24 21:23:52 +01:00
2024-01-30 22:28:12 +01:00
private async determineStartOptions (
2024-02-13 22:19:35 +01:00
options : StartRLNOptions ,
credentials : KeystoreEntity | undefined
2024-01-30 22:28:12 +01:00
) : Promise < StartRLNOptions > {
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 ,
2024-03-01 01:07:33 +01:00
registryAddress
2024-01-30 22:28:12 +01:00
} ;
}
2024-02-13 22:19:35 +01:00
private static async decryptCredentialsIfNeeded (
2024-01-30 22:28:12 +01:00
credentials? : EncryptedCredentials | DecryptedCredentials
2024-02-13 22:19:35 +01:00
) : Promise < { credentials? : DecryptedCredentials ; keystore? : Keystore } > {
2024-01-30 22:28:12 +01:00
if ( ! credentials ) {
2024-02-13 22:19:35 +01:00
return { } ;
2024-01-30 22:28:12 +01:00
}
if ( "identity" in credentials ) {
2024-02-13 22:19:35 +01:00
return { credentials } ;
2024-01-30 22:28:12 +01:00
}
const keystore = Keystore . fromString ( credentials . keystore ) ;
if ( ! keystore ) {
2024-02-13 22:19:35 +01:00
return { } ;
2024-01-30 22:28:12 +01:00
}
2024-02-13 22:19:35 +01:00
const decryptedCredentials = await keystore . readCredential (
2024-01-30 22:28:12 +01:00
credentials . id ,
credentials . password
) ;
2024-02-13 22:19:35 +01:00
return {
keystore ,
2024-03-01 01:07:33 +01:00
credentials : decryptedCredentials
2024-02-13 22:19:35 +01:00
} ;
2024-01-30 22:28:12 +01:00
}
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 ) {
2024-03-01 01:07:33 +01:00
identity = this . zerokit . generateSeededIdentityCredential (
2024-02-17 00:22:47 +01:00
options . signature
) ;
2024-01-30 22:28:12 +01:00
}
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 ) ;
}
2024-02-13 22:19:35 +01:00
public async createEncoder (
options : WakuRLNEncoderOptions
) : Promise < RLNEncoder > {
const { credentials : decryptedCredentials } =
await RLNInstance . decryptCredentialsIfNeeded ( options . credentials ) ;
const credentials = decryptedCredentials || this . _credentials ;
if ( ! credentials ) {
2024-01-30 22:28:12 +01:00
throw Error (
"Failed to create Encoder: missing RLN credentials. Use createRLNEncoder directly."
) ;
}
2024-02-13 22:19:35 +01:00
await this . verifyCredentialsAgainstContract ( credentials ) ;
2024-01-30 22:28:12 +01:00
return createRLNEncoder ( {
encoder : createEncoder ( options ) ,
rlnInstance : this ,
2024-02-13 22:19:35 +01:00
index : credentials.membership.treeIndex ,
2024-03-01 01:07:33 +01:00
credential : credentials.identity
2024-01-30 22:28:12 +01:00
} ) ;
}
2024-02-13 22:19:35 +01:00
private async verifyCredentialsAgainstContract (
credentials : KeystoreEntity
) : Promise < void > {
if ( ! this . _contract ) {
throw Error (
"Failed to verify chain coordinates: no contract initialized."
) ;
}
const registryAddress = credentials . membership . address ;
const currentRegistryAddress = this . _contract . registry . 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 ;
2024-02-14 00:13:49 +01:00
const network = await this . _contract . registry . provider . getNetwork ( ) ;
2024-02-13 22:19:35 +01:00
const currentChainId = network . chainId ;
if ( chainId !== currentChainId ) {
throw Error (
` Failed to verify chain coordinates: credentials chainID= ${ chainId } is not equal to registryContract chainID= ${ currentChainId } `
) ;
}
}
2024-01-30 22:28:12 +01:00
public createDecoder (
contentTopic : ContentTopic
) : RLNDecoder < IDecodedMessage > {
return createRLNDecoder ( {
rlnInstance : this ,
2024-03-01 01:07:33 +01:00
decoder : createDecoder ( contentTopic )
2024-01-24 21:23:52 +01:00
} ) ;
}
2022-09-06 16:40:19 -04:00
}