2024-01-30 22:28:12 +01:00
import { createDecoder , createEncoder } from "@waku/core" ;
import type {
ContentTopic ,
IDecodedMessage ,
2024-02-16 00:29:21 +01:00
IRateLimitProof ,
2024-01-30 22:28:12 +01:00
EncoderOptions as WakuEncoderOptions ,
} 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-01-30 22:28:12 +01:00
import type { RLNDecoder , RLNEncoder } from "./codec.js" ;
import { createRLNDecoder , createRLNEncoder } from "./codec.js" ;
2024-01-24 21:23:52 +01:00
import { SEPOLIA_CONTRACT } from "./constants.js" ;
2024-02-15 23:56:26 +01:00
import { RLNContract } from "./contract/index.js" ;
2022-09-28 13:51:53 +10:00
import { dateToEpoch , epochIntToBytes } from "./epoch.js" ;
2024-01-30 22:28:12 +01:00
import { Keystore } from "./keystore/index.js" ;
import type {
DecryptedCredentials ,
EncryptedCredentials ,
} from "./keystore/index.js" ;
2024-02-13 22:19:35 +01:00
import { KeystoreEntity , Password } from "./keystore/types.js" ;
2024-02-16 00:29:21 +01:00
import { Proof , proofToBytes } from "./proof.js" ;
2022-09-26 10:51:04 +10:00
import verificationKey from "./resources/verification_key.js" ;
2024-02-16 00:19:42 +01:00
import {
buildBigIntFromUint8Array ,
2024-02-16 00:29:21 +01:00
concatenate ,
extractMetaMaskSigner ,
2024-02-16 00:19:42 +01:00
writeUIntLE ,
} from "./utils/index.js" ;
2022-09-23 16:53:00 +10:00
import * as wc from "./witness_calculator.js" ;
2022-09-27 22:41:25 +10:00
import { WitnessCalculator } from "./witness_calculator.js" ;
2022-09-06 16:40:19 -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 > {
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-15 23:56:26 +01:00
2022-09-24 13:59:13 -04:00
const witnessCalculator = await loadWitnessCalculator ( ) ;
const zkey = await loadZkey ( ) ;
2024-02-15 23:56:26 +01:00
const stringEncoder = new TextEncoder ( ) ;
2022-09-24 13:59:13 -04:00
const vkey = stringEncoder . encode ( JSON . stringify ( verificationKey ) ) ;
2024-02-15 23:56:26 +01:00
const DEPTH = 20 ;
2022-09-24 13:59:13 -04:00
const zkRLN = zerokitRLN . newRLN ( DEPTH , zkey , vkey ) ;
2024-02-15 23:56:26 +01:00
2022-09-06 16:40:19 -04:00
return new RLNInstance ( zkRLN , witnessCalculator ) ;
}
2023-05-08 18:10:26 -04:00
export class IdentityCredential {
2022-09-27 15:22:52 +10:00
constructor (
2023-05-08 18:10:26 -04:00
public readonly IDTrapdoor : Uint8Array ,
public readonly IDNullifier : Uint8Array ,
public readonly IDSecretHash : Uint8Array ,
2023-01-26 18:58:18 +01:00
public readonly IDCommitment : Uint8Array ,
public readonly IDCommitmentBigInt : bigint
2022-09-27 15:22:52 +10:00
) { }
2023-05-08 18:10:26 -04:00
static fromBytes ( memKeys : Uint8Array ) : IdentityCredential {
const idTrapdoor = memKeys . subarray ( 0 , 32 ) ;
const idNullifier = memKeys . subarray ( 32 , 64 ) ;
const idSecretHash = memKeys . subarray ( 64 , 96 ) ;
const idCommitment = memKeys . subarray ( 96 ) ;
2023-01-26 18:58:18 +01:00
const idCommitmentBigInt = buildBigIntFromUint8Array ( idCommitment ) ;
2023-05-08 18:10:26 -04:00
return new IdentityCredential (
idTrapdoor ,
idNullifier ,
idSecretHash ,
idCommitment ,
idCommitmentBigInt
) ;
2022-09-06 16:40:19 -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
2022-09-27 22:41:25 +10:00
constructor (
private zkRLN : number ,
private witnessCalculator : WitnessCalculator
) { }
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 ! ,
signer : signer ! ,
} ) ;
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-01-24 21:23:52 +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 ,
credentials : decryptedCredentials ,
} ;
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 ) {
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 ) ;
}
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 ,
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 ,
decoder : createDecoder ( contentTopic ) ,
2024-01-24 21:23:52 +01:00
} ) ;
}
2023-05-08 18:10:26 -04:00
generateIdentityCredentials ( ) : IdentityCredential {
const memKeys = zerokitRLN . generateExtendedMembershipKey ( this . zkRLN ) ; // TODO: rename this function in zerokit rln-wasm
return IdentityCredential . fromBytes ( memKeys ) ;
2022-09-06 16:40:19 -04:00
}
2023-05-08 18:10:26 -04:00
generateSeededIdentityCredential ( seed : string ) : IdentityCredential {
2024-02-15 23:56:26 +01:00
const stringEncoder = new TextEncoder ( ) ;
2022-12-08 14:52:04 +05:30
const seedBytes = stringEncoder . encode ( seed ) ;
2023-05-08 18:10:26 -04:00
// TODO: rename this function in zerokit rln-wasm
const memKeys = zerokitRLN . generateSeededExtendedMembershipKey (
2022-12-08 14:52:04 +05:30
this . zkRLN ,
seedBytes
) ;
2023-05-08 18:10:26 -04:00
return IdentityCredential . fromBytes ( memKeys ) ;
2022-12-08 14:52:04 +05:30
}
2022-09-23 21:35:17 -04:00
insertMember ( idCommitment : Uint8Array ) : void {
2022-09-06 16:40:19 -04:00
zerokitRLN . insertMember ( this . zkRLN , idCommitment ) ;
}
2023-05-08 18:10:26 -04:00
insertMembers ( index : number , . . . idCommitments : Array < Uint8Array > ) : void {
// serializes a seq of IDCommitments to a byte seq
// the order of serialization is |id_commitment_len<8>|id_commitment<var>|
const idCommitmentLen = writeUIntLE (
new Uint8Array ( 8 ) ,
idCommitments . length ,
0 ,
8
) ;
const idCommitmentBytes = concatenate ( idCommitmentLen , . . . idCommitments ) ;
zerokitRLN . setLeavesFrom ( this . zkRLN , index , idCommitmentBytes ) ;
}
deleteMember ( index : number ) : void {
zerokitRLN . deleteLeaf ( this . zkRLN , index ) ;
}
2022-10-07 14:49:32 -04:00
getMerkleRoot ( ) : Uint8Array {
return zerokitRLN . getRoot ( this . zkRLN ) ;
}
2022-09-06 16:40:19 -04:00
serializeMessage (
uint8Msg : Uint8Array ,
memIndex : number ,
epoch : Uint8Array ,
idKey : Uint8Array
) : Uint8Array {
// calculate message length
2022-09-22 11:55:14 -04:00
const msgLen = writeUIntLE ( new Uint8Array ( 8 ) , uint8Msg . length , 0 , 8 ) ;
2022-09-06 16:40:19 -04:00
// Converting index to LE bytes
2022-09-22 11:55:14 -04:00
const memIndexBytes = writeUIntLE ( new Uint8Array ( 8 ) , memIndex , 0 , 8 ) ;
2022-09-06 16:40:19 -04:00
// [ id_key<32> | id_index<8> | epoch<32> | signal_len<8> | signal<var> ]
return concatenate ( idKey , memIndexBytes , epoch , msgLen , uint8Msg ) ;
}
2022-10-01 08:02:13 -04:00
async generateRLNProof (
2022-09-06 16:40:19 -04:00
msg : Uint8Array ,
index : number ,
2022-09-22 11:55:14 -04:00
epoch : Uint8Array | Date | undefined ,
2023-05-08 18:10:26 -04:00
idSecretHash : Uint8Array
2023-04-19 20:02:13 +02:00
) : Promise < IRateLimitProof > {
2022-09-22 11:55:14 -04:00
if ( epoch == undefined ) {
2022-09-28 13:51:53 +10:00
epoch = epochIntToBytes ( dateToEpoch ( new Date ( ) ) ) ;
2022-09-22 11:55:14 -04:00
} else if ( epoch instanceof Date ) {
2022-09-28 13:51:53 +10:00
epoch = epochIntToBytes ( dateToEpoch ( epoch ) ) ;
2022-09-22 11:55:14 -04:00
}
2022-09-06 16:40:19 -04:00
if ( epoch . length != 32 ) throw "invalid epoch" ;
2023-05-08 18:10:26 -04:00
if ( idSecretHash . length != 32 ) throw "invalid id secret hash" ;
2022-09-06 16:40:19 -04:00
if ( index < 0 ) throw "index must be >= 0" ;
2023-05-08 18:10:26 -04:00
const serialized_msg = this . serializeMessage (
msg ,
index ,
epoch ,
idSecretHash
) ;
2022-09-06 16:40:19 -04:00
const rlnWitness = zerokitRLN . getSerializedRLNWitness (
this . zkRLN ,
serialized_msg
) ;
const inputs = zerokitRLN . RLNWitnessToJson ( this . zkRLN , rlnWitness ) ;
const calculatedWitness = await this . witnessCalculator . calculateWitness (
inputs ,
false
) ; // no sanity check being used in zerokit
2022-09-22 11:55:14 -04:00
const proofBytes = zerokitRLN . generate_rln_proof_with_witness (
2022-09-06 16:40:19 -04:00
this . zkRLN ,
calculatedWitness ,
rlnWitness
) ;
2022-09-22 11:55:14 -04:00
2022-09-26 14:58:03 -04:00
return new Proof ( proofBytes ) ;
2022-09-06 16:40:19 -04:00
}
2023-04-19 20:02:13 +02:00
verifyRLNProof (
proof : IRateLimitProof | Uint8Array ,
msg : Uint8Array
) : boolean {
2022-09-26 14:58:03 -04:00
let pBytes : Uint8Array ;
if ( proof instanceof Uint8Array ) {
pBytes = proof ;
} else {
pBytes = proofToBytes ( proof ) ;
2022-09-22 11:55:14 -04:00
}
2022-10-01 08:02:13 -04:00
// calculate message length
const msgLen = writeUIntLE ( new Uint8Array ( 8 ) , msg . length , 0 , 8 ) ;
return zerokitRLN . verifyRLNProof (
this . zkRLN ,
concatenate ( pBytes , msgLen , msg )
) ;
2022-09-06 16:40:19 -04:00
}
2022-10-07 14:49:32 -04:00
verifyWithRoots (
2023-04-19 20:02:13 +02:00
proof : IRateLimitProof | Uint8Array ,
2023-05-08 18:10:26 -04:00
msg : Uint8Array ,
. . . roots : Array < Uint8Array >
2022-10-07 14:49:32 -04:00
) : boolean {
let pBytes : Uint8Array ;
if ( proof instanceof Uint8Array ) {
pBytes = proof ;
} else {
pBytes = proofToBytes ( proof ) ;
}
// calculate message length
const msgLen = writeUIntLE ( new Uint8Array ( 8 ) , msg . length , 0 , 8 ) ;
2023-05-08 18:10:26 -04:00
const rootsBytes = concatenate ( . . . roots ) ;
2022-10-07 14:49:32 -04:00
return zerokitRLN . verifyWithRoots (
this . zkRLN ,
concatenate ( pBytes , msgLen , msg ) ,
2023-05-08 18:10:26 -04:00
rootsBytes
2022-10-07 14:49:32 -04:00
) ;
}
2022-10-10 09:59:24 -05:00
verifyWithNoRoot (
2023-04-19 20:02:13 +02:00
proof : IRateLimitProof | Uint8Array ,
2022-10-10 09:59:24 -05:00
msg : Uint8Array
) : boolean {
let pBytes : Uint8Array ;
if ( proof instanceof Uint8Array ) {
pBytes = proof ;
} else {
pBytes = proofToBytes ( proof ) ;
}
// calculate message length
const msgLen = writeUIntLE ( new Uint8Array ( 8 ) , msg . length , 0 , 8 ) ;
return zerokitRLN . verifyWithRoots (
this . zkRLN ,
concatenate ( pBytes , msgLen , msg ) ,
new Uint8Array ( )
) ;
}
2022-09-06 16:40:19 -04:00
}