feat(group): update group with lean-imt

Former-commit-id: 1c8e2185b9
This commit is contained in:
cedoor
2023-12-12 14:42:26 +00:00
parent 361510d206
commit f3e896eaaf
6 changed files with 116 additions and 131 deletions

View File

@@ -49,8 +49,8 @@
</h4>
</div>
| This library is an abstraction of [`@zk-kit/incremental-merkle-tree`](https://github.com/privacy-scaling-explorations/zk-kit/tree/main/packages/incremental-merkle-tree). The main goal is to make it easier to create offchain groups, which are also used to generate Semaphore proofs. Semaphore groups are actually incremental Merkle trees, and the group members are tree leaves. Since the Merkle tree implementation we are using is a binary tree, the maximum number of members of a group is equal to `2^treeDepth`. |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| This library is an abstraction of the LeanIMT data structure (part of [`@zk-kit/imt`](https://github.com/privacy-scaling-explorations/zk-kit/tree/main/packages/imt)). The main goal is to make it easier to create offchain groups, which are also used to generate Semaphore proofs. Semaphore groups are actually Merkle trees, and the group members are tree leaves. |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
## 🛠 Install
@@ -70,54 +70,88 @@ yarn add @semaphore-protocol/group
## 📜 Usage
\# **new Group**(groupId: _Member_, treeDepth = 20): _Group_
\# **new Group**(members: _BigNumberish[]_ = []): _Group_
```typescript
import { Group } from "@semaphore-protocol/group"
import { Identity } from "@semaphore-protocol/identity"
// Group with max 1048576 members (20^²).
const group1 = new Group(1)
const group1 = new Group()
// Group with max 65536 members (16^²).
const group2 = new Group(1, 16)
// Group with max 16777216 members (24^²).
const group3 = new Group(1, 24)
// Group with a list of predefined members.
const identity1 = new Identity()
const identity2 = new Identity()
const identity3 = new Identity()
const group3 = new Group(1, 16, [identity1.commitment, identity2.commitment, identity3.commitment])
const group2 = new Group([identity1.commitment, identity2.commitment])
```
\# **addMember**(identityCommitment: _Member_)
\# **addMember**(member: _BigNumberish_)
```typescript
import { Group } from "@semaphore-protocol/group"
import { Identity } from "@semaphore-protocol/identity"
const identity = new Identity()
const commitment = identity.generateCommitment()
const group = new Group()
const { commitment } = new Identity()
group.addMember(commitment)
// "12989101133047504182892154686643420754368236204022364847543591045056549053997"
console.log(group.members[0])
```
\# **updateMember**(index: _number_, member: _BigNumberish_)
```typescript
import { Group } from "@semaphore-protocol/group"
const group = new Group([1, 3])
group.updateMember(0, 2)
console.log(group.members[0]) // "2"
```
\# **removeMember**(index: _number_)
```typescript
import { Group } from "@semaphore-protocol/group"
const group = new Group([1, 3])
group.removeMember(0)
console.log(group.members[0]) // "0"
```
\# **indexOf**(member: _Member_): _number_
\# **indexOf**(member: _BigNumberish_): _number_
```typescript
group.indexOf(commitment) // 0
import { Group } from "@semaphore-protocol/group"
const group = new Group([1])
const index = group.indexOf(1)
console.log(index) // 0
```
\# **generateMerkleProof**(index: _number_): _MerkleProof_
```typescript
import { Group } from "@semaphore-protocol/group"
const group = new Group([1, 3])
const proof = group.generateMerkleProof(0)
console.log(proof)
/*
{
index: 0,
leaf: '1',
root: '21106761926285267690763443010820487107972411248208546226053195422384279971821',
siblings: [ '3' ]
}
*/
```

View File

@@ -37,9 +37,6 @@
"rollup-plugin-typescript2": "^0.31.2"
},
"dependencies": {
"@ethersproject/bignumber": "^5.7.0",
"@ethersproject/bytes": "^5.7.0",
"@ethersproject/keccak256": "^5.7.0",
"@zk-kit/incremental-merkle-tree": "1.1.0"
"@zk-kit/imt": "^2.0.0-beta"
}
}

View File

@@ -1,75 +1,56 @@
import Group from "./group"
import hash from "./hash"
describe("Group", () => {
describe("# Group", () => {
it("Should create a group", () => {
const group = new Group(1)
const group = new Group()
expect(group.id).toBe(1)
expect(group.root.toString()).toContain("103543")
expect(group.depth).toBe(20)
expect(group.zeroValue).toBe(hash(1))
expect(group.members).toHaveLength(0)
})
it("Should not create a group with a wrong tree depth", () => {
const fun = () => new Group(1, 33)
expect(fun).toThrow("The tree depth must be between 16 and 32")
})
it("Should create a group with a different tree depth", () => {
const group = new Group(1, 32)
expect(group.root.toString()).toContain("460373")
expect(group.depth).toBe(32)
expect(group.zeroValue).toBe(hash(1))
expect(group.members).toHaveLength(0)
expect(group.root).toBeUndefined()
expect(group.depth).toBe(0)
expect(group.size).toBe(0)
})
it("Should create a group with a list of members", () => {
const group = new Group(2, 20, [1, 2, 3])
const group = new Group([1, 2, 3])
const group2 = new Group(2, 20)
const group2 = new Group()
group2.addMember(1)
group2.addMember(2)
group2.addMember(3)
expect(group.root.toString()).toContain(group2.root.toString())
expect(group.depth).toBe(20)
expect(group.zeroValue).toBe(hash(2))
expect(group.members).toHaveLength(3)
expect(group.root).toContain(group2.root)
expect(group.depth).toBe(2)
expect(group.size).toBe(3)
})
})
describe("# addMember", () => {
it("Should add a member to a group", () => {
const group = new Group(1)
const group = new Group()
group.addMember(BigInt(3))
group.addMember(3)
expect(group.members).toHaveLength(1)
expect(group.size).toBe(1)
})
})
describe("# addMembers", () => {
it("Should add many members to a group", () => {
const group = new Group(1)
const group = new Group()
group.addMembers([BigInt(1), BigInt(3)])
group.addMembers([1, 3])
expect(group.members).toHaveLength(2)
expect(group.size).toBe(2)
})
})
describe("# indexOf", () => {
it("Should return the index of a member in a group", () => {
const group = new Group(1)
group.addMembers([BigInt(1), BigInt(3)])
const group = new Group()
group.addMembers([1, 3])
const index = group.indexOf(BigInt(3))
const index = group.indexOf(3)
expect(index).toBe(1)
})
@@ -77,36 +58,39 @@ describe("Group", () => {
describe("# updateMember", () => {
it("Should update a member in a group", () => {
const group = new Group(1)
group.addMembers([BigInt(1), BigInt(3)])
const group = new Group()
group.addMembers([1, 3])
group.updateMember(0, BigInt(1))
group.updateMember(0, 1)
expect(group.members).toHaveLength(2)
expect(group.members[0]).toBe(BigInt(1))
expect(group.size).toBe(2)
expect(group.members[0]).toBe("1")
})
})
describe("# removeMember", () => {
it("Should remove a member from a group", () => {
const group = new Group(1)
group.addMembers([BigInt(1), BigInt(3)])
const group = new Group()
group.addMembers([1, 3])
group.removeMember(0)
expect(group.members).toHaveLength(2)
expect(group.members[0]).toBe(group.zeroValue)
expect(group.size).toBe(2)
expect(group.members[0]).toBe("0")
})
})
describe("# generateMerkleProof", () => {
it("Should generate a proof of membership", () => {
const group = new Group(1)
group.addMembers([BigInt(1), BigInt(3)])
const group = new Group()
group.addMembers([1, 3])
const proof = group.generateMerkleProof(0)
expect(proof.leaf).toBe(BigInt(1))
console.log(proof)
expect(proof.leaf).toBe("1")
})
})
})

View File

@@ -1,42 +1,24 @@
import { IncrementalMerkleTree, MerkleProof } from "@zk-kit/incremental-merkle-tree"
import { LeanIMT } from "@zk-kit/imt"
import { poseidon2 } from "poseidon-lite/poseidon2"
import hash from "./hash"
import { BigNumberish } from "./types"
import { BigNumberish, MerkleProof } from "./types"
export default class Group {
private _id: BigNumberish
merkleTree: IncrementalMerkleTree
leanIMT: LeanIMT
/**
* Initializes the group with the group id and the tree depth.
* @param id Group identifier.
* @param treeDepth Tree depth.
* @param members List of group members.
*/
constructor(id: BigNumberish, treeDepth = 20, members: BigNumberish[] = []) {
if (treeDepth < 16 || treeDepth > 32) {
throw new Error("The tree depth must be between 16 and 32")
}
this._id = id
this.merkleTree = new IncrementalMerkleTree(poseidon2, treeDepth, hash(id), 2, members.map(BigInt))
}
/**
* Returns the id of the group.
* @returns Group id.
*/
get id(): BigNumberish {
return this._id
constructor(members: BigNumberish[] = []) {
this.leanIMT = new LeanIMT((a, b) => poseidon2([a, b]), members.map(BigInt))
}
/**
* Returns the root hash of the tree.
* @returns Root hash.
*/
get root(): BigNumberish {
return this.merkleTree.root
get root(): string | undefined {
return this.leanIMT.root?.toString()
}
/**
@@ -44,23 +26,23 @@ export default class Group {
* @returns Tree depth.
*/
get depth(): number {
return this.merkleTree.depth
return this.leanIMT.depth
}
/**
* Returns the zero value of the tree.
* @returns Tree zero value.
* Returns the size of the tree (i.e. number of leaves).
* @returns Tree depth.
*/
get zeroValue(): BigNumberish {
return this.merkleTree.zeroes[0]
get size(): number {
return this.leanIMT.size
}
/**
* Returns the members (i.e. identity commitments) of the group.
* @returns List of members.
*/
get members(): BigNumberish[] {
return this.merkleTree.leaves
get members(): string[] {
return this.leanIMT.leaves.map(String)
}
/**
@@ -69,7 +51,7 @@ export default class Group {
* @returns Index of the member.
*/
indexOf(member: BigNumberish): number {
return this.merkleTree.indexOf(member)
return this.leanIMT.indexOf(BigInt(member))
}
/**
@@ -77,18 +59,15 @@ export default class Group {
* @param member New member.
*/
addMember(member: BigNumberish) {
this.merkleTree.insert(BigInt(member))
this.leanIMT.insert(BigInt(member))
}
/**
* Adds new members to the group.
* @param members New members.
* @deprecated Use the new class parameter to add a list of members.
*/
addMembers(members: BigNumberish[]) {
for (const member of members) {
this.addMember(member)
}
this.leanIMT.insertMany(members.map(BigInt))
}
/**
@@ -97,7 +76,7 @@ export default class Group {
* @param member New member value.
*/
updateMember(index: number, member: BigNumberish) {
this.merkleTree.update(index, member)
this.leanIMT.update(index, BigInt(member))
}
/**
@@ -105,7 +84,7 @@ export default class Group {
* @param index Index of the member to be removed.
*/
removeMember(index: number) {
this.merkleTree.delete(index)
this.leanIMT.update(index, BigInt(0))
}
/**
@@ -114,10 +93,13 @@ export default class Group {
* @returns Proof object.
*/
generateMerkleProof(index: number): MerkleProof {
const merkleProof = this.merkleTree.createProof(index)
const { leaf, root, siblings } = this.leanIMT.generateProof(index)
merkleProof.siblings = merkleProof.siblings.map((s) => s[0])
return merkleProof
return {
index,
leaf: leaf.toString(),
root: root.toString(),
siblings: siblings.map(String)
}
}
}

View File

@@ -1,15 +0,0 @@
import { BigNumber } from "@ethersproject/bignumber"
import { BytesLike, Hexable, zeroPad } from "@ethersproject/bytes"
import { keccak256 } from "@ethersproject/keccak256"
/**
* Creates a keccak256 hash of a message compatible with the SNARK scalar modulus.
* @param message The message to be hashed.
* @returns The message digest.
*/
export default function hash(message: BytesLike | Hexable | number | bigint): bigint {
message = BigNumber.from(message).toTwos(256).toHexString()
message = zeroPad(message, 32)
return BigInt(keccak256(message)) >> BigInt(8)
}

View File

@@ -1 +1,4 @@
import { LeanIMTMerkleProof } from "@zk-kit/imt"
export type BigNumberish = string | number | bigint
export type MerkleProof = LeanIMTMerkleProof<string>