feat: use Proxy contract address, add fixes to RLN Contract (#74)

* use Proxy contract address, add fixes to RLN Contract

* whitelist word

* update tesst

* up mock

* up

* fixes to contract, keystore and utils

* whitelist word

* update tests

* move to hash map

* up mock

* up
This commit is contained in:
Sasha 2023-10-19 03:23:05 +02:00 committed by GitHub
parent 89283768c3
commit fa49e29856
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 121 additions and 57 deletions

View File

@ -20,7 +20,9 @@
"kdfparams",
"ciphertext",
"cipherparams",
"codegen"
"codegen",
"hexlify",
"Arraylike"
],
"flagWords": [],
"ignorePaths": [

View File

@ -63,6 +63,6 @@ export const RLN_STORAGE_ABI = [
export const SEPOLIA_CONTRACT = {
chainId: 11155111,
address: "0xF1935b338321013f11068abCafC548A7B0db732C",
address: "0xF471d71E9b1455bBF4b85d475afb9BB0954A29c4",
abi: RLN_REGISTRY_ABI,
};

View File

@ -202,25 +202,25 @@ describe("Keystore", () => {
const expectedHash =
"9DB2B4718A97485B9F70F68D1CC19F4E10F0B4CE943418838E94956CB8E57548";
const identity = {
IDTrapdoor: [
IDTrapdoor: new Uint8Array([
211, 23, 66, 42, 179, 130, 131, 111, 201, 205, 244, 34, 27, 238, 244,
216, 131, 240, 188, 45, 193, 172, 4, 168, 225, 225, 43, 197, 114, 176,
126, 9,
],
IDNullifier: [
]),
IDNullifier: new Uint8Array([
238, 168, 239, 65, 73, 63, 105, 19, 132, 62, 213, 205, 191, 255, 209, 9,
178, 155, 239, 201, 131, 125, 233, 136, 246, 217, 9, 237, 55, 89, 81,
42,
],
IDSecretHash: [
]),
IDSecretHash: new Uint8Array([
150, 54, 194, 28, 18, 216, 138, 253, 95, 139, 120, 109, 98, 129, 146,
101, 41, 194, 36, 36, 96, 152, 152, 89, 151, 160, 118, 15, 222, 124,
187, 4,
],
IDCommitment: [
]),
IDCommitment: new Uint8Array([
112, 216, 27, 89, 188, 135, 203, 19, 168, 211, 117, 13, 231, 135, 229,
58, 94, 20, 246, 8, 33, 65, 238, 37, 112, 97, 65, 241, 255, 93, 171, 15,
],
]),
IDCommitmentBigInt: buildBigIntFromUint8Array(
new Uint8Array([
112, 216, 27, 89, 188, 135, 203, 19, 168, 211, 117, 13, 231, 135, 229,

View File

@ -245,13 +245,23 @@ export class Keystore {
// TODO: add runtime validation of nwaku credentials
return {
identity: {
IDCommitment: _.get(obj, "identityCredential.idCommitment"),
IDTrapdoor: _.get(obj, "identityCredential.idTrapdoor"),
IDNullifier: _.get(obj, "identityCredential.idNullifier"),
IDCommitmentBigInt: buildBigIntFromUint8Array(
new Uint8Array(_.get(obj, "identityCredential.idCommitment", []))
IDCommitment: Keystore.fromArraylikeToBytes(
_.get(obj, "identityCredential.idCommitment", [])
),
IDTrapdoor: Keystore.fromArraylikeToBytes(
_.get(obj, "identityCredential.idTrapdoor", [])
),
IDNullifier: Keystore.fromArraylikeToBytes(
_.get(obj, "identityCredential.idNullifier", [])
),
IDCommitmentBigInt: buildBigIntFromUint8Array(
Keystore.fromArraylikeToBytes(
_.get(obj, "identityCredential.idCommitment", [])
)
),
IDSecretHash: Keystore.fromArraylikeToBytes(
_.get(obj, "identityCredential.idSecretHash", [])
),
IDSecretHash: _.get(obj, "identityCredential.idSecretHash"),
},
membership: {
treeIndex: _.get(obj, "treeIndex"),
@ -265,6 +275,23 @@ export class Keystore {
}
}
private static fromArraylikeToBytes(obj: {
[key: number]: number;
}): Uint8Array {
const bytes = [];
let index = 0;
let lastElement = obj[index];
while (lastElement !== undefined) {
bytes.push(lastElement);
index += 1;
lastElement = obj[index];
}
return new Uint8Array(bytes);
}
// follows nwaku implementation
// https://github.com/waku-org/nwaku/blob/f05528d4be3d3c876a8b07f9bb7dfaae8aa8ec6e/waku/waku_keystore/protocol_types.nim#L111
private static computeMembershipHash(info: MembershipInfo): MembershipHash {

View File

@ -15,7 +15,7 @@ describe("RLN Contract abstraction", () => {
const voidSigner = new ethers.VoidSigner(rln.SEPOLIA_CONTRACT.address);
const rlnContract = new rln.RLNContract(rlnInstance, {
address: rln.SEPOLIA_CONTRACT.address,
registryAddress: rln.SEPOLIA_CONTRACT.address,
provider: voidSigner,
});
@ -39,7 +39,7 @@ describe("RLN Contract abstraction", () => {
const rlnInstance = await rln.create();
const voidSigner = new ethers.VoidSigner(rln.SEPOLIA_CONTRACT.address);
const rlnContract = new rln.RLNContract(rlnInstance, {
address: rln.SEPOLIA_CONTRACT.address,
registryAddress: rln.SEPOLIA_CONTRACT.address,
provider: voidSigner,
});
@ -49,12 +49,12 @@ describe("RLN Contract abstraction", () => {
topics: [],
} as unknown as ethers.EventFilter;
rlnContract["registryContract"] = {
register: () =>
"register(uint16,uint256)": () =>
Promise.resolve({ wait: () => Promise.resolve(undefined) }),
} as unknown as ethers.Contract;
const contractSpy = chai.spy.on(
rlnContract["registryContract"],
"register"
"register(uint16,uint256)"
);
await rlnContract.registerWithSignature(rlnInstance, mockSignature);
@ -66,8 +66,8 @@ describe("RLN Contract abstraction", () => {
function mockEvent(): ethers.Event {
return {
args: {
pubkey: "0x9e7d3f8f8c7a1d2bef96a2e8dbb8e7c1ea9a9ab78d6b3c6c3c",
index: 1,
idCommitment: "0x9e7d3f8f8c7a1d2bef96a2e8dbb8e7c1ea9a9ab78d6b3c6c3c",
index: ethers.BigNumber.from(1),
},
} as unknown as ethers.Event;
}

View File

@ -5,17 +5,23 @@ import { IdentityCredential, RLNInstance } from "./rln.js";
import { MerkleRootTracker } from "./root_tracker.js";
type Member = {
pubkey: string;
index: number;
idCommitment: string;
index: ethers.BigNumber;
};
type Provider = ethers.Signer | ethers.providers.Provider;
type ContractOptions = {
address: string;
type RLNContractOptions = {
provider: Provider;
registryAddress: string;
};
type RLNStorageOptions = {
storageIndex?: number;
};
type RLNContractInitOptions = RLNContractOptions & RLNStorageOptions;
type FetchMembersOptions = {
fromBlock?: number;
fetchRange?: number;
@ -31,11 +37,11 @@ export class RLNContract {
private storageContract: undefined | ethers.Contract;
private _membersFilter: undefined | ethers.EventFilter;
private _members: Member[] = [];
private _members: Map<number, Member> = new Map();
public static async init(
rlnInstance: RLNInstance,
options: ContractOptions
options: RLNContractInitOptions
): Promise<RLNContract> {
const rlnContract = new RLNContract(rlnInstance, options);
@ -48,25 +54,34 @@ export class RLNContract {
constructor(
rlnInstance: RLNInstance,
{ address, provider }: ContractOptions
{ registryAddress, provider }: RLNContractOptions
) {
const initialRoot = rlnInstance.getMerkleRoot();
this.registryContract = new ethers.Contract(
address,
registryAddress,
RLN_REGISTRY_ABI,
provider
);
this.merkleRootTracker = new MerkleRootTracker(5, initialRoot);
}
private async initStorageContract(provider: Provider): Promise<void> {
const index = await this.registryContract.usingStorageIndex();
const address = await this.registryContract.storages(index);
private async initStorageContract(
provider: Provider,
options: RLNStorageOptions = {}
): Promise<void> {
const storageIndex = options?.storageIndex
? options.storageIndex
: await this.registryContract.usingStorageIndex();
const storageAddress = await this.registryContract.storages(storageIndex);
this.storageIndex = index;
if (!storageAddress || storageAddress === ethers.constants.AddressZero) {
throw Error("No RLN Storage initialized on registry contract.");
}
this.storageIndex = storageIndex;
this.storageContract = new ethers.Contract(
address,
storageAddress,
RLN_STORAGE_ABI,
provider
);
@ -77,13 +92,16 @@ export class RLNContract {
public get contract(): ethers.Contract {
if (!this.storageContract) {
throw Error("Storage contract was not initialized.");
throw Error("Storage contract was not initialized");
}
return this.storageContract as ethers.Contract;
}
public get members(): Member[] {
return this._members;
const sortedMembers = Array.from(this._members.values()).sort(
(left, right) => left.index.toNumber() - right.index.toNumber()
);
return sortedMembers;
}
private get membersFilter(): ethers.EventFilter {
@ -115,13 +133,13 @@ export class RLNContract {
}
if (evt.removed) {
const index: number = evt.args.index;
const index: ethers.BigNumber = evt.args.index;
const toRemoveVal = toRemoveTable.get(evt.blockNumber);
if (toRemoveVal != undefined) {
toRemoveVal.push(index);
toRemoveVal.push(index.toNumber());
toRemoveTable.set(evt.blockNumber, toRemoveVal);
} else {
toRemoveTable.set(evt.blockNumber, [index]);
toRemoveTable.set(evt.blockNumber, [index.toNumber()]);
}
} else {
let eventsPerBlock = toInsertTable.get(evt.blockNumber);
@ -144,18 +162,23 @@ export class RLNContract {
): void {
toInsert.forEach((events: ethers.Event[], blockNumber: number) => {
events.forEach((evt) => {
if (!evt.args) {
const _idCommitment = evt?.args?.idCommitment;
const index: ethers.BigNumber = evt?.args?.index;
if (!_idCommitment || !index) {
return;
}
const pubkey = evt.args.pubkey;
const index = evt.args.index;
const idCommitment = ethers.utils.zeroPad(
ethers.utils.arrayify(pubkey),
ethers.utils.arrayify(_idCommitment),
32
);
rlnInstance.insertMember(idCommitment);
this.members.push({ index, pubkey });
this._members.set(index.toNumber(), {
index,
idCommitment:
_idCommitment?._hex || ethers.utils.hexlify(idCommitment),
});
});
const currentRoot = rlnInstance.getMerkleRoot();
@ -170,9 +193,8 @@ export class RLNContract {
const removeDescending = new Map([...toRemove].sort().reverse());
removeDescending.forEach((indexes: number[], blockNumber: number) => {
indexes.forEach((index) => {
const idx = this.members.findIndex((m) => m.index === index);
if (idx > -1) {
this.members.splice(idx, 1);
if (this._members.has(index)) {
this._members.delete(index);
}
rlnInstance.deleteMember(index);
});
@ -183,14 +205,14 @@ export class RLNContract {
public subscribeToMembers(rlnInstance: RLNInstance): void {
this.contract.on(this.membersFilter, (_pubkey, _index, event) =>
this.processEvents(rlnInstance, event)
this.processEvents(rlnInstance, [event])
);
}
public async registerWithSignature(
rlnInstance: RLNInstance,
signature: string
): Promise<ethers.Event | undefined> {
): Promise<Member | undefined> {
const identityCredential =
await rlnInstance.generateSeededIdentityCredential(signature);
@ -199,23 +221,36 @@ export class RLNContract {
public async registerWithKey(
credential: IdentityCredential
): Promise<ethers.Event | undefined> {
if (!this.storageIndex) {
): Promise<Member | undefined> {
if (this.storageIndex === undefined) {
throw Error(
"Cannot register credential, no storage contract index found."
);
}
const txRegisterResponse: ethers.ContractTransaction =
await this.registryContract.register(
await this.registryContract["register(uint16,uint256)"](
this.storageIndex,
credential.IDCommitmentBigInt,
{
gasLimit: 100000,
}
{ gasLimit: 100000 }
);
const txRegisterReceipt = await txRegisterResponse.wait();
return txRegisterReceipt?.events?.[0];
// assumption: register(uint16,uint256) emits one event
const memberRegistered = txRegisterReceipt?.events?.[0];
if (!memberRegistered) {
return undefined;
}
const decodedData = this.contract.interface.decodeEventLog(
"MemberRegistered",
memberRegistered.data
);
return {
idCommitment: decodedData.idCommitment,
index: decodedData.index,
};
}
public roots(): Uint8Array[] {