mirror of
https://github.com/logos-messaging/js-waku.git
synced 2026-01-02 13:53:12 +00:00
fix: store merkle proof/root as constant, remove use of RPC in proof gen/verification test
This commit is contained in:
parent
30ca244c32
commit
f7d7673e6f
1557
package-lock.json
generated
1557
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,6 @@
|
||||
import { expect, use } from "chai";
|
||||
import chaiAsPromised from "chai-as-promised";
|
||||
import sinon from "sinon";
|
||||
import { PublicClient } from "viem";
|
||||
|
||||
import { RLNBaseContract } from "./rln_base_contract.js";
|
||||
|
||||
@ -9,29 +8,17 @@ use(chaiAsPromised);
|
||||
|
||||
function createMockRLNBaseContract(
|
||||
mockContract: any,
|
||||
<<<<<<< HEAD
|
||||
mockRpcClient: any
|
||||
): RLNBaseContract {
|
||||
const dummy = Object.create(RLNBaseContract.prototype);
|
||||
dummy.contract = mockContract;
|
||||
dummy.rpcClient = mockRpcClient;
|
||||
=======
|
||||
mockPublicClient: PublicClient
|
||||
): RLNBaseContract {
|
||||
const dummy = Object.create(RLNBaseContract.prototype);
|
||||
dummy.contract = mockContract;
|
||||
dummy.publicClient = mockPublicClient;
|
||||
>>>>>>> a88dd8cdbd (feat: migrate rln from ethers to viem)
|
||||
return dummy as RLNBaseContract;
|
||||
}
|
||||
|
||||
describe("RLNBaseContract.getPriceForRateLimit (unit)", function () {
|
||||
let mockContract: any;
|
||||
<<<<<<< HEAD
|
||||
let mockRpcClient: any;
|
||||
=======
|
||||
let mockPublicClient: any;
|
||||
>>>>>>> a88dd8cdbd (feat: migrate rln from ethers to viem)
|
||||
let priceCalculatorReadStub: sinon.SinonStub;
|
||||
let readContractStub: sinon.SinonStub;
|
||||
|
||||
@ -45,11 +32,7 @@ describe("RLNBaseContract.getPriceForRateLimit (unit)", function () {
|
||||
}
|
||||
};
|
||||
|
||||
<<<<<<< HEAD
|
||||
mockRpcClient = {
|
||||
=======
|
||||
mockPublicClient = {
|
||||
>>>>>>> a88dd8cdbd (feat: migrate rln from ethers to viem)
|
||||
readContract: readContractStub
|
||||
};
|
||||
});
|
||||
@ -66,11 +49,7 @@ describe("RLNBaseContract.getPriceForRateLimit (unit)", function () {
|
||||
priceCalculatorReadStub.resolves(priceCalculatorAddress);
|
||||
readContractStub.resolves([fakeToken, fakePrice]);
|
||||
|
||||
<<<<<<< HEAD
|
||||
const rlnBase = createMockRLNBaseContract(mockContract, mockRpcClient);
|
||||
=======
|
||||
const rlnBase = createMockRLNBaseContract(mockContract, mockPublicClient);
|
||||
>>>>>>> a88dd8cdbd (feat: migrate rln from ethers to viem)
|
||||
const result = await rlnBase.getPriceForRateLimit(20);
|
||||
|
||||
expect(result.token).to.equal(fakeToken);
|
||||
@ -92,11 +71,7 @@ describe("RLNBaseContract.getPriceForRateLimit (unit)", function () {
|
||||
priceCalculatorReadStub.resolves(priceCalculatorAddress);
|
||||
readContractStub.rejects(new Error("fail"));
|
||||
|
||||
<<<<<<< HEAD
|
||||
const rlnBase = createMockRLNBaseContract(mockContract, mockRpcClient);
|
||||
=======
|
||||
const rlnBase = createMockRLNBaseContract(mockContract, mockPublicClient);
|
||||
>>>>>>> a88dd8cdbd (feat: migrate rln from ethers to viem)
|
||||
await expect(rlnBase.getPriceForRateLimit(20)).to.be.rejectedWith("fail");
|
||||
|
||||
expect(priceCalculatorReadStub.calledOnce).to.be.true;
|
||||
@ -109,11 +84,7 @@ describe("RLNBaseContract.getPriceForRateLimit (unit)", function () {
|
||||
priceCalculatorReadStub.resolves(priceCalculatorAddress);
|
||||
readContractStub.resolves([null, null]);
|
||||
|
||||
<<<<<<< HEAD
|
||||
const rlnBase = createMockRLNBaseContract(mockContract, mockRpcClient);
|
||||
=======
|
||||
const rlnBase = createMockRLNBaseContract(mockContract, mockPublicClient);
|
||||
>>>>>>> a88dd8cdbd (feat: migrate rln from ethers to viem)
|
||||
const result = await rlnBase.getPriceForRateLimit(20);
|
||||
|
||||
expect(result.token).to.be.null;
|
||||
|
||||
@ -1,187 +0,0 @@
|
||||
import { expect } from "chai";
|
||||
import { type Address, createPublicClient, http } from "viem";
|
||||
import { lineaSepolia } from "viem/chains";
|
||||
|
||||
import { Keystore } from "../keystore/index.js";
|
||||
import { RLNInstance } from "../rln.js";
|
||||
import { BytesUtils } from "../utils/index.js";
|
||||
import {
|
||||
calculateRateCommitment,
|
||||
extractPathDirectionsFromProof,
|
||||
MERKLE_TREE_DEPTH,
|
||||
reconstructMerkleRoot
|
||||
} from "../utils/merkle.js";
|
||||
import { TEST_KEYSTORE_DATA } from "../utils/test_keystore.js";
|
||||
|
||||
import { RLN_CONTRACT } from "./constants.js";
|
||||
import { RLNBaseContract } from "./rln_base_contract.js";
|
||||
|
||||
describe("RLN Proof Integration Tests", function () {
|
||||
this.timeout(30000);
|
||||
|
||||
let rpcUrl: string;
|
||||
|
||||
before(async function () {
|
||||
this.timeout(10000); // Allow time for WASM initialization
|
||||
|
||||
// Initialize WASM module before running tests
|
||||
await RLNInstance.create();
|
||||
|
||||
rpcUrl = process.env.RPC_URL || "https://rpc.sepolia.linea.build";
|
||||
|
||||
if (!rpcUrl) {
|
||||
console.log(
|
||||
"Skipping integration tests - RPC_URL environment variable not set"
|
||||
);
|
||||
console.log(
|
||||
"To run these tests, set RPC_URL to a Linea Sepolia RPC endpoint"
|
||||
);
|
||||
this.skip();
|
||||
}
|
||||
});
|
||||
|
||||
it("get merkle proof from contract, construct rln proof, verify rln proof", async function () {
|
||||
// Load the test keystore from constant (browser-compatible)
|
||||
const keystore = Keystore.fromString(TEST_KEYSTORE_DATA.keystoreJson);
|
||||
if (!keystore) {
|
||||
throw new Error("Failed to load test keystore");
|
||||
}
|
||||
|
||||
// Use the known credential hash and password from the test data
|
||||
const credentialHash = TEST_KEYSTORE_DATA.credentialHash;
|
||||
const password = TEST_KEYSTORE_DATA.password;
|
||||
const credential = await keystore.readCredential(credentialHash, password);
|
||||
if (!credential) {
|
||||
throw new Error("Failed to unlock credential with provided password");
|
||||
}
|
||||
|
||||
const idCommitment = credential.identity.IDCommitmentBigInt;
|
||||
|
||||
const publicClient = createPublicClient({
|
||||
chain: lineaSepolia,
|
||||
transport: http(rpcUrl)
|
||||
});
|
||||
|
||||
const dummyWalletClient = createPublicClient({
|
||||
chain: lineaSepolia,
|
||||
transport: http(rpcUrl)
|
||||
}) as any;
|
||||
|
||||
const contract = await RLNBaseContract.create({
|
||||
address: RLN_CONTRACT.address as Address,
|
||||
publicClient,
|
||||
walletClient: dummyWalletClient
|
||||
});
|
||||
|
||||
const membershipInfo = await contract.getMembershipInfo(idCommitment);
|
||||
|
||||
if (!membershipInfo) {
|
||||
throw new Error(
|
||||
`ID commitment ${idCommitment.toString()} not found in membership set`
|
||||
);
|
||||
}
|
||||
|
||||
const merkleProof = await contract.getMerkleProof(membershipInfo.index);
|
||||
|
||||
expect(merkleProof).to.be.an("array");
|
||||
expect(merkleProof).to.have.lengthOf(MERKLE_TREE_DEPTH); // RLN uses fixed depth merkle tree
|
||||
|
||||
merkleProof.forEach((element, i) => {
|
||||
console.log(
|
||||
` [${i}]: ${element.toString()} (0x${element.toString(16)})`
|
||||
);
|
||||
});
|
||||
|
||||
merkleProof.forEach((element, i) => {
|
||||
expect(element).to.be.a(
|
||||
"bigint",
|
||||
`Proof element ${i} should be a bigint`
|
||||
);
|
||||
expect(element).to.not.equal(0n, `Proof element ${i} should not be zero`);
|
||||
});
|
||||
});
|
||||
|
||||
it("should generate a valid RLN proof", async function () {
|
||||
const publicClient = createPublicClient({
|
||||
chain: lineaSepolia,
|
||||
transport: http(rpcUrl)
|
||||
});
|
||||
|
||||
const dummyWalletClient = createPublicClient({
|
||||
chain: lineaSepolia,
|
||||
transport: http(rpcUrl)
|
||||
}) as any;
|
||||
|
||||
const contract = await RLNBaseContract.create({
|
||||
address: RLN_CONTRACT.address as Address,
|
||||
publicClient,
|
||||
walletClient: dummyWalletClient
|
||||
});
|
||||
const keystore = Keystore.fromString(TEST_KEYSTORE_DATA.keystoreJson);
|
||||
if (!keystore) {
|
||||
throw new Error("Failed to load test keystore");
|
||||
}
|
||||
const credentialHash = TEST_KEYSTORE_DATA.credentialHash;
|
||||
const password = TEST_KEYSTORE_DATA.password;
|
||||
const credential = await keystore.readCredential(credentialHash, password);
|
||||
if (!credential) {
|
||||
throw new Error("Failed to unlock credential with provided password");
|
||||
}
|
||||
const idCommitment = credential.identity.IDCommitmentBigInt;
|
||||
const membershipInfo = await contract.getMembershipInfo(idCommitment);
|
||||
if (!membershipInfo) {
|
||||
throw new Error("Failed to get membership info");
|
||||
}
|
||||
const rateLimit = BigInt(membershipInfo.rateLimit);
|
||||
|
||||
const merkleProof = await contract.getMerkleProof(membershipInfo.index);
|
||||
const merkleRoot = await contract.getMerkleRoot();
|
||||
const rateCommitment = calculateRateCommitment(idCommitment, rateLimit);
|
||||
|
||||
const proofElementIndexes = extractPathDirectionsFromProof(
|
||||
merkleProof,
|
||||
rateCommitment,
|
||||
merkleRoot
|
||||
);
|
||||
if (!proofElementIndexes) {
|
||||
throw new Error("Failed to extract proof element indexes");
|
||||
}
|
||||
|
||||
expect(proofElementIndexes).to.have.lengthOf(MERKLE_TREE_DEPTH);
|
||||
|
||||
const reconstructedRoot = reconstructMerkleRoot(
|
||||
merkleProof as bigint[],
|
||||
BigInt(membershipInfo.index),
|
||||
rateCommitment
|
||||
);
|
||||
|
||||
expect(reconstructedRoot).to.equal(
|
||||
merkleRoot,
|
||||
"Reconstructed root should match contract root"
|
||||
);
|
||||
|
||||
const testMessage = new TextEncoder().encode("test");
|
||||
const rlnInstance = await RLNInstance.create();
|
||||
|
||||
const proof = await rlnInstance.zerokit.generateRLNProof(
|
||||
testMessage,
|
||||
membershipInfo.index,
|
||||
new Date(),
|
||||
credential.identity.IDSecretHash,
|
||||
merkleProof.map((proof) => BytesUtils.fromBigInt(proof, 32, "little")),
|
||||
proofElementIndexes.map((index) =>
|
||||
BytesUtils.writeUIntLE(new Uint8Array(1), index, 0, 1)
|
||||
),
|
||||
Number(rateLimit),
|
||||
0
|
||||
);
|
||||
|
||||
const isValid = rlnInstance.zerokit.verifyRLNProof(
|
||||
BytesUtils.writeUIntLE(new Uint8Array(8), testMessage.length, 0, 8),
|
||||
testMessage,
|
||||
proof,
|
||||
[BytesUtils.fromBigInt(merkleRoot, 32, "little")]
|
||||
);
|
||||
expect(isValid).to.be.true;
|
||||
});
|
||||
});
|
||||
@ -1,10 +1,6 @@
|
||||
<<<<<<< HEAD
|
||||
import { Address } from "viem";
|
||||
|
||||
import { RpcClient } from "../utils/index.js";
|
||||
=======
|
||||
import { Address, PublicClient, WalletClient } from "viem";
|
||||
>>>>>>> a88dd8cdbd (feat: migrate rln from ethers to viem)
|
||||
|
||||
export type Member = {
|
||||
idCommitment: string;
|
||||
@ -12,12 +8,7 @@ export type Member = {
|
||||
};
|
||||
|
||||
export interface RLNContractOptions {
|
||||
<<<<<<< HEAD
|
||||
rpcClient: RpcClient;
|
||||
=======
|
||||
publicClient: PublicClient;
|
||||
walletClient: WalletClient;
|
||||
>>>>>>> a88dd8cdbd (feat: migrate rln from ethers to viem)
|
||||
address: Address;
|
||||
rateLimit?: number;
|
||||
}
|
||||
|
||||
@ -4,11 +4,7 @@ import { createRLN } from "./create.js";
|
||||
import { IdentityCredential } from "./identity.js";
|
||||
import { Keystore } from "./keystore/index.js";
|
||||
import { RLNInstance } from "./rln.js";
|
||||
<<<<<<< HEAD
|
||||
import { createViemClientFromWindow } from "./utils/index.js";
|
||||
=======
|
||||
import { createViemClientsFromWindow } from "./utils/index.js";
|
||||
>>>>>>> a88dd8cdbd (feat: migrate rln from ethers to viem)
|
||||
|
||||
export {
|
||||
RLNBaseContract,
|
||||
@ -17,11 +13,7 @@ export {
|
||||
RLNInstance,
|
||||
IdentityCredential,
|
||||
RLN_CONTRACT,
|
||||
<<<<<<< HEAD
|
||||
createViemClientFromWindow
|
||||
=======
|
||||
createViemClientsFromWindow as extractMetaMaskSigner
|
||||
>>>>>>> a88dd8cdbd (feat: migrate rln from ethers to viem)
|
||||
};
|
||||
|
||||
export {
|
||||
|
||||
101
packages/rln/src/proof.spec.ts
Normal file
101
packages/rln/src/proof.spec.ts
Normal file
@ -0,0 +1,101 @@
|
||||
import { expect } from "chai";
|
||||
|
||||
import { Keystore } from "./keystore/index.js";
|
||||
import { RLNInstance } from "./rln.js";
|
||||
import { BytesUtils } from "./utils/index.js";
|
||||
import {
|
||||
calculateRateCommitment,
|
||||
extractPathDirectionsFromProof,
|
||||
MERKLE_TREE_DEPTH,
|
||||
reconstructMerkleRoot
|
||||
} from "./utils/merkle.js";
|
||||
import { TEST_KEYSTORE_DATA } from "./utils/test_keystore.js";
|
||||
|
||||
describe("RLN Proof Integration Tests", function () {
|
||||
this.timeout(30000);
|
||||
|
||||
it("validate stored merkle proof data", function () {
|
||||
// Convert stored merkle proof strings to bigints
|
||||
const merkleProof = TEST_KEYSTORE_DATA.merkleProof.map((p) => BigInt(p));
|
||||
|
||||
expect(merkleProof).to.be.an("array");
|
||||
expect(merkleProof).to.have.lengthOf(MERKLE_TREE_DEPTH); // RLN uses fixed depth merkle tree
|
||||
|
||||
merkleProof.forEach((element, i) => {
|
||||
expect(element).to.be.a(
|
||||
"bigint",
|
||||
`Proof element ${i} should be a bigint`
|
||||
);
|
||||
expect(element).to.not.equal(0n, `Proof element ${i} should not be zero`);
|
||||
});
|
||||
});
|
||||
|
||||
it("should generate a valid RLN proof", async function () {
|
||||
const rlnInstance = await RLNInstance.create();
|
||||
// Load credential from test keystore
|
||||
const keystore = Keystore.fromString(TEST_KEYSTORE_DATA.keystoreJson);
|
||||
if (!keystore) {
|
||||
throw new Error("Failed to load test keystore");
|
||||
}
|
||||
const credentialHash = TEST_KEYSTORE_DATA.credentialHash;
|
||||
const password = TEST_KEYSTORE_DATA.password;
|
||||
const credential = await keystore.readCredential(credentialHash, password);
|
||||
if (!credential) {
|
||||
throw new Error("Failed to unlock credential with provided password");
|
||||
}
|
||||
|
||||
const idCommitment = credential.identity.IDCommitmentBigInt;
|
||||
|
||||
const merkleProof = TEST_KEYSTORE_DATA.merkleProof.map((p) => BigInt(p));
|
||||
const merkleRoot = BigInt(TEST_KEYSTORE_DATA.merkleRoot);
|
||||
const membershipIndex = BigInt(TEST_KEYSTORE_DATA.membershipIndex);
|
||||
const rateLimit = BigInt(TEST_KEYSTORE_DATA.rateLimit);
|
||||
|
||||
const rateCommitment = calculateRateCommitment(idCommitment, rateLimit);
|
||||
|
||||
const proofElementIndexes = extractPathDirectionsFromProof(
|
||||
merkleProof,
|
||||
rateCommitment,
|
||||
merkleRoot
|
||||
);
|
||||
if (!proofElementIndexes) {
|
||||
throw new Error("Failed to extract proof element indexes");
|
||||
}
|
||||
|
||||
expect(proofElementIndexes).to.have.lengthOf(MERKLE_TREE_DEPTH);
|
||||
|
||||
const reconstructedRoot = reconstructMerkleRoot(
|
||||
merkleProof,
|
||||
membershipIndex,
|
||||
rateCommitment
|
||||
);
|
||||
|
||||
expect(reconstructedRoot).to.equal(
|
||||
merkleRoot,
|
||||
"Reconstructed root should match stored root"
|
||||
);
|
||||
|
||||
const testMessage = new TextEncoder().encode("test");
|
||||
|
||||
const proof = await rlnInstance.zerokit.generateRLNProof(
|
||||
testMessage,
|
||||
Number(membershipIndex),
|
||||
new Date(),
|
||||
credential.identity.IDSecretHash,
|
||||
merkleProof.map((proof) => BytesUtils.fromBigInt(proof, 32, "little")),
|
||||
proofElementIndexes.map((index) =>
|
||||
BytesUtils.writeUIntLE(new Uint8Array(1), index, 0, 1)
|
||||
),
|
||||
Number(rateLimit),
|
||||
0
|
||||
);
|
||||
|
||||
const isValid = rlnInstance.zerokit.verifyRLNProof(
|
||||
BytesUtils.writeUIntLE(new Uint8Array(8), testMessage.length, 0, 8),
|
||||
testMessage,
|
||||
proof,
|
||||
[BytesUtils.fromBigInt(merkleRoot, 32, "little")]
|
||||
);
|
||||
expect(isValid).to.be.true;
|
||||
});
|
||||
});
|
||||
@ -3,5 +3,31 @@ export const TEST_KEYSTORE_DATA = {
|
||||
'{"application":"waku-rln-relay","appIdentifier":"01234567890abcdef","version":"0.2","credentials":{"E0A8AC077B95F64C1B2C4B116468B22EFA3B1CFF250069AE07422F645BAA555E":{"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"96aff104d7bb23cefb57a4c5e816a3b9"},"ciphertext":"1ae2c7a47274d12d6a4b439da48abfa89be29e4ba3308d153e2e808d3e120cc85da472ab1e0278c945231092162d31d753ecb48484ac0c3a7efe6380d08f5dedecc9cda26bd156a30d232b9da4313c5ec92b21cd3dc3ca03cff68afde94a063799b658cc3e4a5c648e620d584a8a184d2d473e3e94c897e21e0de7580639dcf40c0133f36896ac5bee2dd5fe8810a5441e31e1938ecc4b195db57c1b6d320a374508406dfb7a4879081b70100140515b4c6c551f25f9b4c9a7214ac2dc222410bf74666407343dfd4af477c85cf2f316bb7a512a88948d88f5474374563d51d02c13eede6b6cf64fab7991e529157d7de39033099d26f323d9710159b47d2511695b4fb428e3b02c760e1470a3ece712c6a03692d067e0e17930bc25ce7dc4ad2634e07ef51fa7369de6b4d495c7ae1d8ad8dccdd2fa12802db4203c527887adf5eb42e2551e120b8a455892d0ac9369faf708465a983c03c7c8f77c268f85cacc7b718a1e9e2800b160ca1f7a78f2c160cbc97396f5dfe0e0f3b35addb4f8d667021c79eec5248122d8c983075b9e8ca20679e90a12bdbeefb33df21523b4e1ea7ab57ddc706b43bf4827fbc3530d20cb906468af5c5c31ac08815f3ed1d00341be7e287a3fb7ef67aecf2e56f694c51ba6db8641ac873e26659c92a8527c42df2d5ac15ff6201bdfa8a5ee34b6a90ff864fba89370a8c51efcb4ed1b69f3ed0e37ee97c66eb84763f107e1214e088e3149b2433a8da595293343b2290b0a84b7f796b70005d1672446d98d45da7c89c3eb8d91ece94ee41099f9f43c6810ce71d9f75ac3dffe1de0c79e40baad486ecaefbd0cc0e89aed7e0a16ea271a371d3f5927a1c7b813608de5715692e58322260a4bcd4ccba4b2376df01f58645c16a7b37c8473b94c7577ae774e5c72132ed15507ab2027ddabf137aa417b134b653eda247314","kdf":"pbkdf2","kdfparams":{"dklen":32,"c":262144,"prf":"hmac-sha256","salt":"5f2081f089e9e277873bf1f538c60d714749a2bb910d8f1ed119d8d403235a8c"},"mac":"8d0667893b7d3b5f0b37c43edef616a8d295dc58292c98655eec8b5fe2ad69c3"}}}}',
|
||||
credentialHash:
|
||||
"E0A8AC077B95F64C1B2C4B116468B22EFA3B1CFF250069AE07422F645BAA555E",
|
||||
password: "12345678"
|
||||
password: "12345678",
|
||||
merkleProof: [
|
||||
"21837427992620339064281119305700224965155897361776876451171527491637273262703",
|
||||
"2849928341676773476316761863425436901389023422598778907382563142042850204484",
|
||||
"21699429914184421678079077958020273488709892845081201722564329942861605328226",
|
||||
"8522396354694062508299995669286882048091268903835874022564768254605186873188",
|
||||
"4967828252976847302563643214799688359334626491919847999565033460501719790119",
|
||||
"985039452502497454598906195897243897432778848314526706136284672198477696437",
|
||||
"3565679202982155915846059790230166166058846233389836779083891288518797717794",
|
||||
"1241870589869015758600129850815671823696180350556207862318506998039540071293",
|
||||
"21551820661461729022865262380882070649935529853313286572328683688269863701601",
|
||||
"16870197621778677478951480138572599814910741341994641594346262317677658226992",
|
||||
"12413880268183407374852357075976609371175688755676981206018884971008854919922",
|
||||
"14271763308400718165336499097156975241954733520325982997864342600795471836726",
|
||||
"20066985985293572387227381049700832219069292839614107140851619262827735677018",
|
||||
"9394776414966240069580838672673694685292165040808226440647796406499139370960",
|
||||
"11331146992410411304059858900317123658895005918277453009197229807340014528524",
|
||||
"15819538789928229930262697811477882737253464456578333862691129291651619515538",
|
||||
"19217088683336594659449020493828377907203207941212636669271704950158751593251",
|
||||
"21035245323335827719745544373081896983162834604456827698288649288827293579666",
|
||||
"6939770416153240137322503476966641397417391950902474480970945462551409848591",
|
||||
"10941962436777715901943463195175331263348098796018438960955633645115732864202"
|
||||
],
|
||||
merkleRoot:
|
||||
"3281768056038133311055294993138164819435524453040629949691729675724822631973",
|
||||
membershipIndex: "703",
|
||||
rateLimit: "300"
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user