Skip to content

Commit 0e34bb8

Browse files
committed
fix: add additional credentials for testing
1 parent 7a3334b commit 0e34bb8

5 files changed

Lines changed: 287 additions & 89 deletions

File tree

.cspell.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,8 @@
171171
"proto",
172172
"*.spec.ts",
173173
"*.log",
174-
"CHANGELOG.md"
174+
"CHANGELOG.md",
175+
"test_keystore.ts"
175176
],
176177
"patterns": [
177178
{

packages/rln/src/proof.spec.ts

Lines changed: 87 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -9,86 +9,96 @@ import {
99
MERKLE_TREE_DEPTH,
1010
reconstructMerkleRoot
1111
} from "./utils/merkle.js";
12-
import { TEST_KEYSTORE_DATA } from "./utils/test_keystore.js";
12+
import {
13+
TEST_CREDENTIALS,
14+
TEST_KEYSTORE_PASSWORD,
15+
TEST_MERKLE_ROOT
16+
} from "./utils/test_keystore.js";
1317

1418
describe("RLN Proof Integration Tests", function () {
1519
this.timeout(30000);
1620

17-
it("validate stored merkle proof data", function () {
18-
const merkleProof = TEST_KEYSTORE_DATA.merkleProof.map((p) => BigInt(p));
19-
20-
expect(merkleProof).to.be.an("array");
21-
expect(merkleProof).to.have.lengthOf(MERKLE_TREE_DEPTH);
22-
23-
for (let i = 0; i < merkleProof.length; i++) {
24-
const element = merkleProof[i];
25-
expect(element).to.be.a(
26-
"bigint",
27-
`Proof element ${i} should be a bigint`
28-
);
29-
expect(element).to.not.equal(0n, `Proof element ${i} should not be zero`);
30-
}
31-
});
32-
33-
it("should generate a valid RLN proof", async function () {
34-
const rlnInstance = await RLNInstance.create();
35-
const keystore = Keystore.fromString(TEST_KEYSTORE_DATA.keystoreJson);
36-
if (!keystore) {
37-
throw new Error("Failed to load test keystore");
38-
}
39-
const credentialHash = TEST_KEYSTORE_DATA.credentialHash;
40-
const password = TEST_KEYSTORE_DATA.password;
41-
const credential = await keystore.readCredential(credentialHash, password);
42-
if (!credential) {
43-
throw new Error("Failed to unlock credential with provided password");
44-
}
45-
46-
const idCommitment = credential.identity.IDCommitmentBigInt;
47-
48-
const merkleProof = TEST_KEYSTORE_DATA.merkleProof.map((p) => BigInt(p));
49-
const merkleRoot = BigInt(TEST_KEYSTORE_DATA.merkleRoot);
50-
const membershipIndex = BigInt(TEST_KEYSTORE_DATA.membershipIndex);
51-
const rateLimit = BigInt(TEST_KEYSTORE_DATA.rateLimit);
52-
53-
const rateCommitment = calculateRateCommitment(idCommitment, rateLimit);
54-
55-
const proofElementIndexes = getPathDirectionsFromIndex(membershipIndex);
56-
57-
expect(proofElementIndexes).to.have.lengthOf(MERKLE_TREE_DEPTH);
58-
59-
const reconstructedRoot = reconstructMerkleRoot(
60-
merkleProof,
61-
membershipIndex,
62-
rateCommitment
63-
);
64-
65-
expect(reconstructedRoot).to.equal(
66-
merkleRoot,
67-
"Reconstructed root should match stored root"
68-
);
69-
70-
const testMessage = new TextEncoder().encode("test");
71-
72-
const proof = await rlnInstance.zerokit.generateRLNProof(
73-
testMessage,
74-
new Date(),
75-
credential.identity.IDSecretHash,
76-
merkleProof.map((element) =>
77-
BytesUtils.bytes32FromBigInt(element, "little")
78-
),
79-
proofElementIndexes.map((index) =>
80-
BytesUtils.writeUintLE(new Uint8Array(1), index, 0, 1)
81-
),
82-
Number(rateLimit),
83-
0
84-
);
85-
86-
const isValid = rlnInstance.zerokit.verifyRLNProof(
87-
BytesUtils.writeUintLE(new Uint8Array(8), testMessage.length, 0, 8),
88-
testMessage,
89-
proof,
90-
[BytesUtils.bytes32FromBigInt(merkleRoot, "little")]
91-
);
92-
expect(isValid).to.be.true;
21+
TEST_CREDENTIALS.forEach((credential, index) => {
22+
describe(`Credential ${index + 1}`, function () {
23+
it("validate stored merkle proof data", function () {
24+
const merkleProof = credential.merkleProof.map((p) => BigInt(p));
25+
26+
expect(merkleProof).to.be.an("array");
27+
expect(merkleProof).to.have.lengthOf(MERKLE_TREE_DEPTH);
28+
29+
for (let i = 0; i < merkleProof.length; i++) {
30+
const element = merkleProof[i];
31+
expect(element).to.be.a(
32+
"bigint",
33+
`Proof element ${i} should be a bigint`
34+
);
35+
// Note: First element can be 0 for some tree positions (e.g., credential 3)
36+
}
37+
});
38+
39+
it("should generate a valid RLN proof", async function () {
40+
const rlnInstance = await RLNInstance.create();
41+
const keystore = Keystore.fromString(credential.keystoreJson);
42+
if (!keystore) {
43+
throw new Error("Failed to load test keystore");
44+
}
45+
const credentialHash = credential.credentialHash;
46+
const decrypted = await keystore.readCredential(
47+
credentialHash,
48+
TEST_KEYSTORE_PASSWORD
49+
);
50+
if (!decrypted) {
51+
throw new Error("Failed to unlock credential with provided password");
52+
}
53+
54+
const idCommitment = decrypted.identity.IDCommitmentBigInt;
55+
56+
const merkleProof = credential.merkleProof.map((p) => BigInt(p));
57+
const merkleRoot = BigInt(TEST_MERKLE_ROOT);
58+
const membershipIndex = BigInt(credential.membershipIndex);
59+
const rateLimit = BigInt(credential.rateLimit);
60+
61+
const rateCommitment = calculateRateCommitment(idCommitment, rateLimit);
62+
63+
const proofElementIndexes = getPathDirectionsFromIndex(membershipIndex);
64+
65+
expect(proofElementIndexes).to.have.lengthOf(MERKLE_TREE_DEPTH);
66+
67+
const reconstructedRoot = reconstructMerkleRoot(
68+
merkleProof,
69+
membershipIndex,
70+
rateCommitment
71+
);
72+
73+
expect(reconstructedRoot).to.equal(
74+
merkleRoot,
75+
"Reconstructed root should match stored root"
76+
);
77+
78+
const testMessage = new TextEncoder().encode("test");
79+
80+
const proof = await rlnInstance.zerokit.generateRLNProof(
81+
testMessage,
82+
new Date(),
83+
decrypted.identity.IDSecretHash,
84+
merkleProof.map((element) =>
85+
BytesUtils.bytes32FromBigInt(element, "little")
86+
),
87+
proofElementIndexes.map((idx) =>
88+
BytesUtils.writeUintLE(new Uint8Array(1), idx, 0, 1)
89+
),
90+
Number(rateLimit),
91+
0
92+
);
93+
94+
const isValid = rlnInstance.zerokit.verifyRLNProof(
95+
BytesUtils.writeUintLE(new Uint8Array(8), testMessage.length, 0, 8),
96+
testMessage,
97+
proof,
98+
[BytesUtils.bytes32FromBigInt(merkleRoot, "little")]
99+
);
100+
expect(isValid).to.be.true;
101+
});
102+
});
93103
});
94104
});
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { expect } from "chai";
2+
import fc from "fast-check";
3+
4+
import { getPathDirectionsFromIndex, MERKLE_TREE_DEPTH } from "./merkle.js";
5+
6+
describe("getPathDirectionsFromIndex", () => {
7+
it("should return an array of length MERKLE_TREE_DEPTH", () => {
8+
const result = getPathDirectionsFromIndex(0n);
9+
expect(result).to.have.lengthOf(MERKLE_TREE_DEPTH);
10+
});
11+
12+
it("should return all zeros for index 0", () => {
13+
const result = getPathDirectionsFromIndex(0n);
14+
expect(result.every((bit) => bit === 0)).to.be.true;
15+
});
16+
17+
it("should return [1, 0, 0, ...] for index 1 (only LSB set)", () => {
18+
const result = getPathDirectionsFromIndex(1n);
19+
expect(result[0]).to.equal(1);
20+
expect(result.slice(1).every((bit) => bit === 0)).to.be.true;
21+
});
22+
23+
it("should return [0, 1, 0, ...] for index 2 (only bit 1 set)", () => {
24+
const result = getPathDirectionsFromIndex(2n);
25+
expect(result[0]).to.equal(0);
26+
expect(result[1]).to.equal(1);
27+
expect(result.slice(2).every((bit) => bit === 0)).to.be.true;
28+
});
29+
30+
it("should return [1, 1, 0, ...] for index 3 (bits 0 and 1 set)", () => {
31+
const result = getPathDirectionsFromIndex(3n);
32+
expect(result[0]).to.equal(1);
33+
expect(result[1]).to.equal(1);
34+
expect(result.slice(2).every((bit) => bit === 0)).to.be.true;
35+
});
36+
37+
it("should correctly extract bits for a known value", () => {
38+
// Index 42 = 0b101010: bits 1, 3, 5 are set
39+
const result = getPathDirectionsFromIndex(42n);
40+
expect(result[0]).to.equal(0); // bit 0
41+
expect(result[1]).to.equal(1); // bit 1
42+
expect(result[2]).to.equal(0); // bit 2
43+
expect(result[3]).to.equal(1); // bit 3
44+
expect(result[4]).to.equal(0); // bit 4
45+
expect(result[5]).to.equal(1); // bit 5
46+
});
47+
48+
it("should only contain 0 or 1 values", () => {
49+
fc.assert(
50+
fc.property(fc.bigInt({ min: 0n, max: 2n ** 20n - 1n }), (index) => {
51+
const result = getPathDirectionsFromIndex(index);
52+
return result.every((bit) => bit === 0 || bit === 1);
53+
})
54+
);
55+
});
56+
57+
it("should produce consistent results for the same input", () => {
58+
fc.assert(
59+
fc.property(fc.bigInt({ min: 0n, max: 2n ** 20n - 1n }), (index) => {
60+
const result1 = getPathDirectionsFromIndex(index);
61+
const result2 = getPathDirectionsFromIndex(index);
62+
return (
63+
result1.length === result2.length &&
64+
result1.every((bit, i) => bit === result2[i])
65+
);
66+
})
67+
);
68+
});
69+
70+
it("should reconstruct the original index from path directions", () => {
71+
fc.assert(
72+
fc.property(fc.bigInt({ min: 0n, max: 2n ** 20n - 1n }), (index) => {
73+
const pathDirections = getPathDirectionsFromIndex(index);
74+
75+
// Reconstruct the index from path directions
76+
let reconstructed = 0n;
77+
for (let level = 0; level < MERKLE_TREE_DEPTH; level++) {
78+
if (pathDirections[level] === 1) {
79+
reconstructed |= 1n << BigInt(level);
80+
}
81+
}
82+
83+
return reconstructed === index;
84+
})
85+
);
86+
});
87+
});

0 commit comments

Comments
 (0)