Skip to content

Commit dc61abd

Browse files
committed
🎫 Emit HumanIDv1 add events for offchain MerkleTree listeners
- Refactor humandID from a function library to a base `SmartContract` - Emit "KimlikDAO-add-HumanIDv1" event each time a HumanIDv1 is accepted - Emit "KimlikDAO-init" add app contract initialization along with the MerkleTree height.
1 parent 53ca7d3 commit dc61abd

File tree

6 files changed

+135
-147
lines changed

6 files changed

+135
-147
lines changed

‎mina/humanIDv1.test.ts ‎mina/HumanIDv1.test.ts

+21-25
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Field, Poseidon, PrivateKey, PublicKey, Signature } from "o1js";
2-
import { Signatures, authenticate, requireConsistent } from "./humanIDv1";
2+
import { HumanIDv1, authenticate, requireConsistent } from "./HumanIDv1";
33

44
const Nodes = [
55
PrivateKey.fromBigInt(1n),
@@ -15,44 +15,40 @@ const blindingCommit = (sender: PublicKey) => {
1515
];
1616
};
1717

18-
const signHumanIDv1 = (
19-
humanIDv1Key: bigint,
20-
sender: PublicKey
21-
): [Field, Field, Signatures] => {
22-
const humanIDv1 = Field(humanIDv1Key);
18+
const signHumanIDv1 = (humanIDv1Id: bigint, sender: PublicKey): HumanIDv1 => {
19+
const id = Field(humanIDv1Id);
2320
const [commitmentR, commitment] = blindingCommit(sender);
24-
const sigs = new Signatures({
25-
sig0: Signature.create(Nodes[0], [humanIDv1, commitment]),
26-
sig1: Signature.create(Nodes[1], [humanIDv1, commitment]),
27-
sig2: Signature.create(Nodes[2], [humanIDv1, commitment]),
21+
return new HumanIDv1({
22+
id,
23+
commitmentR,
24+
sig0: Signature.create(Nodes[0], [id, commitment]),
25+
sig1: Signature.create(Nodes[1], [id, commitment]),
26+
sig2: Signature.create(Nodes[2], [id, commitment]),
2827
});
29-
return [humanIDv1, commitmentR, sigs];
3028
};
3129

32-
const truncateHumanIDv1 = (humanIDv1Key: bigint) => humanIDv1Key & 0xffffffffn;
33-
34-
const badSignHumanIDv1 = (
35-
humanIDv1Key: bigint,
36-
sender: PublicKey
37-
): [Field, Field, Signatures] => {
38-
const humanIDv1 = Field(humanIDv1Key);
30+
const badSignHumanIDv1 = (humanIDv1: bigint, sender: PublicKey): HumanIDv1 => {
31+
const id = Field(humanIDv1);
3932
const [commitmentR, commitment] = blindingCommit(sender);
40-
const sigs = new Signatures({
41-
sig0: Signature.create(Nodes[0], [humanIDv1, commitment]),
42-
sig1: Signature.create(Nodes[1], [humanIDv1, commitment]),
43-
sig2: Signature.create(Nodes[3], [humanIDv1, commitment]),
33+
return new HumanIDv1({
34+
id,
35+
commitmentR,
36+
sig0: Signature.create(Nodes[0], [id, commitment]),
37+
sig1: Signature.create(Nodes[1], [id, commitment]),
38+
sig2: Signature.create(Nodes[0], [id, commitment]),
4439
});
45-
return [humanIDv1, commitmentR, sigs];
4640
};
4741

42+
const truncateHumanIDv1 = (id: bigint) => id & 0xffffffffn;
43+
4844
describe("humanIDv1 SDK tests", () => {
4945
it("should authenticate geniune humanIDv1s and reject others", () => {
5046
const claimant = PrivateKey.fromBigInt(0x1337n).toPublicKey();
5147
expect(() =>
52-
authenticate(claimant, ...signHumanIDv1(100n, claimant))
48+
authenticate(claimant, signHumanIDv1(100n, claimant))
5349
).not.toThrow();
5450
expect(() =>
55-
authenticate(claimant, ...badSignHumanIDv1(200n, claimant))
51+
authenticate(claimant, badSignHumanIDv1(200n, claimant))
5652
).toThrow();
5753
});
5854

‎mina/HumanIDv1.ts

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import {
2+
Field,
3+
MerkleWitness,
4+
Poseidon,
5+
PrivateKey,
6+
PublicKey,
7+
Signature,
8+
SmartContract,
9+
State,
10+
Struct,
11+
state,
12+
} from "o1js";
13+
14+
class HumanIDv1 extends Struct({
15+
id: Field,
16+
commitmentR: Field,
17+
sig0: Signature,
18+
sig1: Signature,
19+
sig2: Signature,
20+
}) { }
21+
22+
class HumanIDv1Witness extends MerkleWitness(33) { }
23+
24+
const KimlikDAONodes = [
25+
PrivateKey.fromBigInt(1n).toPublicKey(),
26+
PrivateKey.fromBigInt(2n).toPublicKey(),
27+
PrivateKey.fromBigInt(3n).toPublicKey(),
28+
];
29+
30+
const authenticate = (owner: PublicKey, hid: HumanIDv1) => {
31+
const commitment = Poseidon.hash([
32+
hid.commitmentR,
33+
owner.x.add(owner.isOdd.toField()),
34+
]);
35+
hid.sig0.verify(KimlikDAONodes[0], [hid.id, commitment]).assertTrue();
36+
hid.sig1.verify(KimlikDAONodes[1], [hid.id, commitment]).assertTrue();
37+
hid.sig2.verify(KimlikDAONodes[2], [hid.id, commitment]).assertTrue();
38+
};
39+
40+
const EmptyRoot =
41+
Field(0x21afce36daa1a2d67391072035f4555a85aea7197e5830b128f121aa382770cdn);
42+
43+
const Inverse2Exp32 =
44+
Field(0x3fffffffc00000000000000000000000224698fbe706601f8fe037d166d2cf14n);
45+
46+
const requireConsistent = (humanIDv1: Field, truncatedHumanIDv1: Field) =>
47+
humanIDv1
48+
.sub(truncatedHumanIDv1)
49+
.mul(Inverse2Exp32)
50+
.assertLessThan(
51+
(1n << 222n) + 0x224698fc094cf91b992d30edn,
52+
"HumanID does not match the witness"
53+
);
54+
55+
class PerHumanIDv1 extends SmartContract {
56+
events = {
57+
"KimlikDAO-init": Field, // Emits the tree height along with init event
58+
"KimlikDAO-add-HumanIDv1": Field, // Emits the added HumanIDv1.id
59+
};
60+
61+
@state(Field) treeRoot = State<Field>();
62+
63+
init() {
64+
super.init();
65+
this.treeRoot.set(EmptyRoot);
66+
this.emitEvent("KimlikDAO-init", Field(32));
67+
}
68+
69+
acceptHumanIDv1(
70+
owner: PublicKey,
71+
humanIDv1: HumanIDv1,
72+
witness: HumanIDv1Witness
73+
) {
74+
authenticate(owner, humanIDv1);
75+
requireConsistent(humanIDv1.id, witness.calculateIndex());
76+
this.addToMerkleTree(witness);
77+
this.emitEvent("KimlikDAO-add-HumanIDv1", humanIDv1.id);
78+
}
79+
80+
addToMerkleTree(witness: HumanIDv1Witness) {
81+
const currentTreeRoot = this.treeRoot.getAndRequireEquals();
82+
currentTreeRoot.assertEquals(
83+
witness.calculateRoot(Field(0)),
84+
"HumanID already exists in the set"
85+
);
86+
this.treeRoot.set(witness.calculateRoot(Field(1)));
87+
}
88+
}
89+
90+
export {
91+
EmptyRoot,
92+
HumanIDv1,
93+
HumanIDv1Witness,
94+
KimlikDAONodes,
95+
PerHumanIDv1,
96+
authenticate,
97+
requireConsistent
98+
};

‎mina/examples/Airdrop.test.ts

+10-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Field, MerkleTree, Mina, PrivateKey, PublicKey, UInt64 } from "o1js";
2-
import { HumanIDWitness } from "../humanIDv1";
3-
import { signHumanIDv1, truncateHumanIDv1 } from "../humanIDv1.test";
2+
import { HumanIDv1Witness } from "../HumanIDv1";
3+
import { signHumanIDv1, truncateHumanIDv1 } from "../HumanIDv1.test";
44
import { Airdrop } from "./Airdrop";
55

66
describe("Example Airdrop zkApp", () => {
@@ -32,7 +32,7 @@ describe("Example Airdrop zkApp", () => {
3232

3333
const getWitnessAndInsert = (humanIDv1Key: bigint) => {
3434
const truncated = truncateHumanIDv1(humanIDv1Key);
35-
const witness = new HumanIDWitness(tree.getWitness(truncated));
35+
const witness = new HumanIDv1Witness(tree.getWitness(truncated));
3636
tree.setLeaf(truncated, Field(1));
3737
return witness;
3838
};
@@ -42,7 +42,7 @@ describe("Example Airdrop zkApp", () => {
4242

4343
it("should let people claimReward()", () =>
4444
Mina.transaction(sender, () =>
45-
app.claimReward(...signHumanIDv1(100n, sender), getWitnessAndInsert(100n))
45+
app.claimReward(signHumanIDv1(100n, sender), getWitnessAndInsert(100n))
4646
)
4747
.prove()
4848
.sign([senderKey])
@@ -51,15 +51,15 @@ describe("Example Airdrop zkApp", () => {
5151
it("should let 2 people claimReward()", async () => {
5252
const id1 = 123123123123123123123123123123n;
5353
await Mina.transaction(sender, () =>
54-
app.claimReward(...signHumanIDv1(id1, sender), getWitnessAndInsert(id1))
54+
app.claimReward(signHumanIDv1(id1, sender), getWitnessAndInsert(id1))
5555
)
5656
.prove()
5757
.sign([senderKey])
5858
.send();
5959

6060
const id2 = 123123123123123123123123123124n;
6161
await Mina.transaction(sender, () =>
62-
app.claimReward(...signHumanIDv1(id2, sender), getWitnessAndInsert(id2))
62+
app.claimReward(signHumanIDv1(id2, sender), getWitnessAndInsert(id2))
6363
)
6464
.prove()
6565
.sign([senderKey])
@@ -70,7 +70,7 @@ describe("Example Airdrop zkApp", () => {
7070
const id = 123123123123123123123123123124n;
7171
expect(() =>
7272
Mina.transaction(sender, () =>
73-
app.claimReward(...signHumanIDv1(id, sender), getWitnessAndInsert(100n))
73+
app.claimReward(signHumanIDv1(id, sender), getWitnessAndInsert(100n))
7474
)
7575
.prove()
7676
.sign([senderKey])
@@ -81,15 +81,15 @@ describe("Example Airdrop zkApp", () => {
8181
it("should not let double claimReward()", async () => {
8282
const id = 123123123123123123123123123123n;
8383
await Mina.transaction(sender, () =>
84-
app.claimReward(...signHumanIDv1(id, sender), getWitnessAndInsert(id))
84+
app.claimReward(signHumanIDv1(id, sender), getWitnessAndInsert(id))
8585
)
8686
.prove()
8787
.sign([senderKey])
8888
.send();
8989

9090
expect(() =>
9191
Mina.transaction(sender, () =>
92-
app.claimReward(...signHumanIDv1(id, sender), getWitnessAndInsert(id))
92+
app.claimReward(signHumanIDv1(id, sender), getWitnessAndInsert(id))
9393
)
9494
.prove()
9595
.sign([senderKey])
@@ -101,7 +101,7 @@ describe("Example Airdrop zkApp", () => {
101101
let firstBalance = Mina.getBalance(sender);
102102

103103
await Mina.transaction(sender, () =>
104-
app.claimReward(...signHumanIDv1(100n, sender), getWitnessAndInsert(100n))
104+
app.claimReward(signHumanIDv1(100n, sender), getWitnessAndInsert(100n))
105105
)
106106
.prove()
107107
.sign([senderKey])

‎mina/examples/Airdrop.ts

+5-28
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,15 @@
1-
import {
2-
Field,
3-
SmartContract,
4-
State,
5-
method,
6-
state,
7-
} from "o1js";
8-
import {
9-
EmptyRoot,
10-
HumanIDWitness,
11-
Signatures,
12-
acceptHumanIDv1,
13-
} from "../humanIDv1";
1+
import { method } from "o1js";
2+
import { HumanIDv1, HumanIDv1Witness, PerHumanIDv1 } from "../HumanIDv1";
143

154
const MINA = 1e9;
165

176
/**
187
* Example airdrop zkApp, which gives 10 MINA rewards to each unique human.
198
*/
20-
class Airdrop extends SmartContract {
21-
@state(Field) treeRoot = State<Field>();
22-
23-
init() {
24-
super.init();
25-
this.treeRoot.set(EmptyRoot);
26-
}
27-
28-
@method async claimReward(
29-
humanIDv1: Field,
30-
commitmentR: Field,
31-
sigs: Signatures,
32-
witness: HumanIDWitness,
33-
) {
9+
class Airdrop extends PerHumanIDv1 {
10+
@method async claimReward(humanIDv1: HumanIDv1, witness: HumanIDv1Witness) {
3411
const sender = this.sender.getUnconstrained();
35-
acceptHumanIDv1(sender, humanIDv1, commitmentR, sigs, this.treeRoot, witness);
12+
this.acceptHumanIDv1(sender, humanIDv1, witness);
3613
this.send({ to: sender, amount: 10 * MINA });
3714
}
3815
}

‎mina/humanIDv1.ts

-83
This file was deleted.

0 commit comments

Comments
 (0)