Skip to content

Commit

Permalink
zk proof working
Browse files Browse the repository at this point in the history
  • Loading branch information
zemse committed Mar 16, 2024
1 parent 88d01bc commit 05b09c9
Show file tree
Hide file tree
Showing 16 changed files with 467 additions and 12 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"prepare": "dts build",
"size": "size-limit",
"start": "dts watch",
"test": "dts test"
"test": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" dts test"
},
"husky": {
"hooks": {
Expand Down
7 changes: 7 additions & 0 deletions src/field.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Commitable } from './utils';
import {
BigNumberish,
toBigInt,
Expand All @@ -9,6 +10,8 @@ import {
const PRIME =
0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001n;

export type FieldRaw = string;

export class Field {
public value: bigint;
constructor(_value: BigNumberish) {
Expand Down Expand Up @@ -39,6 +42,10 @@ export class Field {
return zeroPadValue(toBeHex(this.value), 32);
}

raw(): FieldRaw {
return this.hex();
}

add(other: Field): Field {
return new Field((this.value + other.value) % PRIME);
}
Expand Down
9 changes: 6 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
export * from './merkle-tree';
export * from './hash';
export * from './utils';
export * from './field';
export * from './keypair';
export * from './hash';
export * from './note';
export * from './input';
export * from './utils';
export * from './merkle-tree';
export * from './note-merkle-tree';
export * from './transaction';
27 changes: 25 additions & 2 deletions src/input.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
import { Field } from './field';
import { Note } from './note';
import { InputMap } from '@noir-lang/noir_js';

import input_json from '@ultralane/circuits/bin/input/target/input.json';

import { Field, FieldRaw } from './field';
import { Note, NoteRaw } from './note';
import { circuit } from './utils';

export interface InputRaw extends InputMap {
note: NoteRaw;
path_index: FieldRaw;
path_elements: FieldRaw[];
}

export class Input {
constructor(
Expand All @@ -15,6 +26,10 @@ export class Input {
}
}

static async circuit() {
return circuit(input_json);
}

static async random(
note?: Note,
pathIndex?: Field,
Expand All @@ -29,4 +44,12 @@ export class Input {
pathElements
);
}

raw(): InputRaw {
return {
note: this.note.raw(),
path_index: this.pathIndex.raw(),
path_elements: this.pathElements.map((e) => e.raw()),
};
}
}
38 changes: 37 additions & 1 deletion src/keypair.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
import { InputMap, ProofData } from '@noir-lang/noir_js';

import keypair_json from '@ultralane/circuits/bin/keypair/target/keypair.json';

import {
getCreate2Address,
AddressLike,
resolveAddress,
BytesLike,
} from 'ethers';

import { circuit } from './utils';
import { Field } from './field';
import { hash } from './hash';
import { hash, hash_prove } from './hash';

export interface KeyPairRaw extends InputMap {
public_key: string;
private_key: string;
}

const _guard = {};

Expand Down Expand Up @@ -46,4 +60,26 @@ export class KeyPair {
async sign(message: Field[]): Promise<Field> {
return hash([this.privateKey!, ...message]);
}

async deriveStealthAddress(
i: number,
pool: AddressLike,
initCodeHash: BytesLike
): Promise<{ salt: Field; address: string }> {
const salt = await hash([this.privateKey!, Field.from(i)]);
pool = await resolveAddress(pool);
const address = getCreate2Address(pool, salt.hex(), initCodeHash);
return { salt, address };
}

async proveStealthAddressOwnership(i: number): Promise<ProofData> {
return hash_prove([this.privateKey!, Field.from(i)]);
}

raw(): KeyPairRaw {
return {
public_key: this.publicKey!.raw(),
private_key: this.privateKey!.raw(),
};
}
}
44 changes: 44 additions & 0 deletions src/merkle-tree.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import { Noir, ProofData } from '@noir-lang/noir_js';

import compute_merkle_root from '@ultralane/circuits/bin/compute_merkle_root/target/compute_merkle_root.json';

import { circuit } from './utils';
import { hash } from './hash';
import { Field } from './field';

Expand Down Expand Up @@ -98,6 +103,24 @@ export class MerkleTree {
return proof;
}

/**
* Generates a zk proof for a given element
* @param index Index of the element to generate the zk proof for
* @returns A zk proof for the element
*/
async zkProof(index: number): Promise<ProofData> {
const noir = await this._circuit();
let proof = await noir.generateProof({
leaf: (await this.elements[index]).hex(),
index: index,
hash_path: (await this.merkleProof(index)).map((x) => x.hex()),
});
if (!(await noir.verifyProof(proof))) {
throw new Error('Proof is invalid');
}
return proof;
}

async _null_root(
depth: number,
null_leaf: Field = Field.zero()
Expand All @@ -117,4 +140,25 @@ export class MerkleTree {
this.null_root_cache[null_leaf.hex()][depth] = node;
return node;
}

async _circuit(): Promise<Noir> {
let json;
switch (this.depth) {
// case 3:
// json = merkle_3;
// break;
// case 8:
// json = merkle_8;
// break;
// case 16:
// json = merkle_16;
// break;
case 32:
json = compute_merkle_root;
break;
default:
throw new Error('depth not supported: ' + this.depth);
}
return circuit(json);
}
}
27 changes: 26 additions & 1 deletion src/note-merkle-tree.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import split_join_16_json from '@ultralane/circuits/bin/split_join_16/target/split_join_16.json';
import split_join_32_json from '@ultralane/circuits/bin/split_join_32/target/split_join_32.json';

import { BigNumberish } from 'ethers';

import { Field } from './field';
import { MerkleTree } from './merkle-tree';
import { Note } from './note';
import { Input } from './input';
import { KeyPair } from './keypair';
import { circuit } from './utils';
import { Transaction } from './transaction';

interface CreateTransactionArgs {
Expand Down Expand Up @@ -87,6 +91,16 @@ export class NoteMerkleTree extends MerkleTree {
throw new Error('exactly 2 inputs are supported');
}

// check
let noir = await circuit(this._getJson());
await noir.execute({
root: root.hex(),
inputs: inputs.map((x) => x.raw()),
outputs: [note.raw()],
deposit_amount: depositAmount.raw(),
ext_data: withdrawAddress.raw(),
});

if (updateTree) {
this.insert(await note.commitment());
}
Expand All @@ -112,7 +126,7 @@ export class NoteMerkleTree extends MerkleTree {
throw new Error(
`Note not found from list of ${
this.elements.length
} notes: ${JSON.stringify(note.toString())}`
} notes: ${JSON.stringify(note.raw())}`
);
}

Expand All @@ -125,4 +139,15 @@ export class NoteMerkleTree extends MerkleTree {
this.depth
);
}

_getJson() {
switch (this.depth) {
case 16:
return split_join_16_json;
case 32:
return split_join_32_json;
default:
throw new Error('depth not supported: ' + this.depth);
}
}
}
47 changes: 45 additions & 2 deletions src/note.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
import { InputMap } from '@noir-lang/noir_js';
import note_json from '@ultralane/circuits/bin/note/target/note.json';

import { BigNumberish } from 'ethers';

import { Field } from './field';
import { hash } from './hash';
import { KeyPair } from './keypair';
import { KeyPair, KeyPairRaw } from './keypair';
import { Commitable, circuit } from './utils';

export interface NoteRaw extends InputMap {
amount: string;
keypair: KeyPairRaw;
blinding: string;
}

export class Note {
export class Note extends Commitable {
public amount: Field;

constructor(
amount: Field | BigNumberish,
public keypair: KeyPair,
public blinding: Field = Field.random()
) {
super();
this.amount = Field.from(amount);
}

Expand All @@ -27,6 +38,10 @@ export class Note {
return new Note(Field.zero(), KeyPair.zero(), Field.zero());
}

static async circuit() {
return circuit(note_json);
}

async commitment(): Promise<Field> {
return hash([this.amount, this.keypair.publicKey!, this.blinding]);
}
Expand All @@ -50,4 +65,32 @@ export class Note {
async nullifierHex(merkle_index: Field): Promise<string> {
return (await this.nullifier(merkle_index)).hex();
}

raw(): NoteRaw {
return {
amount: this.amount.raw(),
keypair: this.keypair.raw(),
blinding: this.blinding.raw(),
};
}

async from(json: NoteRaw) {
return new Note(
Field.from(json.amount),
await KeyPair.newAsync(Field.from(json.keypair.private_key)),
Field.from(json.blinding)
);
}

async publicInputs(): Promise<string[]> {
return [this.amount.raw(), await this.commitmentHex()];
}

async prove() {
const noir = await circuit(note_json);
const proof = await noir.generateProof({
input: this.raw(),
});
return proof;
}
}
Loading

0 comments on commit 05b09c9

Please sign in to comment.