mirror of
https://github.com/0xbow-io/privacy-pools-core.git
synced 2026-01-09 09:27:58 -05:00
chore: integrate SDK into contracts tests (#47)
Co-authored-by: Lempire <61431140+lempire123@users.noreply.github.com>
This commit is contained in:
16
.github/workflows/contracts.yml
vendored
16
.github/workflows/contracts.yml
vendored
@@ -65,8 +65,22 @@ jobs:
|
||||
node-version: 20.x
|
||||
cache: "yarn"
|
||||
|
||||
- name: Install dependencies
|
||||
- name: Install root dependencies
|
||||
run: yarn --frozen-lockfile --network-concurrency 1
|
||||
working-directory: .
|
||||
|
||||
- name: Build SDK
|
||||
run: |
|
||||
yarn build
|
||||
chmod +x ./scripts/copy_circuits.sh
|
||||
bash ./scripts/copy_circuits.sh
|
||||
working-directory: packages/sdk
|
||||
|
||||
- name: Link SDK
|
||||
run: |
|
||||
cd ../sdk && yarn link
|
||||
cd ../contracts && yarn link "@privacy-pool-core/sdk"
|
||||
shell: bash
|
||||
|
||||
- name: Precompile
|
||||
run: yarn build
|
||||
|
||||
1
packages/circuits/.gitignore
vendored
1
packages/circuits/.gitignore
vendored
@@ -104,7 +104,6 @@ dist
|
||||
.tern-port
|
||||
|
||||
# builds
|
||||
build
|
||||
dist
|
||||
|
||||
# circuit-specific powers of tau are ignored
|
||||
|
||||
BIN
packages/circuits/build/commitment/commitment.r1cs
Normal file
BIN
packages/circuits/build/commitment/commitment.r1cs
Normal file
Binary file not shown.
2288
packages/circuits/build/commitment/commitment.sym
Normal file
2288
packages/circuits/build/commitment/commitment.sym
Normal file
File diff suppressed because it is too large
Load Diff
BIN
packages/circuits/build/commitment/commitment_js/commitment.wasm
Normal file
BIN
packages/circuits/build/commitment/commitment_js/commitment.wasm
Normal file
Binary file not shown.
@@ -0,0 +1,21 @@
|
||||
const wc = require("./witness_calculator.js");
|
||||
const { readFileSync, writeFile } = require("fs");
|
||||
|
||||
if (process.argv.length != 5) {
|
||||
console.log("Usage: node generate_witness.js <file.wasm> <input.json> <output.wtns>");
|
||||
} else {
|
||||
const input = JSON.parse(readFileSync(process.argv[3], "utf8"));
|
||||
|
||||
const buffer = readFileSync(process.argv[2]);
|
||||
wc(buffer).then(async witnessCalculator => {
|
||||
const w= await witnessCalculator.calculateWitness(input,0);
|
||||
/*
|
||||
for (let i=0; i< w.length; i++){
|
||||
console.log(w[i]);
|
||||
}*/
|
||||
const buff= await witnessCalculator.calculateWTNSBin(input,0);
|
||||
writeFile(process.argv[4], buff, function(err) {
|
||||
if (err) throw err;
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,381 @@
|
||||
module.exports = async function builder(code, options) {
|
||||
|
||||
options = options || {};
|
||||
|
||||
let wasmModule;
|
||||
try {
|
||||
wasmModule = await WebAssembly.compile(code);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
console.log("\nTry to run circom --c in order to generate c++ code instead\n");
|
||||
throw new Error(err);
|
||||
}
|
||||
|
||||
let wc;
|
||||
|
||||
let errStr = "";
|
||||
let msgStr = "";
|
||||
|
||||
const instance = await WebAssembly.instantiate(wasmModule, {
|
||||
runtime: {
|
||||
exceptionHandler : function(code) {
|
||||
let err;
|
||||
if (code == 1) {
|
||||
err = "Signal not found.\n";
|
||||
} else if (code == 2) {
|
||||
err = "Too many signals set.\n";
|
||||
} else if (code == 3) {
|
||||
err = "Signal already set.\n";
|
||||
} else if (code == 4) {
|
||||
err = "Assert Failed.\n";
|
||||
} else if (code == 5) {
|
||||
err = "Not enough memory.\n";
|
||||
} else if (code == 6) {
|
||||
err = "Input signal array access exceeds the size.\n";
|
||||
} else {
|
||||
err = "Unknown error.\n";
|
||||
}
|
||||
throw new Error(err + errStr);
|
||||
},
|
||||
printErrorMessage : function() {
|
||||
errStr += getMessage() + "\n";
|
||||
// console.error(getMessage());
|
||||
},
|
||||
writeBufferMessage : function() {
|
||||
const msg = getMessage();
|
||||
// Any calls to `log()` will always end with a `\n`, so that's when we print and reset
|
||||
if (msg === "\n") {
|
||||
console.log(msgStr);
|
||||
msgStr = "";
|
||||
} else {
|
||||
// If we've buffered other content, put a space in between the items
|
||||
if (msgStr !== "") {
|
||||
msgStr += " "
|
||||
}
|
||||
// Then append the message to the message we are creating
|
||||
msgStr += msg;
|
||||
}
|
||||
},
|
||||
showSharedRWMemory : function() {
|
||||
printSharedRWMemory ();
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
const sanityCheck =
|
||||
options
|
||||
// options &&
|
||||
// (
|
||||
// options.sanityCheck ||
|
||||
// options.logGetSignal ||
|
||||
// options.logSetSignal ||
|
||||
// options.logStartComponent ||
|
||||
// options.logFinishComponent
|
||||
// );
|
||||
|
||||
|
||||
wc = new WitnessCalculator(instance, sanityCheck);
|
||||
return wc;
|
||||
|
||||
function getMessage() {
|
||||
var message = "";
|
||||
var c = instance.exports.getMessageChar();
|
||||
while ( c != 0 ) {
|
||||
message += String.fromCharCode(c);
|
||||
c = instance.exports.getMessageChar();
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
function printSharedRWMemory () {
|
||||
const shared_rw_memory_size = instance.exports.getFieldNumLen32();
|
||||
const arr = new Uint32Array(shared_rw_memory_size);
|
||||
for (let j=0; j<shared_rw_memory_size; j++) {
|
||||
arr[shared_rw_memory_size-1-j] = instance.exports.readSharedRWMemory(j);
|
||||
}
|
||||
|
||||
// If we've buffered other content, put a space in between the items
|
||||
if (msgStr !== "") {
|
||||
msgStr += " "
|
||||
}
|
||||
// Then append the value to the message we are creating
|
||||
msgStr += (fromArray32(arr).toString());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class WitnessCalculator {
|
||||
constructor(instance, sanityCheck) {
|
||||
this.instance = instance;
|
||||
|
||||
this.version = this.instance.exports.getVersion();
|
||||
this.n32 = this.instance.exports.getFieldNumLen32();
|
||||
|
||||
this.instance.exports.getRawPrime();
|
||||
const arr = new Uint32Array(this.n32);
|
||||
for (let i=0; i<this.n32; i++) {
|
||||
arr[this.n32-1-i] = this.instance.exports.readSharedRWMemory(i);
|
||||
}
|
||||
this.prime = fromArray32(arr);
|
||||
|
||||
this.witnessSize = this.instance.exports.getWitnessSize();
|
||||
|
||||
this.sanityCheck = sanityCheck;
|
||||
}
|
||||
|
||||
circom_version() {
|
||||
return this.instance.exports.getVersion();
|
||||
}
|
||||
|
||||
async _doCalculateWitness(input_orig, sanityCheck) {
|
||||
//input is assumed to be a map from signals to arrays of bigints
|
||||
this.instance.exports.init((this.sanityCheck || sanityCheck) ? 1 : 0);
|
||||
let prefix = "";
|
||||
var input = new Object();
|
||||
//console.log("Input: ", input_orig);
|
||||
qualify_input(prefix,input_orig,input);
|
||||
//console.log("Input after: ",input);
|
||||
const keys = Object.keys(input);
|
||||
var input_counter = 0;
|
||||
keys.forEach( (k) => {
|
||||
const h = fnvHash(k);
|
||||
const hMSB = parseInt(h.slice(0,8), 16);
|
||||
const hLSB = parseInt(h.slice(8,16), 16);
|
||||
const fArr = flatArray(input[k]);
|
||||
let signalSize = this.instance.exports.getInputSignalSize(hMSB, hLSB);
|
||||
if (signalSize < 0){
|
||||
throw new Error(`Signal ${k} not found\n`);
|
||||
}
|
||||
if (fArr.length < signalSize) {
|
||||
throw new Error(`Not enough values for input signal ${k}\n`);
|
||||
}
|
||||
if (fArr.length > signalSize) {
|
||||
throw new Error(`Too many values for input signal ${k}\n`);
|
||||
}
|
||||
for (let i=0; i<fArr.length; i++) {
|
||||
const arrFr = toArray32(normalize(fArr[i],this.prime),this.n32)
|
||||
for (let j=0; j<this.n32; j++) {
|
||||
this.instance.exports.writeSharedRWMemory(j,arrFr[this.n32-1-j]);
|
||||
}
|
||||
try {
|
||||
this.instance.exports.setInputSignal(hMSB, hLSB,i);
|
||||
input_counter++;
|
||||
} catch (err) {
|
||||
// console.log(`After adding signal ${i} of ${k}`)
|
||||
throw new Error(err);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
if (input_counter < this.instance.exports.getInputSize()) {
|
||||
throw new Error(`Not all inputs have been set. Only ${input_counter} out of ${this.instance.exports.getInputSize()}`);
|
||||
}
|
||||
}
|
||||
|
||||
async calculateWitness(input, sanityCheck) {
|
||||
|
||||
const w = [];
|
||||
await this._doCalculateWitness(input, sanityCheck);
|
||||
|
||||
for (let i=0; i<this.witnessSize; i++) {
|
||||
this.instance.exports.getWitness(i);
|
||||
const arr = new Uint32Array(this.n32);
|
||||
for (let j=0; j<this.n32; j++) {
|
||||
arr[this.n32-1-j] = this.instance.exports.readSharedRWMemory(j);
|
||||
}
|
||||
w.push(fromArray32(arr));
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
async calculateBinWitness(input, sanityCheck) {
|
||||
|
||||
const buff32 = new Uint32Array(this.witnessSize*this.n32);
|
||||
const buff = new Uint8Array( buff32.buffer);
|
||||
await this._doCalculateWitness(input, sanityCheck);
|
||||
|
||||
for (let i=0; i<this.witnessSize; i++) {
|
||||
this.instance.exports.getWitness(i);
|
||||
const pos = i*this.n32;
|
||||
for (let j=0; j<this.n32; j++) {
|
||||
buff32[pos+j] = this.instance.exports.readSharedRWMemory(j);
|
||||
}
|
||||
}
|
||||
|
||||
return buff;
|
||||
}
|
||||
|
||||
|
||||
async calculateWTNSBin(input, sanityCheck) {
|
||||
|
||||
const buff32 = new Uint32Array(this.witnessSize*this.n32+this.n32+11);
|
||||
const buff = new Uint8Array( buff32.buffer);
|
||||
await this._doCalculateWitness(input, sanityCheck);
|
||||
|
||||
//"wtns"
|
||||
buff[0] = "w".charCodeAt(0)
|
||||
buff[1] = "t".charCodeAt(0)
|
||||
buff[2] = "n".charCodeAt(0)
|
||||
buff[3] = "s".charCodeAt(0)
|
||||
|
||||
//version 2
|
||||
buff32[1] = 2;
|
||||
|
||||
//number of sections: 2
|
||||
buff32[2] = 2;
|
||||
|
||||
//id section 1
|
||||
buff32[3] = 1;
|
||||
|
||||
const n8 = this.n32*4;
|
||||
//id section 1 length in 64bytes
|
||||
const idSection1length = 8 + n8;
|
||||
const idSection1lengthHex = idSection1length.toString(16);
|
||||
buff32[4] = parseInt(idSection1lengthHex.slice(0,8), 16);
|
||||
buff32[5] = parseInt(idSection1lengthHex.slice(8,16), 16);
|
||||
|
||||
//this.n32
|
||||
buff32[6] = n8;
|
||||
|
||||
//prime number
|
||||
this.instance.exports.getRawPrime();
|
||||
|
||||
var pos = 7;
|
||||
for (let j=0; j<this.n32; j++) {
|
||||
buff32[pos+j] = this.instance.exports.readSharedRWMemory(j);
|
||||
}
|
||||
pos += this.n32;
|
||||
|
||||
// witness size
|
||||
buff32[pos] = this.witnessSize;
|
||||
pos++;
|
||||
|
||||
//id section 2
|
||||
buff32[pos] = 2;
|
||||
pos++;
|
||||
|
||||
// section 2 length
|
||||
const idSection2length = n8*this.witnessSize;
|
||||
const idSection2lengthHex = idSection2length.toString(16);
|
||||
buff32[pos] = parseInt(idSection2lengthHex.slice(0,8), 16);
|
||||
buff32[pos+1] = parseInt(idSection2lengthHex.slice(8,16), 16);
|
||||
|
||||
pos += 2;
|
||||
for (let i=0; i<this.witnessSize; i++) {
|
||||
this.instance.exports.getWitness(i);
|
||||
for (let j=0; j<this.n32; j++) {
|
||||
buff32[pos+j] = this.instance.exports.readSharedRWMemory(j);
|
||||
}
|
||||
pos += this.n32;
|
||||
}
|
||||
|
||||
return buff;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
function qualify_input_list(prefix,input,input1){
|
||||
if (Array.isArray(input)) {
|
||||
for (let i = 0; i<input.length; i++) {
|
||||
let new_prefix = prefix + "[" + i + "]";
|
||||
qualify_input_list(new_prefix,input[i],input1);
|
||||
}
|
||||
} else {
|
||||
qualify_input(prefix,input,input1);
|
||||
}
|
||||
}
|
||||
|
||||
function qualify_input(prefix,input,input1) {
|
||||
if (Array.isArray(input)) {
|
||||
a = flatArray(input);
|
||||
if (a.length > 0) {
|
||||
let t = typeof a[0];
|
||||
for (let i = 1; i<a.length; i++) {
|
||||
if (typeof a[i] != t){
|
||||
throw new Error(`Types are not the same in the the key ${prefix}`);
|
||||
}
|
||||
}
|
||||
if (t == "object") {
|
||||
qualify_input_list(prefix,input,input1);
|
||||
} else {
|
||||
input1[prefix] = input;
|
||||
}
|
||||
} else {
|
||||
input1[prefix] = input;
|
||||
}
|
||||
} else if (typeof input == "object") {
|
||||
const keys = Object.keys(input);
|
||||
keys.forEach( (k) => {
|
||||
let new_prefix = prefix == ""? k : prefix + "." + k;
|
||||
qualify_input(new_prefix,input[k],input1);
|
||||
});
|
||||
} else {
|
||||
input1[prefix] = input;
|
||||
}
|
||||
}
|
||||
|
||||
function toArray32(rem,size) {
|
||||
const res = []; //new Uint32Array(size); //has no unshift
|
||||
const radix = BigInt(0x100000000);
|
||||
while (rem) {
|
||||
res.unshift( Number(rem % radix));
|
||||
rem = rem / radix;
|
||||
}
|
||||
if (size) {
|
||||
var i = size - res.length;
|
||||
while (i>0) {
|
||||
res.unshift(0);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function fromArray32(arr) { //returns a BigInt
|
||||
var res = BigInt(0);
|
||||
const radix = BigInt(0x100000000);
|
||||
for (let i = 0; i<arr.length; i++) {
|
||||
res = res*radix + BigInt(arr[i]);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function flatArray(a) {
|
||||
var res = [];
|
||||
fillArray(res, a);
|
||||
return res;
|
||||
|
||||
function fillArray(res, a) {
|
||||
if (Array.isArray(a)) {
|
||||
for (let i=0; i<a.length; i++) {
|
||||
fillArray(res, a[i]);
|
||||
}
|
||||
} else {
|
||||
res.push(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function normalize(n, prime) {
|
||||
let res = BigInt(n) % prime
|
||||
if (res < 0) res += prime
|
||||
return res
|
||||
}
|
||||
|
||||
function fnvHash(str) {
|
||||
const uint64_max = BigInt(2) ** BigInt(64);
|
||||
let hash = BigInt("0xCBF29CE484222325");
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
hash ^= BigInt(str[i].charCodeAt());
|
||||
hash *= BigInt(0x100000001B3);
|
||||
hash %= uint64_max;
|
||||
}
|
||||
let shash = hash.toString(16);
|
||||
let n = 16 - shash.length;
|
||||
shash = '0'.repeat(n).concat(shash);
|
||||
return shash;
|
||||
}
|
||||
BIN
packages/circuits/build/commitment/groth16_pkey.zkey
Normal file
BIN
packages/circuits/build/commitment/groth16_pkey.zkey
Normal file
Binary file not shown.
114
packages/circuits/build/commitment/groth16_vkey.json
Normal file
114
packages/circuits/build/commitment/groth16_vkey.json
Normal file
@@ -0,0 +1,114 @@
|
||||
{
|
||||
"protocol": "groth16",
|
||||
"curve": "bn128",
|
||||
"nPublic": 5,
|
||||
"vk_alpha_1": [
|
||||
"20491192805390485299153009773594534940189261866228447918068658471970481763042",
|
||||
"9383485363053290200918347156157836566562967994039712273449902621266178545958",
|
||||
"1"
|
||||
],
|
||||
"vk_beta_2": [
|
||||
[
|
||||
"6375614351688725206403948262868962793625744043794305715222011528459656738731",
|
||||
"4252822878758300859123897981450591353533073413197771768651442665752259397132"
|
||||
],
|
||||
[
|
||||
"10505242626370262277552901082094356697409835680220590971873171140371331206856",
|
||||
"21847035105528745403288232691147584728191162732299865338377159692350059136679"
|
||||
],
|
||||
[
|
||||
"1",
|
||||
"0"
|
||||
]
|
||||
],
|
||||
"vk_gamma_2": [
|
||||
[
|
||||
"10857046999023057135944570762232829481370756359578518086990519993285655852781",
|
||||
"11559732032986387107991004021392285783925812861821192530917403151452391805634"
|
||||
],
|
||||
[
|
||||
"8495653923123431417604973247489272438418190587263600148770280649306958101930",
|
||||
"4082367875863433681332203403145435568316851327593401208105741076214120093531"
|
||||
],
|
||||
[
|
||||
"1",
|
||||
"0"
|
||||
]
|
||||
],
|
||||
"vk_delta_2": [
|
||||
[
|
||||
"898097132297908470451554122776098576315968266972858647533165182875866952791",
|
||||
"1566345194044855115881573792161398148180261128047745808006007461179430151610"
|
||||
],
|
||||
[
|
||||
"15906465618553129247467149629216463797378789817015585134048618945243648523528",
|
||||
"15822370534108199031188891317296695687790226659418312260844092412205261640832"
|
||||
],
|
||||
[
|
||||
"1",
|
||||
"0"
|
||||
]
|
||||
],
|
||||
"vk_alphabeta_12": [
|
||||
[
|
||||
[
|
||||
"2029413683389138792403550203267699914886160938906632433982220835551125967885",
|
||||
"21072700047562757817161031222997517981543347628379360635925549008442030252106"
|
||||
],
|
||||
[
|
||||
"5940354580057074848093997050200682056184807770593307860589430076672439820312",
|
||||
"12156638873931618554171829126792193045421052652279363021382169897324752428276"
|
||||
],
|
||||
[
|
||||
"7898200236362823042373859371574133993780991612861777490112507062703164551277",
|
||||
"7074218545237549455313236346927434013100842096812539264420499035217050630853"
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
"7077479683546002997211712695946002074877511277312570035766170199895071832130",
|
||||
"10093483419865920389913245021038182291233451549023025229112148274109565435465"
|
||||
],
|
||||
[
|
||||
"4595479056700221319381530156280926371456704509942304414423590385166031118820",
|
||||
"19831328484489333784475432780421641293929726139240675179672856274388269393268"
|
||||
],
|
||||
[
|
||||
"11934129596455521040620786944827826205713621633706285934057045369193958244500",
|
||||
"8037395052364110730298837004334506829870972346962140206007064471173334027475"
|
||||
]
|
||||
]
|
||||
],
|
||||
"IC": [
|
||||
[
|
||||
"1572230892394329298681454529771558079791160063426885123778364988544600092204",
|
||||
"10907590284113869617484274240268476524847769824791981908687430628861786438015",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"10474414297782319012492981593026892901081275462495776991555687221816541216900",
|
||||
"15321095481963456890874969330033977457618275793259026176586929376066181453736",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"13138702880773387357558529934705632835113674933276916583589324350059927789153",
|
||||
"6064914178481296202591806117982402658654598116872976684106810010371403125019",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"10719343491775345794099860407704182788959155125855439786428064016754111035422",
|
||||
"7779139665335559405950032410441065252584147304930423206355909646039958820020",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"16910579626507489012151445846795295827027101681136434702411459401969953620581",
|
||||
"20647160295577946447627355853840077359681537432857792991384502843165850580199",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"18485600617966003417591456535707369551725037876608081280415615642437854736144",
|
||||
"11477648431712045495256626524643422283393263725368967050936220244116015568244",
|
||||
"1"
|
||||
]
|
||||
]
|
||||
}
|
||||
BIN
packages/circuits/build/merkleTree/groth16_pkey.zkey
Normal file
BIN
packages/circuits/build/merkleTree/groth16_pkey.zkey
Normal file
Binary file not shown.
269
packages/circuits/build/merkleTree/groth16_vkey.json
Normal file
269
packages/circuits/build/merkleTree/groth16_vkey.json
Normal file
@@ -0,0 +1,269 @@
|
||||
{
|
||||
"protocol": "groth16",
|
||||
"curve": "bn128",
|
||||
"nPublic": 36,
|
||||
"vk_alpha_1": [
|
||||
"20491192805390485299153009773594534940189261866228447918068658471970481763042",
|
||||
"9383485363053290200918347156157836566562967994039712273449902621266178545958",
|
||||
"1"
|
||||
],
|
||||
"vk_beta_2": [
|
||||
[
|
||||
"6375614351688725206403948262868962793625744043794305715222011528459656738731",
|
||||
"4252822878758300859123897981450591353533073413197771768651442665752259397132"
|
||||
],
|
||||
[
|
||||
"10505242626370262277552901082094356697409835680220590971873171140371331206856",
|
||||
"21847035105528745403288232691147584728191162732299865338377159692350059136679"
|
||||
],
|
||||
[
|
||||
"1",
|
||||
"0"
|
||||
]
|
||||
],
|
||||
"vk_gamma_2": [
|
||||
[
|
||||
"10857046999023057135944570762232829481370756359578518086990519993285655852781",
|
||||
"11559732032986387107991004021392285783925812861821192530917403151452391805634"
|
||||
],
|
||||
[
|
||||
"8495653923123431417604973247489272438418190587263600148770280649306958101930",
|
||||
"4082367875863433681332203403145435568316851327593401208105741076214120093531"
|
||||
],
|
||||
[
|
||||
"1",
|
||||
"0"
|
||||
]
|
||||
],
|
||||
"vk_delta_2": [
|
||||
[
|
||||
"7293509593764445741572780348188486736774197953251602660226893707791894022844",
|
||||
"17595143459289278985334958984697877312612244071428999760626200005733738862376"
|
||||
],
|
||||
[
|
||||
"14316487887725813289703135026360860371948516767541336268439836314652291349030",
|
||||
"16607097246723879725479027737155288871157594317839486880379766022134571857995"
|
||||
],
|
||||
[
|
||||
"1",
|
||||
"0"
|
||||
]
|
||||
],
|
||||
"vk_alphabeta_12": [
|
||||
[
|
||||
[
|
||||
"2029413683389138792403550203267699914886160938906632433982220835551125967885",
|
||||
"21072700047562757817161031222997517981543347628379360635925549008442030252106"
|
||||
],
|
||||
[
|
||||
"5940354580057074848093997050200682056184807770593307860589430076672439820312",
|
||||
"12156638873931618554171829126792193045421052652279363021382169897324752428276"
|
||||
],
|
||||
[
|
||||
"7898200236362823042373859371574133993780991612861777490112507062703164551277",
|
||||
"7074218545237549455313236346927434013100842096812539264420499035217050630853"
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
"7077479683546002997211712695946002074877511277312570035766170199895071832130",
|
||||
"10093483419865920389913245021038182291233451549023025229112148274109565435465"
|
||||
],
|
||||
[
|
||||
"4595479056700221319381530156280926371456704509942304414423590385166031118820",
|
||||
"19831328484489333784475432780421641293929726139240675179672856274388269393268"
|
||||
],
|
||||
[
|
||||
"11934129596455521040620786944827826205713621633706285934057045369193958244500",
|
||||
"8037395052364110730298837004334506829870972346962140206007064471173334027475"
|
||||
]
|
||||
]
|
||||
],
|
||||
"IC": [
|
||||
[
|
||||
"9497813076528340138316588733213449892462953064404245059359205470345687870574",
|
||||
"17808086660268581915670664163873243806996040594422184551433403450429503599996",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"19249706472283364215784093095449617676470453777949267673861634347574220428927",
|
||||
"6898617059546449436574330675449095600862730117812008874226138582598698541852",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"8854344932830623390786565645528891758311408517441172771419019006407757849385",
|
||||
"2782611401689197757111432955519607282826863716737357730627430787031126224793",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"18846595300489200157732179775593614002857824881031310161580602846067953323150",
|
||||
"16028456420198545138309260677227574238060447601635222950735109005975641112920",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"14454877749742005256357922924433988777025983408707946979226974829243536637737",
|
||||
"20847955748973497526923301042367409295338860651333968355371518665790383534317",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"3961645360245855899387296526310522356664444460696089319047952867702506437669",
|
||||
"19845166069178640737100132906148528997835192771203663778999162641928409682227",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"3671013875226320075639424148731713602537537316866071166478350737725112294407",
|
||||
"21653555742035219054398874329088114721906045680901325095162336765074020219312",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"7542846817623480697482077606890271816486816504974630019920502084495800393844",
|
||||
"19795393283524424539331924244513658308956536395728415457289404723833111888800",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"2976335300664535599439510327066547939400640057785395056546905328150132646610",
|
||||
"12684791957729307221637014495760244421444397081471360513124916190354295117777",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"20700996052670748683629792535377946144279698374897594854973612277594264135474",
|
||||
"18074301891372912154273215363039088422275385239159118082981483124856214882703",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"6738235903083663924013977738434128427782563092289645758900248911175331943939",
|
||||
"19438547289229906904943267694246194804241179491152551957145568254599884814550",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"7521420748518552369921014791308387251686577615437374303200193963706657478973",
|
||||
"18614789563345871852499840243502432944922227938758106299517972898579490281367",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"5196618861934682262201965345263639901442044162688769753475091115495247231672",
|
||||
"16144687578840589928528837505742623815779952410118406136925948979662598056500",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"13133727748925297740120531984496198660322414224627810040663981985541350351387",
|
||||
"2668034394624482754437217333672961223695379472248518288032476607955996898514",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"703681476615025540730822238794622373175518697284673100509994600503123574860",
|
||||
"9231107425571001856107661417267189486896768051348605481496026150342958824829",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"4910090695196254005681098567586673542640701300977769881681181120714990956800",
|
||||
"11058482947846412823895986548516650461466845691816940607282489068039657585916",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"2537302447348223671161364934156452910763482665593249686267455527309219528507",
|
||||
"3991169649149893463765233150620517684474473364870049810768295743133827244994",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"19521891266704169586634079250893606520475825744424102298418983183220434356970",
|
||||
"1339689779000298046262989689403820313642223407995155813951887023999223301103",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"4059744339414146146802264207802868793245353725742960534823162705815729904614",
|
||||
"20662197903169571331623163797429732661230086722946376311793426163313366028045",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"11231594852527425443072273404738766417971576329013151034328917723152831268863",
|
||||
"1343096281966196787980503205893540458554758304151674442300653531760968510021",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"11484709672206176246805916894227843696681251412400242518806989261366415260742",
|
||||
"8666460026426066629171468096392026465152145290663213086280759952444485022389",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"6348995198304628732464047032450528138727014786074923113696093876399338440522",
|
||||
"631016086194326213997243920735610146987204415332280312337399025841270013275",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"15673591614204743452982108858785372031532804882733400843610305724785042684731",
|
||||
"17229707127287137411753849396431747650610342119355602370390415719292944827472",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"7496321795330954980771290685906929034131296023068431292631254574679778358687",
|
||||
"5975643633028828309397519466245390241785854797307648043399683411726394888870",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"3453910334384059212165934400259823393094230259188372169351508012693771006589",
|
||||
"20047900571952735242721782023806262182636981434308366560875469211378804752572",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"5176306098339201918963582712091138717953177044671695015150460988443736919876",
|
||||
"15105125775842216617956876126520886107649015385899855729590783969290888131470",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"7347780338610901287268040824269961011880620858645607996053478101456733217169",
|
||||
"8143411679970342139788617939995661768986201520370669284340109148895637020925",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"8363465659265491400608213147202638532716283018596285533903307020516246656903",
|
||||
"17909158371458046147584286550608666794918923760860225380322584473067123307420",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"9727069811344288904039267194092859427813946491272045087813015989659784227932",
|
||||
"17684286751876046119969119432422616908727363356691351041378648073869662447850",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"1140506746377437955982387035880211597151224070967959746323393101720730534434",
|
||||
"18385981680704086953675476967627801747441060086587687553016974232936859558913",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"20278859612395482938438701941136102116811466692968715076527509286306128434457",
|
||||
"11359761943764629813035447564060336186415497033507269525370923623338654997719",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"12762952972599126503746073044016688062996547665647695217604936818374030710452",
|
||||
"13333666643646397541828989655201603485834750934303416401841862491076153152032",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"4848886485415867160058838601199445840953813354726296766595825087135031239348",
|
||||
"20713350927920929219519137673966565208175518090599693569948897590096628657756",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"3949983651856626007523224065122876945275949274677819534645668134577618573581",
|
||||
"1619937215550744087285843782317348414009888101764848011791583180300916974708",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"16662058216940346546792073736723846059840894617210896839550930283911572579181",
|
||||
"16890664454650841636976003648190148109116710460481245970237602433115188016036",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"3958090283350290066697830268679437821960476483991768096126944493430961596427",
|
||||
"10310255206429207503298367306614550398219906917747676202962711264506743689095",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"7398712724064053143392770525213410239543426683092096903575135263083277584621",
|
||||
"8702533950231630014787759903158177627126133709810287535841613975484585252337",
|
||||
"1"
|
||||
]
|
||||
]
|
||||
}
|
||||
BIN
packages/circuits/build/merkleTree/merkleTree.r1cs
Normal file
BIN
packages/circuits/build/merkleTree/merkleTree.r1cs
Normal file
Binary file not shown.
24998
packages/circuits/build/merkleTree/merkleTree.sym
Normal file
24998
packages/circuits/build/merkleTree/merkleTree.sym
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
||||
const wc = require("./witness_calculator.js");
|
||||
const { readFileSync, writeFile } = require("fs");
|
||||
|
||||
if (process.argv.length != 5) {
|
||||
console.log("Usage: node generate_witness.js <file.wasm> <input.json> <output.wtns>");
|
||||
} else {
|
||||
const input = JSON.parse(readFileSync(process.argv[3], "utf8"));
|
||||
|
||||
const buffer = readFileSync(process.argv[2]);
|
||||
wc(buffer).then(async witnessCalculator => {
|
||||
const w= await witnessCalculator.calculateWitness(input,0);
|
||||
/*
|
||||
for (let i=0; i< w.length; i++){
|
||||
console.log(w[i]);
|
||||
}*/
|
||||
const buff= await witnessCalculator.calculateWTNSBin(input,0);
|
||||
writeFile(process.argv[4], buff, function(err) {
|
||||
if (err) throw err;
|
||||
});
|
||||
});
|
||||
}
|
||||
BIN
packages/circuits/build/merkleTree/merkleTree_js/merkleTree.wasm
Normal file
BIN
packages/circuits/build/merkleTree/merkleTree_js/merkleTree.wasm
Normal file
Binary file not shown.
@@ -0,0 +1,381 @@
|
||||
module.exports = async function builder(code, options) {
|
||||
|
||||
options = options || {};
|
||||
|
||||
let wasmModule;
|
||||
try {
|
||||
wasmModule = await WebAssembly.compile(code);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
console.log("\nTry to run circom --c in order to generate c++ code instead\n");
|
||||
throw new Error(err);
|
||||
}
|
||||
|
||||
let wc;
|
||||
|
||||
let errStr = "";
|
||||
let msgStr = "";
|
||||
|
||||
const instance = await WebAssembly.instantiate(wasmModule, {
|
||||
runtime: {
|
||||
exceptionHandler : function(code) {
|
||||
let err;
|
||||
if (code == 1) {
|
||||
err = "Signal not found.\n";
|
||||
} else if (code == 2) {
|
||||
err = "Too many signals set.\n";
|
||||
} else if (code == 3) {
|
||||
err = "Signal already set.\n";
|
||||
} else if (code == 4) {
|
||||
err = "Assert Failed.\n";
|
||||
} else if (code == 5) {
|
||||
err = "Not enough memory.\n";
|
||||
} else if (code == 6) {
|
||||
err = "Input signal array access exceeds the size.\n";
|
||||
} else {
|
||||
err = "Unknown error.\n";
|
||||
}
|
||||
throw new Error(err + errStr);
|
||||
},
|
||||
printErrorMessage : function() {
|
||||
errStr += getMessage() + "\n";
|
||||
// console.error(getMessage());
|
||||
},
|
||||
writeBufferMessage : function() {
|
||||
const msg = getMessage();
|
||||
// Any calls to `log()` will always end with a `\n`, so that's when we print and reset
|
||||
if (msg === "\n") {
|
||||
console.log(msgStr);
|
||||
msgStr = "";
|
||||
} else {
|
||||
// If we've buffered other content, put a space in between the items
|
||||
if (msgStr !== "") {
|
||||
msgStr += " "
|
||||
}
|
||||
// Then append the message to the message we are creating
|
||||
msgStr += msg;
|
||||
}
|
||||
},
|
||||
showSharedRWMemory : function() {
|
||||
printSharedRWMemory ();
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
const sanityCheck =
|
||||
options
|
||||
// options &&
|
||||
// (
|
||||
// options.sanityCheck ||
|
||||
// options.logGetSignal ||
|
||||
// options.logSetSignal ||
|
||||
// options.logStartComponent ||
|
||||
// options.logFinishComponent
|
||||
// );
|
||||
|
||||
|
||||
wc = new WitnessCalculator(instance, sanityCheck);
|
||||
return wc;
|
||||
|
||||
function getMessage() {
|
||||
var message = "";
|
||||
var c = instance.exports.getMessageChar();
|
||||
while ( c != 0 ) {
|
||||
message += String.fromCharCode(c);
|
||||
c = instance.exports.getMessageChar();
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
function printSharedRWMemory () {
|
||||
const shared_rw_memory_size = instance.exports.getFieldNumLen32();
|
||||
const arr = new Uint32Array(shared_rw_memory_size);
|
||||
for (let j=0; j<shared_rw_memory_size; j++) {
|
||||
arr[shared_rw_memory_size-1-j] = instance.exports.readSharedRWMemory(j);
|
||||
}
|
||||
|
||||
// If we've buffered other content, put a space in between the items
|
||||
if (msgStr !== "") {
|
||||
msgStr += " "
|
||||
}
|
||||
// Then append the value to the message we are creating
|
||||
msgStr += (fromArray32(arr).toString());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class WitnessCalculator {
|
||||
constructor(instance, sanityCheck) {
|
||||
this.instance = instance;
|
||||
|
||||
this.version = this.instance.exports.getVersion();
|
||||
this.n32 = this.instance.exports.getFieldNumLen32();
|
||||
|
||||
this.instance.exports.getRawPrime();
|
||||
const arr = new Uint32Array(this.n32);
|
||||
for (let i=0; i<this.n32; i++) {
|
||||
arr[this.n32-1-i] = this.instance.exports.readSharedRWMemory(i);
|
||||
}
|
||||
this.prime = fromArray32(arr);
|
||||
|
||||
this.witnessSize = this.instance.exports.getWitnessSize();
|
||||
|
||||
this.sanityCheck = sanityCheck;
|
||||
}
|
||||
|
||||
circom_version() {
|
||||
return this.instance.exports.getVersion();
|
||||
}
|
||||
|
||||
async _doCalculateWitness(input_orig, sanityCheck) {
|
||||
//input is assumed to be a map from signals to arrays of bigints
|
||||
this.instance.exports.init((this.sanityCheck || sanityCheck) ? 1 : 0);
|
||||
let prefix = "";
|
||||
var input = new Object();
|
||||
//console.log("Input: ", input_orig);
|
||||
qualify_input(prefix,input_orig,input);
|
||||
//console.log("Input after: ",input);
|
||||
const keys = Object.keys(input);
|
||||
var input_counter = 0;
|
||||
keys.forEach( (k) => {
|
||||
const h = fnvHash(k);
|
||||
const hMSB = parseInt(h.slice(0,8), 16);
|
||||
const hLSB = parseInt(h.slice(8,16), 16);
|
||||
const fArr = flatArray(input[k]);
|
||||
let signalSize = this.instance.exports.getInputSignalSize(hMSB, hLSB);
|
||||
if (signalSize < 0){
|
||||
throw new Error(`Signal ${k} not found\n`);
|
||||
}
|
||||
if (fArr.length < signalSize) {
|
||||
throw new Error(`Not enough values for input signal ${k}\n`);
|
||||
}
|
||||
if (fArr.length > signalSize) {
|
||||
throw new Error(`Too many values for input signal ${k}\n`);
|
||||
}
|
||||
for (let i=0; i<fArr.length; i++) {
|
||||
const arrFr = toArray32(normalize(fArr[i],this.prime),this.n32)
|
||||
for (let j=0; j<this.n32; j++) {
|
||||
this.instance.exports.writeSharedRWMemory(j,arrFr[this.n32-1-j]);
|
||||
}
|
||||
try {
|
||||
this.instance.exports.setInputSignal(hMSB, hLSB,i);
|
||||
input_counter++;
|
||||
} catch (err) {
|
||||
// console.log(`After adding signal ${i} of ${k}`)
|
||||
throw new Error(err);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
if (input_counter < this.instance.exports.getInputSize()) {
|
||||
throw new Error(`Not all inputs have been set. Only ${input_counter} out of ${this.instance.exports.getInputSize()}`);
|
||||
}
|
||||
}
|
||||
|
||||
async calculateWitness(input, sanityCheck) {
|
||||
|
||||
const w = [];
|
||||
await this._doCalculateWitness(input, sanityCheck);
|
||||
|
||||
for (let i=0; i<this.witnessSize; i++) {
|
||||
this.instance.exports.getWitness(i);
|
||||
const arr = new Uint32Array(this.n32);
|
||||
for (let j=0; j<this.n32; j++) {
|
||||
arr[this.n32-1-j] = this.instance.exports.readSharedRWMemory(j);
|
||||
}
|
||||
w.push(fromArray32(arr));
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
async calculateBinWitness(input, sanityCheck) {
|
||||
|
||||
const buff32 = new Uint32Array(this.witnessSize*this.n32);
|
||||
const buff = new Uint8Array( buff32.buffer);
|
||||
await this._doCalculateWitness(input, sanityCheck);
|
||||
|
||||
for (let i=0; i<this.witnessSize; i++) {
|
||||
this.instance.exports.getWitness(i);
|
||||
const pos = i*this.n32;
|
||||
for (let j=0; j<this.n32; j++) {
|
||||
buff32[pos+j] = this.instance.exports.readSharedRWMemory(j);
|
||||
}
|
||||
}
|
||||
|
||||
return buff;
|
||||
}
|
||||
|
||||
|
||||
async calculateWTNSBin(input, sanityCheck) {
|
||||
|
||||
const buff32 = new Uint32Array(this.witnessSize*this.n32+this.n32+11);
|
||||
const buff = new Uint8Array( buff32.buffer);
|
||||
await this._doCalculateWitness(input, sanityCheck);
|
||||
|
||||
//"wtns"
|
||||
buff[0] = "w".charCodeAt(0)
|
||||
buff[1] = "t".charCodeAt(0)
|
||||
buff[2] = "n".charCodeAt(0)
|
||||
buff[3] = "s".charCodeAt(0)
|
||||
|
||||
//version 2
|
||||
buff32[1] = 2;
|
||||
|
||||
//number of sections: 2
|
||||
buff32[2] = 2;
|
||||
|
||||
//id section 1
|
||||
buff32[3] = 1;
|
||||
|
||||
const n8 = this.n32*4;
|
||||
//id section 1 length in 64bytes
|
||||
const idSection1length = 8 + n8;
|
||||
const idSection1lengthHex = idSection1length.toString(16);
|
||||
buff32[4] = parseInt(idSection1lengthHex.slice(0,8), 16);
|
||||
buff32[5] = parseInt(idSection1lengthHex.slice(8,16), 16);
|
||||
|
||||
//this.n32
|
||||
buff32[6] = n8;
|
||||
|
||||
//prime number
|
||||
this.instance.exports.getRawPrime();
|
||||
|
||||
var pos = 7;
|
||||
for (let j=0; j<this.n32; j++) {
|
||||
buff32[pos+j] = this.instance.exports.readSharedRWMemory(j);
|
||||
}
|
||||
pos += this.n32;
|
||||
|
||||
// witness size
|
||||
buff32[pos] = this.witnessSize;
|
||||
pos++;
|
||||
|
||||
//id section 2
|
||||
buff32[pos] = 2;
|
||||
pos++;
|
||||
|
||||
// section 2 length
|
||||
const idSection2length = n8*this.witnessSize;
|
||||
const idSection2lengthHex = idSection2length.toString(16);
|
||||
buff32[pos] = parseInt(idSection2lengthHex.slice(0,8), 16);
|
||||
buff32[pos+1] = parseInt(idSection2lengthHex.slice(8,16), 16);
|
||||
|
||||
pos += 2;
|
||||
for (let i=0; i<this.witnessSize; i++) {
|
||||
this.instance.exports.getWitness(i);
|
||||
for (let j=0; j<this.n32; j++) {
|
||||
buff32[pos+j] = this.instance.exports.readSharedRWMemory(j);
|
||||
}
|
||||
pos += this.n32;
|
||||
}
|
||||
|
||||
return buff;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
function qualify_input_list(prefix,input,input1){
|
||||
if (Array.isArray(input)) {
|
||||
for (let i = 0; i<input.length; i++) {
|
||||
let new_prefix = prefix + "[" + i + "]";
|
||||
qualify_input_list(new_prefix,input[i],input1);
|
||||
}
|
||||
} else {
|
||||
qualify_input(prefix,input,input1);
|
||||
}
|
||||
}
|
||||
|
||||
function qualify_input(prefix,input,input1) {
|
||||
if (Array.isArray(input)) {
|
||||
a = flatArray(input);
|
||||
if (a.length > 0) {
|
||||
let t = typeof a[0];
|
||||
for (let i = 1; i<a.length; i++) {
|
||||
if (typeof a[i] != t){
|
||||
throw new Error(`Types are not the same in the the key ${prefix}`);
|
||||
}
|
||||
}
|
||||
if (t == "object") {
|
||||
qualify_input_list(prefix,input,input1);
|
||||
} else {
|
||||
input1[prefix] = input;
|
||||
}
|
||||
} else {
|
||||
input1[prefix] = input;
|
||||
}
|
||||
} else if (typeof input == "object") {
|
||||
const keys = Object.keys(input);
|
||||
keys.forEach( (k) => {
|
||||
let new_prefix = prefix == ""? k : prefix + "." + k;
|
||||
qualify_input(new_prefix,input[k],input1);
|
||||
});
|
||||
} else {
|
||||
input1[prefix] = input;
|
||||
}
|
||||
}
|
||||
|
||||
function toArray32(rem,size) {
|
||||
const res = []; //new Uint32Array(size); //has no unshift
|
||||
const radix = BigInt(0x100000000);
|
||||
while (rem) {
|
||||
res.unshift( Number(rem % radix));
|
||||
rem = rem / radix;
|
||||
}
|
||||
if (size) {
|
||||
var i = size - res.length;
|
||||
while (i>0) {
|
||||
res.unshift(0);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function fromArray32(arr) { //returns a BigInt
|
||||
var res = BigInt(0);
|
||||
const radix = BigInt(0x100000000);
|
||||
for (let i = 0; i<arr.length; i++) {
|
||||
res = res*radix + BigInt(arr[i]);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function flatArray(a) {
|
||||
var res = [];
|
||||
fillArray(res, a);
|
||||
return res;
|
||||
|
||||
function fillArray(res, a) {
|
||||
if (Array.isArray(a)) {
|
||||
for (let i=0; i<a.length; i++) {
|
||||
fillArray(res, a[i]);
|
||||
}
|
||||
} else {
|
||||
res.push(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function normalize(n, prime) {
|
||||
let res = BigInt(n) % prime
|
||||
if (res < 0) res += prime
|
||||
return res
|
||||
}
|
||||
|
||||
function fnvHash(str) {
|
||||
const uint64_max = BigInt(2) ** BigInt(64);
|
||||
let hash = BigInt("0xCBF29CE484222325");
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
hash ^= BigInt(str[i].charCodeAt());
|
||||
hash *= BigInt(0x100000001B3);
|
||||
hash %= uint64_max;
|
||||
}
|
||||
let shash = hash.toString(16);
|
||||
let n = 16 - shash.length;
|
||||
shash = '0'.repeat(n).concat(shash);
|
||||
return shash;
|
||||
}
|
||||
BIN
packages/circuits/build/withdraw/groth16_pkey.zkey
Normal file
BIN
packages/circuits/build/withdraw/groth16_pkey.zkey
Normal file
Binary file not shown.
129
packages/circuits/build/withdraw/groth16_vkey.json
Normal file
129
packages/circuits/build/withdraw/groth16_vkey.json
Normal file
@@ -0,0 +1,129 @@
|
||||
{
|
||||
"protocol": "groth16",
|
||||
"curve": "bn128",
|
||||
"nPublic": 8,
|
||||
"vk_alpha_1": [
|
||||
"20491192805390485299153009773594534940189261866228447918068658471970481763042",
|
||||
"9383485363053290200918347156157836566562967994039712273449902621266178545958",
|
||||
"1"
|
||||
],
|
||||
"vk_beta_2": [
|
||||
[
|
||||
"6375614351688725206403948262868962793625744043794305715222011528459656738731",
|
||||
"4252822878758300859123897981450591353533073413197771768651442665752259397132"
|
||||
],
|
||||
[
|
||||
"10505242626370262277552901082094356697409835680220590971873171140371331206856",
|
||||
"21847035105528745403288232691147584728191162732299865338377159692350059136679"
|
||||
],
|
||||
[
|
||||
"1",
|
||||
"0"
|
||||
]
|
||||
],
|
||||
"vk_gamma_2": [
|
||||
[
|
||||
"10857046999023057135944570762232829481370756359578518086990519993285655852781",
|
||||
"11559732032986387107991004021392285783925812861821192530917403151452391805634"
|
||||
],
|
||||
[
|
||||
"8495653923123431417604973247489272438418190587263600148770280649306958101930",
|
||||
"4082367875863433681332203403145435568316851327593401208105741076214120093531"
|
||||
],
|
||||
[
|
||||
"1",
|
||||
"0"
|
||||
]
|
||||
],
|
||||
"vk_delta_2": [
|
||||
[
|
||||
"1486986654560761301713455953272498657121229884579052555795983089481728689679",
|
||||
"21310541422874827293880171982528429236667249433013822057255176270531431868642"
|
||||
],
|
||||
[
|
||||
"21641295423770389167224011883026537472504249086048994666432736497471481714819",
|
||||
"7701627035368501351340317635290605147001193359451711067122274170209344601850"
|
||||
],
|
||||
[
|
||||
"1",
|
||||
"0"
|
||||
]
|
||||
],
|
||||
"vk_alphabeta_12": [
|
||||
[
|
||||
[
|
||||
"2029413683389138792403550203267699914886160938906632433982220835551125967885",
|
||||
"21072700047562757817161031222997517981543347628379360635925549008442030252106"
|
||||
],
|
||||
[
|
||||
"5940354580057074848093997050200682056184807770593307860589430076672439820312",
|
||||
"12156638873931618554171829126792193045421052652279363021382169897324752428276"
|
||||
],
|
||||
[
|
||||
"7898200236362823042373859371574133993780991612861777490112507062703164551277",
|
||||
"7074218545237549455313236346927434013100842096812539264420499035217050630853"
|
||||
]
|
||||
],
|
||||
[
|
||||
[
|
||||
"7077479683546002997211712695946002074877511277312570035766170199895071832130",
|
||||
"10093483419865920389913245021038182291233451549023025229112148274109565435465"
|
||||
],
|
||||
[
|
||||
"4595479056700221319381530156280926371456704509942304414423590385166031118820",
|
||||
"19831328484489333784475432780421641293929726139240675179672856274388269393268"
|
||||
],
|
||||
[
|
||||
"11934129596455521040620786944827826205713621633706285934057045369193958244500",
|
||||
"8037395052364110730298837004334506829870972346962140206007064471173334027475"
|
||||
]
|
||||
]
|
||||
],
|
||||
"IC": [
|
||||
[
|
||||
"16148105666203862965387243430225407356287196650373131595365027485816037911900",
|
||||
"21615999052313154850676241963688611364836973439873693563306467457098331348075",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"13145575450193874255316306319665855572081997698715275916849447632401357731446",
|
||||
"836555222908457845696763107154346624346976089850656490257701631525889114385",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"12197059349166431138974724303199033482190301571498851877111391687392381195938",
|
||||
"1704894320554507498525100014672209992480806357828605226631630626990149408930",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"8141178413351457415236084158729394386655825437906411531082832498448646901965",
|
||||
"19675363546413908975713823178218347287890421074308842936807928595768076605294",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"17196499179582027891027942246916949026674374136496617673383760431730596474777",
|
||||
"14185028421073691544218669605491722641238899407551894132621880121511633035697",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"15853666281260790343165712171318466701136105451825808258033647267174901848674",
|
||||
"21420391690239444554758117369313724729296932815825154131342135686052632329084",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"20905875728535335560111169781316626779771582606165666956375053368850040930925",
|
||||
"1688518663540369383776258717136261524008125744624227835394113982007174060864",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"20359757871341498030337754636881606593312684640209296683737491828747418197565",
|
||||
"16371639775566752906308030676980270689441600748725204364165649296308947022577",
|
||||
"1"
|
||||
],
|
||||
[
|
||||
"10429481242244695482713271321701054592036939456843231359193097479769782286309",
|
||||
"16754892134667431672126588122554824110691553913084171094432716687358411650715",
|
||||
"1"
|
||||
]
|
||||
]
|
||||
}
|
||||
BIN
packages/circuits/build/withdraw/withdraw.r1cs
Normal file
BIN
packages/circuits/build/withdraw/withdraw.r1cs
Normal file
Binary file not shown.
54913
packages/circuits/build/withdraw/withdraw.sym
Normal file
54913
packages/circuits/build/withdraw/withdraw.sym
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
||||
const wc = require("./witness_calculator.js");
|
||||
const { readFileSync, writeFile } = require("fs");
|
||||
|
||||
if (process.argv.length != 5) {
|
||||
console.log("Usage: node generate_witness.js <file.wasm> <input.json> <output.wtns>");
|
||||
} else {
|
||||
const input = JSON.parse(readFileSync(process.argv[3], "utf8"));
|
||||
|
||||
const buffer = readFileSync(process.argv[2]);
|
||||
wc(buffer).then(async witnessCalculator => {
|
||||
const w= await witnessCalculator.calculateWitness(input,0);
|
||||
/*
|
||||
for (let i=0; i< w.length; i++){
|
||||
console.log(w[i]);
|
||||
}*/
|
||||
const buff= await witnessCalculator.calculateWTNSBin(input,0);
|
||||
writeFile(process.argv[4], buff, function(err) {
|
||||
if (err) throw err;
|
||||
});
|
||||
});
|
||||
}
|
||||
BIN
packages/circuits/build/withdraw/withdraw_js/withdraw.wasm
Normal file
BIN
packages/circuits/build/withdraw/withdraw_js/withdraw.wasm
Normal file
Binary file not shown.
@@ -0,0 +1,381 @@
|
||||
module.exports = async function builder(code, options) {
|
||||
|
||||
options = options || {};
|
||||
|
||||
let wasmModule;
|
||||
try {
|
||||
wasmModule = await WebAssembly.compile(code);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
console.log("\nTry to run circom --c in order to generate c++ code instead\n");
|
||||
throw new Error(err);
|
||||
}
|
||||
|
||||
let wc;
|
||||
|
||||
let errStr = "";
|
||||
let msgStr = "";
|
||||
|
||||
const instance = await WebAssembly.instantiate(wasmModule, {
|
||||
runtime: {
|
||||
exceptionHandler : function(code) {
|
||||
let err;
|
||||
if (code == 1) {
|
||||
err = "Signal not found.\n";
|
||||
} else if (code == 2) {
|
||||
err = "Too many signals set.\n";
|
||||
} else if (code == 3) {
|
||||
err = "Signal already set.\n";
|
||||
} else if (code == 4) {
|
||||
err = "Assert Failed.\n";
|
||||
} else if (code == 5) {
|
||||
err = "Not enough memory.\n";
|
||||
} else if (code == 6) {
|
||||
err = "Input signal array access exceeds the size.\n";
|
||||
} else {
|
||||
err = "Unknown error.\n";
|
||||
}
|
||||
throw new Error(err + errStr);
|
||||
},
|
||||
printErrorMessage : function() {
|
||||
errStr += getMessage() + "\n";
|
||||
// console.error(getMessage());
|
||||
},
|
||||
writeBufferMessage : function() {
|
||||
const msg = getMessage();
|
||||
// Any calls to `log()` will always end with a `\n`, so that's when we print and reset
|
||||
if (msg === "\n") {
|
||||
console.log(msgStr);
|
||||
msgStr = "";
|
||||
} else {
|
||||
// If we've buffered other content, put a space in between the items
|
||||
if (msgStr !== "") {
|
||||
msgStr += " "
|
||||
}
|
||||
// Then append the message to the message we are creating
|
||||
msgStr += msg;
|
||||
}
|
||||
},
|
||||
showSharedRWMemory : function() {
|
||||
printSharedRWMemory ();
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
const sanityCheck =
|
||||
options
|
||||
// options &&
|
||||
// (
|
||||
// options.sanityCheck ||
|
||||
// options.logGetSignal ||
|
||||
// options.logSetSignal ||
|
||||
// options.logStartComponent ||
|
||||
// options.logFinishComponent
|
||||
// );
|
||||
|
||||
|
||||
wc = new WitnessCalculator(instance, sanityCheck);
|
||||
return wc;
|
||||
|
||||
function getMessage() {
|
||||
var message = "";
|
||||
var c = instance.exports.getMessageChar();
|
||||
while ( c != 0 ) {
|
||||
message += String.fromCharCode(c);
|
||||
c = instance.exports.getMessageChar();
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
function printSharedRWMemory () {
|
||||
const shared_rw_memory_size = instance.exports.getFieldNumLen32();
|
||||
const arr = new Uint32Array(shared_rw_memory_size);
|
||||
for (let j=0; j<shared_rw_memory_size; j++) {
|
||||
arr[shared_rw_memory_size-1-j] = instance.exports.readSharedRWMemory(j);
|
||||
}
|
||||
|
||||
// If we've buffered other content, put a space in between the items
|
||||
if (msgStr !== "") {
|
||||
msgStr += " "
|
||||
}
|
||||
// Then append the value to the message we are creating
|
||||
msgStr += (fromArray32(arr).toString());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class WitnessCalculator {
|
||||
constructor(instance, sanityCheck) {
|
||||
this.instance = instance;
|
||||
|
||||
this.version = this.instance.exports.getVersion();
|
||||
this.n32 = this.instance.exports.getFieldNumLen32();
|
||||
|
||||
this.instance.exports.getRawPrime();
|
||||
const arr = new Uint32Array(this.n32);
|
||||
for (let i=0; i<this.n32; i++) {
|
||||
arr[this.n32-1-i] = this.instance.exports.readSharedRWMemory(i);
|
||||
}
|
||||
this.prime = fromArray32(arr);
|
||||
|
||||
this.witnessSize = this.instance.exports.getWitnessSize();
|
||||
|
||||
this.sanityCheck = sanityCheck;
|
||||
}
|
||||
|
||||
circom_version() {
|
||||
return this.instance.exports.getVersion();
|
||||
}
|
||||
|
||||
async _doCalculateWitness(input_orig, sanityCheck) {
|
||||
//input is assumed to be a map from signals to arrays of bigints
|
||||
this.instance.exports.init((this.sanityCheck || sanityCheck) ? 1 : 0);
|
||||
let prefix = "";
|
||||
var input = new Object();
|
||||
//console.log("Input: ", input_orig);
|
||||
qualify_input(prefix,input_orig,input);
|
||||
//console.log("Input after: ",input);
|
||||
const keys = Object.keys(input);
|
||||
var input_counter = 0;
|
||||
keys.forEach( (k) => {
|
||||
const h = fnvHash(k);
|
||||
const hMSB = parseInt(h.slice(0,8), 16);
|
||||
const hLSB = parseInt(h.slice(8,16), 16);
|
||||
const fArr = flatArray(input[k]);
|
||||
let signalSize = this.instance.exports.getInputSignalSize(hMSB, hLSB);
|
||||
if (signalSize < 0){
|
||||
throw new Error(`Signal ${k} not found\n`);
|
||||
}
|
||||
if (fArr.length < signalSize) {
|
||||
throw new Error(`Not enough values for input signal ${k}\n`);
|
||||
}
|
||||
if (fArr.length > signalSize) {
|
||||
throw new Error(`Too many values for input signal ${k}\n`);
|
||||
}
|
||||
for (let i=0; i<fArr.length; i++) {
|
||||
const arrFr = toArray32(normalize(fArr[i],this.prime),this.n32)
|
||||
for (let j=0; j<this.n32; j++) {
|
||||
this.instance.exports.writeSharedRWMemory(j,arrFr[this.n32-1-j]);
|
||||
}
|
||||
try {
|
||||
this.instance.exports.setInputSignal(hMSB, hLSB,i);
|
||||
input_counter++;
|
||||
} catch (err) {
|
||||
// console.log(`After adding signal ${i} of ${k}`)
|
||||
throw new Error(err);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
if (input_counter < this.instance.exports.getInputSize()) {
|
||||
throw new Error(`Not all inputs have been set. Only ${input_counter} out of ${this.instance.exports.getInputSize()}`);
|
||||
}
|
||||
}
|
||||
|
||||
async calculateWitness(input, sanityCheck) {
|
||||
|
||||
const w = [];
|
||||
await this._doCalculateWitness(input, sanityCheck);
|
||||
|
||||
for (let i=0; i<this.witnessSize; i++) {
|
||||
this.instance.exports.getWitness(i);
|
||||
const arr = new Uint32Array(this.n32);
|
||||
for (let j=0; j<this.n32; j++) {
|
||||
arr[this.n32-1-j] = this.instance.exports.readSharedRWMemory(j);
|
||||
}
|
||||
w.push(fromArray32(arr));
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
async calculateBinWitness(input, sanityCheck) {
|
||||
|
||||
const buff32 = new Uint32Array(this.witnessSize*this.n32);
|
||||
const buff = new Uint8Array( buff32.buffer);
|
||||
await this._doCalculateWitness(input, sanityCheck);
|
||||
|
||||
for (let i=0; i<this.witnessSize; i++) {
|
||||
this.instance.exports.getWitness(i);
|
||||
const pos = i*this.n32;
|
||||
for (let j=0; j<this.n32; j++) {
|
||||
buff32[pos+j] = this.instance.exports.readSharedRWMemory(j);
|
||||
}
|
||||
}
|
||||
|
||||
return buff;
|
||||
}
|
||||
|
||||
|
||||
async calculateWTNSBin(input, sanityCheck) {
|
||||
|
||||
const buff32 = new Uint32Array(this.witnessSize*this.n32+this.n32+11);
|
||||
const buff = new Uint8Array( buff32.buffer);
|
||||
await this._doCalculateWitness(input, sanityCheck);
|
||||
|
||||
//"wtns"
|
||||
buff[0] = "w".charCodeAt(0)
|
||||
buff[1] = "t".charCodeAt(0)
|
||||
buff[2] = "n".charCodeAt(0)
|
||||
buff[3] = "s".charCodeAt(0)
|
||||
|
||||
//version 2
|
||||
buff32[1] = 2;
|
||||
|
||||
//number of sections: 2
|
||||
buff32[2] = 2;
|
||||
|
||||
//id section 1
|
||||
buff32[3] = 1;
|
||||
|
||||
const n8 = this.n32*4;
|
||||
//id section 1 length in 64bytes
|
||||
const idSection1length = 8 + n8;
|
||||
const idSection1lengthHex = idSection1length.toString(16);
|
||||
buff32[4] = parseInt(idSection1lengthHex.slice(0,8), 16);
|
||||
buff32[5] = parseInt(idSection1lengthHex.slice(8,16), 16);
|
||||
|
||||
//this.n32
|
||||
buff32[6] = n8;
|
||||
|
||||
//prime number
|
||||
this.instance.exports.getRawPrime();
|
||||
|
||||
var pos = 7;
|
||||
for (let j=0; j<this.n32; j++) {
|
||||
buff32[pos+j] = this.instance.exports.readSharedRWMemory(j);
|
||||
}
|
||||
pos += this.n32;
|
||||
|
||||
// witness size
|
||||
buff32[pos] = this.witnessSize;
|
||||
pos++;
|
||||
|
||||
//id section 2
|
||||
buff32[pos] = 2;
|
||||
pos++;
|
||||
|
||||
// section 2 length
|
||||
const idSection2length = n8*this.witnessSize;
|
||||
const idSection2lengthHex = idSection2length.toString(16);
|
||||
buff32[pos] = parseInt(idSection2lengthHex.slice(0,8), 16);
|
||||
buff32[pos+1] = parseInt(idSection2lengthHex.slice(8,16), 16);
|
||||
|
||||
pos += 2;
|
||||
for (let i=0; i<this.witnessSize; i++) {
|
||||
this.instance.exports.getWitness(i);
|
||||
for (let j=0; j<this.n32; j++) {
|
||||
buff32[pos+j] = this.instance.exports.readSharedRWMemory(j);
|
||||
}
|
||||
pos += this.n32;
|
||||
}
|
||||
|
||||
return buff;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
function qualify_input_list(prefix,input,input1){
|
||||
if (Array.isArray(input)) {
|
||||
for (let i = 0; i<input.length; i++) {
|
||||
let new_prefix = prefix + "[" + i + "]";
|
||||
qualify_input_list(new_prefix,input[i],input1);
|
||||
}
|
||||
} else {
|
||||
qualify_input(prefix,input,input1);
|
||||
}
|
||||
}
|
||||
|
||||
function qualify_input(prefix,input,input1) {
|
||||
if (Array.isArray(input)) {
|
||||
a = flatArray(input);
|
||||
if (a.length > 0) {
|
||||
let t = typeof a[0];
|
||||
for (let i = 1; i<a.length; i++) {
|
||||
if (typeof a[i] != t){
|
||||
throw new Error(`Types are not the same in the the key ${prefix}`);
|
||||
}
|
||||
}
|
||||
if (t == "object") {
|
||||
qualify_input_list(prefix,input,input1);
|
||||
} else {
|
||||
input1[prefix] = input;
|
||||
}
|
||||
} else {
|
||||
input1[prefix] = input;
|
||||
}
|
||||
} else if (typeof input == "object") {
|
||||
const keys = Object.keys(input);
|
||||
keys.forEach( (k) => {
|
||||
let new_prefix = prefix == ""? k : prefix + "." + k;
|
||||
qualify_input(new_prefix,input[k],input1);
|
||||
});
|
||||
} else {
|
||||
input1[prefix] = input;
|
||||
}
|
||||
}
|
||||
|
||||
function toArray32(rem,size) {
|
||||
const res = []; //new Uint32Array(size); //has no unshift
|
||||
const radix = BigInt(0x100000000);
|
||||
while (rem) {
|
||||
res.unshift( Number(rem % radix));
|
||||
rem = rem / radix;
|
||||
}
|
||||
if (size) {
|
||||
var i = size - res.length;
|
||||
while (i>0) {
|
||||
res.unshift(0);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function fromArray32(arr) { //returns a BigInt
|
||||
var res = BigInt(0);
|
||||
const radix = BigInt(0x100000000);
|
||||
for (let i = 0; i<arr.length; i++) {
|
||||
res = res*radix + BigInt(arr[i]);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function flatArray(a) {
|
||||
var res = [];
|
||||
fillArray(res, a);
|
||||
return res;
|
||||
|
||||
function fillArray(res, a) {
|
||||
if (Array.isArray(a)) {
|
||||
for (let i=0; i<a.length; i++) {
|
||||
fillArray(res, a[i]);
|
||||
}
|
||||
} else {
|
||||
res.push(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function normalize(n, prime) {
|
||||
let res = BigInt(n) % prime
|
||||
if (res < 0) res += prime
|
||||
return res
|
||||
}
|
||||
|
||||
function fnvHash(str) {
|
||||
const uint64_max = BigInt(2) ** BigInt(64);
|
||||
let hash = BigInt("0xCBF29CE484222325");
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
hash ^= BigInt(str[i].charCodeAt());
|
||||
hash *= BigInt(0x100000001B3);
|
||||
hash %= uint64_max;
|
||||
}
|
||||
let shash = hash.toString(16);
|
||||
let n = 16 - shash.length;
|
||||
shash = '0'.repeat(n).concat(shash);
|
||||
return shash;
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"commitment": {
|
||||
"file": "commitment",
|
||||
"template": "CommitmentHasher"
|
||||
"template": "CommitmentHasher",
|
||||
"pubs": ["value", "label"]
|
||||
},
|
||||
"merkleTree": {
|
||||
"file": "merkleTree",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"value": "1000000000000000000",
|
||||
"label": "6131061687831202463895476385369370444950798926695050324331554981674031506552",
|
||||
"nullifier": "5079170335209171",
|
||||
"secret": "2270088679772029"
|
||||
"value": "12",
|
||||
"label": "115792089237316195423570985008687907853269984665640564039457584007913129639935",
|
||||
"nullifier": "56",
|
||||
"secret": "78"
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
"prove:commitment": "npx circomkit prove commitment default",
|
||||
"verify:withdraw": "npx circomkit verify withdraw default",
|
||||
"verify:commitment": "npx circomkit verify commitment default",
|
||||
"gencontract:withdraw": "snarkjs zkey export solidityverifier build/withdraw/groth16_pkey.zkey WithdrawalVerifier.sol",
|
||||
"gencontract:commitment": "snarkjs zkey export solidityverifier build/commitment/groth16_pkey.zkey CommitmentVerifier.sol"
|
||||
"gencontract:withdraw": "npx snarkjs zkey export solidityverifier build/withdraw/groth16_pkey.zkey WithdrawalVerifier.sol",
|
||||
"gencontract:commitment": "npx snarkjs zkey export solidityverifier build/commitment/groth16_pkey.zkey CommitmentVerifier.sol"
|
||||
},
|
||||
"dependencies": {
|
||||
"@zk-kit/lean-imt": "^2.2.2",
|
||||
@@ -33,6 +33,7 @@
|
||||
"circomlib": "^2.0.5",
|
||||
"maci-circuits": "^2.5.0",
|
||||
"maci-crypto": "^2.5.0",
|
||||
"snarkjs": "^0.7.5",
|
||||
"viem": "^2.21.57"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"prepare": "husky",
|
||||
"test": "forge test -vvv",
|
||||
"test:fuzz": "medusa fuzz",
|
||||
"test:integration": "forge test --match-contract Integration -vvv",
|
||||
"test:integration": "forge test --match-contract Integration -vv --ffi",
|
||||
"test:symbolic": "halmos",
|
||||
"test:unit": "forge test --match-contract Unit -vvv",
|
||||
"test:unit:deep": "FOUNDRY_FUZZ_RUNS=5000 yarn test:unit"
|
||||
@@ -37,6 +37,8 @@
|
||||
"@openzeppelin/contracts": "^5.1.0",
|
||||
"@openzeppelin/contracts-upgradeable": "5.0.2",
|
||||
"@openzeppelin/foundry-upgrades": "^0.3.6",
|
||||
"@privacy-pool-core/sdk": "0.1.0",
|
||||
"@zk-kit/lean-imt": "^2.2.2",
|
||||
"@zk-kit/lean-imt.sol": "^2.0.0",
|
||||
"poseidon-solidity": "^0.0.5",
|
||||
"solc": "0.8.28"
|
||||
@@ -45,11 +47,14 @@
|
||||
"@commitlint/cli": "19.3.0",
|
||||
"@commitlint/config-conventional": "19.2.2",
|
||||
"@defi-wonderland/natspec-smells": "1.1.3",
|
||||
"@types/node": "^22.10.10",
|
||||
"forge-std": "github:foundry-rs/forge-std#1.9.2",
|
||||
"halmos-cheatcodes": "github:a16z/halmos-cheatcodes#c0d8655",
|
||||
"husky": ">=9",
|
||||
"lint-staged": ">=10",
|
||||
"solhint-community": "4.0.0",
|
||||
"sort-package-json": "2.10.0"
|
||||
"sort-package-json": "2.10.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ abstract contract PrivacyPool is State, IPrivacyPool {
|
||||
if (msg.sender != _w.processooor) revert InvalidProcesooor();
|
||||
|
||||
// Check the context matches the proof's public signal to ensure its integrity
|
||||
if (_p.context() != uint256(keccak256(abi.encode(_w, SCOPE)))) revert ContextMismatch();
|
||||
if (_p.context() != uint256(keccak256(abi.encode(_w, SCOPE))) % SNARK_SCALAR_FIELD) revert ContextMismatch();
|
||||
|
||||
// Check the state root is known
|
||||
if (!_isKnownRoot(_p.stateRoot())) revert UnknownStateRoot();
|
||||
@@ -69,7 +69,7 @@ abstract contract PrivacyPool is State, IPrivacyPool {
|
||||
// Store asset address
|
||||
ASSET = _asset;
|
||||
// Compute SCOPE
|
||||
SCOPE = uint256(keccak256(abi.encodePacked(address(this), block.chainid, _asset)));
|
||||
SCOPE = uint256(keccak256(abi.encodePacked(address(this), block.chainid, _asset))) % SNARK_SCALAR_FIELD;
|
||||
}
|
||||
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
@@ -86,7 +86,7 @@ abstract contract PrivacyPool is State, IPrivacyPool {
|
||||
if (dead) revert PoolIsDead();
|
||||
|
||||
// Compute label
|
||||
uint256 _label = uint256(keccak256(abi.encodePacked(SCOPE, ++nonce)));
|
||||
uint256 _label = uint256(keccak256(abi.encodePacked(SCOPE, ++nonce))) % SNARK_SCALAR_FIELD;
|
||||
// Store depositor and ragequit cooldown
|
||||
deposits[_label] = Deposit(_depositor, _value, block.timestamp + 1 weeks);
|
||||
|
||||
@@ -105,7 +105,7 @@ abstract contract PrivacyPool is State, IPrivacyPool {
|
||||
/// @inheritdoc IPrivacyPool
|
||||
function withdraw(Withdrawal memory _w, ProofLib.WithdrawProof memory _p) external validWithdrawal(_w, _p) {
|
||||
// Verify proof with Groth16 verifier
|
||||
if (!WITHDRAWAL_VERIFIER.verifyProof(_p)) revert InvalidProof();
|
||||
if (!WITHDRAWAL_VERIFIER.verifyProof(_p.pA, _p.pB, _p.pC, _p.pubSignals)) revert InvalidProof();
|
||||
|
||||
// Mark commitment nullifier as spent
|
||||
_spend(_p.existingNullifierHash());
|
||||
@@ -126,7 +126,7 @@ abstract contract PrivacyPool is State, IPrivacyPool {
|
||||
if (deposits[_label].depositor != msg.sender) revert OnlyOriginalDepositor();
|
||||
|
||||
// Verify proof with Groth16 verifier
|
||||
if (!RAGEQUIT_VERIFIER.verifyProof(_p)) revert InvalidProof();
|
||||
if (!RAGEQUIT_VERIFIER.verifyProof(_p.pA, _p.pB, _p.pC, _p.pubSignals)) revert InvalidProof();
|
||||
|
||||
// Check commitment exists in state
|
||||
if (!_isInState(_p.commitmentHash())) revert InvalidCommitment();
|
||||
|
||||
@@ -30,6 +30,9 @@ import {IVerifier} from 'interfaces/IVerifier.sol';
|
||||
abstract contract State is IState {
|
||||
using InternalLeanIMT for LeanIMTData;
|
||||
|
||||
uint256 internal constant SNARK_SCALAR_FIELD =
|
||||
21_888_242_871_839_275_222_246_405_745_257_275_088_548_364_400_416_034_343_698_204_186_575_808_495_617;
|
||||
|
||||
/// @inheritdoc IState
|
||||
string public constant VERSION = '0.1.0';
|
||||
/// @inheritdoc IState
|
||||
@@ -106,7 +109,7 @@ abstract contract State is IState {
|
||||
currentRootIndex = newRootIndex;
|
||||
|
||||
// Store the new root at the new index
|
||||
roots[newRootIndex] = _updatedRoot;
|
||||
roots[newRootIndex] = _updatedRoot % SNARK_SCALAR_FIELD;
|
||||
|
||||
emit LeafInserted(_merkleTree.size, _leaf, _updatedRoot);
|
||||
}
|
||||
|
||||
@@ -17,14 +17,14 @@ library ProofLib {
|
||||
* @param pB Second elliptic curve point (π_B) of the Groth16 proof, encoded as 2x2 matrix of field elements
|
||||
* @param pC Third elliptic curve point (π_C) of the Groth16 proof, encoded as two field elements
|
||||
* @param pubSignals Array of public inputs and outputs:
|
||||
* - [0] withdrawnValue: Amount being withdrawn
|
||||
* - [1] stateRoot: Current state root of the privacy pool
|
||||
* - [2] stateTreeDepth: Current depth of the state tree
|
||||
* - [3] ASPRoot: Current root of the Association Set Provider tree
|
||||
* - [4] ASPTreeDepth: Current depth of the ASP tree
|
||||
* - [5] context: Context value for the withdrawal operation
|
||||
* - [6] existingNullifierHash: Hash of the nullifier being spent
|
||||
* - [7] newCommitmentHash: Hash of the new commitment being created
|
||||
* - [0] newCommitmentHash: Hash of the new commitment being created
|
||||
* - [1] existingNullifierHash: Hash of the nullifier being spent
|
||||
* - [2] withdrawnValue: Amount being withdrawn
|
||||
* - [3] stateRoot: Current state root of the privacy pool
|
||||
* - [4] stateTreeDepth: Current depth of the state tree
|
||||
* - [5] ASPRoot: Current root of the Association Set Provider tree
|
||||
* - [6] ASPTreeDepth: Current depth of the ASP tree
|
||||
* - [7] context: Context value for the withdrawal operation
|
||||
*/
|
||||
struct WithdrawProof {
|
||||
uint256[2] pA;
|
||||
@@ -39,74 +39,74 @@ library ProofLib {
|
||||
string public constant VERSION = '0.1.0';
|
||||
|
||||
/**
|
||||
* @notice Retrieves the withdrawn value from the proof's public signals
|
||||
* @notice Retrieves the new commitment hash from the proof's public signals
|
||||
* @param _p The proof containing the public signals
|
||||
* @return The amount being withdrawn from Privacy Pool
|
||||
* @return The hash of the new commitment being created
|
||||
*/
|
||||
function withdrawnValue(WithdrawProof memory _p) public pure returns (uint256) {
|
||||
function newCommitmentHash(WithdrawProof memory _p) public pure returns (uint256) {
|
||||
return _p.pubSignals[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Retrieves the state root from the proof's public signals
|
||||
* @param _p The proof containing the public signals
|
||||
* @return The root of the state tree at time of proof generation
|
||||
*/
|
||||
function stateRoot(WithdrawProof memory _p) public pure returns (uint256) {
|
||||
return _p.pubSignals[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Retrieves the state tree depth from the proof's public signals
|
||||
* @param _p The proof containing the public signals
|
||||
* @return The depth of the state tree at time of proof generation
|
||||
*/
|
||||
function stateTreeDepth(WithdrawProof memory _p) public pure returns (uint256) {
|
||||
return _p.pubSignals[2];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Retrieves the ASP root from the proof's public signals
|
||||
* @param _p The proof containing the public signals
|
||||
* @return The latest root of the ASP tree at time of proof generation
|
||||
*/
|
||||
function ASPRoot(WithdrawProof memory _p) public pure returns (uint256) {
|
||||
return _p.pubSignals[3];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Retrieves the ASP tree depth from the proof's public signals
|
||||
* @param _p The proof containing the public signals
|
||||
* @return The depth of the ASP tree at time of proof generation
|
||||
*/
|
||||
function ASPTreeDepth(WithdrawProof memory _p) public pure returns (uint256) {
|
||||
return _p.pubSignals[4];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Retrieves the context value from the proof's public signals
|
||||
* @param _p The proof containing the public signals
|
||||
* @return The context value binding the proof to specific withdrawal data
|
||||
*/
|
||||
function context(WithdrawProof memory _p) public pure returns (uint256) {
|
||||
return _p.pubSignals[5];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Retrieves the existing nullifier hash from the proof's public signals
|
||||
* @param _p The proof containing the public signals
|
||||
* @return The hash of the nullifier being spent in this withdrawal
|
||||
*/
|
||||
function existingNullifierHash(WithdrawProof memory _p) public pure returns (uint256) {
|
||||
return _p.pubSignals[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Retrieves the withdrawn value from the proof's public signals
|
||||
* @param _p The proof containing the public signals
|
||||
* @return The amount being withdrawn from Privacy Pool
|
||||
*/
|
||||
function withdrawnValue(WithdrawProof memory _p) public pure returns (uint256) {
|
||||
return _p.pubSignals[2];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Retrieves the state root from the proof's public signals
|
||||
* @param _p The proof containing the public signals
|
||||
* @return The root of the state tree at time of proof generation
|
||||
*/
|
||||
function stateRoot(WithdrawProof memory _p) public pure returns (uint256) {
|
||||
return _p.pubSignals[3];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Retrieves the state tree depth from the proof's public signals
|
||||
* @param _p The proof containing the public signals
|
||||
* @return The depth of the state tree at time of proof generation
|
||||
*/
|
||||
function stateTreeDepth(WithdrawProof memory _p) public pure returns (uint256) {
|
||||
return _p.pubSignals[4];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Retrieves the ASP root from the proof's public signals
|
||||
* @param _p The proof containing the public signals
|
||||
* @return The latest root of the ASP tree at time of proof generation
|
||||
*/
|
||||
function ASPRoot(WithdrawProof memory _p) public pure returns (uint256) {
|
||||
return _p.pubSignals[5];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Retrieves the ASP tree depth from the proof's public signals
|
||||
* @param _p The proof containing the public signals
|
||||
* @return The depth of the ASP tree at time of proof generation
|
||||
*/
|
||||
function ASPTreeDepth(WithdrawProof memory _p) public pure returns (uint256) {
|
||||
return _p.pubSignals[6];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Retrieves the new commitment hash from the proof's public signals
|
||||
* @notice Retrieves the context value from the proof's public signals
|
||||
* @param _p The proof containing the public signals
|
||||
* @return The hash of the new commitment being created
|
||||
* @return The context value binding the proof to specific withdrawal data
|
||||
*/
|
||||
function newCommitmentHash(WithdrawProof memory _p) public pure returns (uint256) {
|
||||
function context(WithdrawProof memory _p) public pure returns (uint256) {
|
||||
return _p.pubSignals[7];
|
||||
}
|
||||
|
||||
@@ -114,6 +114,19 @@ library ProofLib {
|
||||
RAGEQUIT PROOF
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
/**
|
||||
* @notice Struct containing Groth16 proof elements and public signals for ragequit verification
|
||||
* @dev The public signals array must match the order of public inputs/outputs in the circuit
|
||||
* @param pA First elliptic curve point (π_A) of the Groth16 proof, encoded as two field elements
|
||||
* @param pB Second elliptic curve point (π_B) of the Groth16 proof, encoded as 2x2 matrix of field elements
|
||||
* @param pC Third elliptic curve point (π_C) of the Groth16 proof, encoded as two field elements
|
||||
* @param pubSignals Array of public inputs and outputs:
|
||||
* - [0] commitmentHash: Hash of the commitment being ragequit
|
||||
* - [1] precommitmentHash: Precommitment hash of the commitment being ragequit
|
||||
* - [2] nullifierHash: Nullifier hash of commitment being ragequit
|
||||
* - [3] value: Value of the commitment being ragequit
|
||||
* - [4] label: Label of commitment
|
||||
*/
|
||||
struct RagequitProof {
|
||||
uint256[2] pA;
|
||||
uint256[2][2] pB;
|
||||
@@ -121,22 +134,47 @@ library ProofLib {
|
||||
uint256[5] pubSignals;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Retrieves the new commitment hash from the proof's public signals
|
||||
* @param _p The ragequit proof containing the public signals
|
||||
* @return The new commitment hash
|
||||
*/
|
||||
function commitmentHash(RagequitProof memory _p) public pure returns (uint256) {
|
||||
return _p.pubSignals[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Retrieves the precommitment hash from the proof's public signals
|
||||
* @param _p The ragequit proof containing the public signals
|
||||
* @return The precommitment hash
|
||||
*/
|
||||
function precommitmentHash(RagequitProof memory _p) public pure returns (uint256) {
|
||||
return _p.pubSignals[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Retrieves the nullifier hash from the proof's public signals
|
||||
* @param _p The ragequit proof containing the public signals
|
||||
* @return The nullifier hash
|
||||
*/
|
||||
function nullifierHash(RagequitProof memory _p) public pure returns (uint256) {
|
||||
return _p.pubSignals[2];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Retrieves the commitment value from the proof's public signals
|
||||
* @param _p The ragequit proof containing the public signals
|
||||
* @return The commitment value
|
||||
*/
|
||||
function value(RagequitProof memory _p) public pure returns (uint256) {
|
||||
return _p.pubSignals[3];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Retrieves the commitment label from the proof's public signals
|
||||
* @param _p The ragequit proof containing the public signals
|
||||
* @return The commitment label
|
||||
*/
|
||||
function label(RagequitProof memory _p) public pure returns (uint256) {
|
||||
return _p.pubSignals[4];
|
||||
}
|
||||
|
||||
@@ -50,13 +50,13 @@ contract CommitmentVerifier {
|
||||
uint256 constant gammay2 =
|
||||
8_495_653_923_123_431_417_604_973_247_489_272_438_418_190_587_263_600_148_770_280_649_306_958_101_930;
|
||||
uint256 constant deltax1 =
|
||||
20_790_266_994_283_256_015_435_651_266_253_645_677_428_985_253_425_511_110_214_637_352_472_378_123_561;
|
||||
1_566_345_194_044_855_115_881_573_792_161_398_148_180_261_128_047_745_808_006_007_461_179_430_151_610;
|
||||
uint256 constant deltax2 =
|
||||
19_747_890_532_008_015_484_269_642_238_616_813_247_812_942_897_181_983_539_295_128_285_006_791_805_051;
|
||||
898_097_132_297_908_470_451_554_122_776_098_576_315_968_266_972_858_647_533_165_182_875_866_952_791;
|
||||
uint256 constant deltay1 =
|
||||
11_252_680_082_161_512_221_391_292_082_484_449_922_382_852_831_667_769_847_614_527_585_920_433_460_812;
|
||||
15_822_370_534_108_199_031_188_891_317_296_695_687_790_226_659_418_312_260_844_092_412_205_261_640_832;
|
||||
uint256 constant deltay2 =
|
||||
12_479_946_964_535_948_300_356_775_236_239_256_577_354_489_178_807_993_113_510_515_197_894_981_622_940;
|
||||
15_906_465_618_553_129_247_467_149_629_216_463_797_378_789_817_015_585_134_048_618_945_243_648_523_528;
|
||||
|
||||
uint256 constant IC0x =
|
||||
1_572_230_892_394_329_298_681_454_529_771_558_079_791_160_063_426_885_123_778_364_988_544_600_092_204;
|
||||
|
||||
@@ -50,13 +50,13 @@ contract WithdrawalVerifier {
|
||||
uint256 constant gammay2 =
|
||||
8_495_653_923_123_431_417_604_973_247_489_272_438_418_190_587_263_600_148_770_280_649_306_958_101_930;
|
||||
uint256 constant deltax1 =
|
||||
5_101_597_244_350_902_433_884_322_636_911_728_027_755_108_462_744_357_774_920_088_302_991_139_465_590;
|
||||
21_310_541_422_874_827_293_880_171_982_528_429_236_667_249_433_013_822_057_255_176_270_531_431_868_642;
|
||||
uint256 constant deltax2 =
|
||||
19_693_919_659_217_571_144_377_343_643_307_001_469_796_988_322_805_048_476_785_491_401_112_761_759_739;
|
||||
1_486_986_654_560_761_301_713_455_953_272_498_657_121_229_884_579_052_555_795_983_089_481_728_689_679;
|
||||
uint256 constant deltay1 =
|
||||
12_278_129_925_984_918_843_866_376_487_446_587_556_019_495_152_278_714_741_203_951_201_143_692_150_656;
|
||||
7_701_627_035_368_501_351_340_317_635_290_605_147_001_193_359_451_711_067_122_274_170_209_344_601_850;
|
||||
uint256 constant deltay2 =
|
||||
7_381_938_079_202_356_459_215_769_894_481_363_111_554_881_792_164_521_288_691_121_794_566_942_286_579;
|
||||
21_641_295_423_770_389_167_224_011_883_026_537_472_504_249_086_048_994_666_432_736_497_471_481_714_819;
|
||||
|
||||
uint256 constant IC0x =
|
||||
16_148_105_666_203_862_965_387_243_430_225_407_356_287_196_650_373_131_595_365_027_485_816_037_911_900;
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.28;
|
||||
|
||||
import {ProofLib} from '../contracts/lib/ProofLib.sol';
|
||||
|
||||
interface IVerifier {
|
||||
function verifyProof(ProofLib.WithdrawProof memory _proof) external returns (bool);
|
||||
function verifyProof(
|
||||
uint256[2] memory pA,
|
||||
uint256[2][2] memory pB,
|
||||
uint256[2] memory pC,
|
||||
uint256[8] memory pubSignals
|
||||
) external returns (bool);
|
||||
|
||||
function verifyProof(ProofLib.RagequitProof memory _proof) external returns (bool);
|
||||
function verifyProof(
|
||||
uint256[2] memory pA,
|
||||
uint256[2][2] memory pB,
|
||||
uint256[2] memory pC,
|
||||
uint256[5] memory pubSignals
|
||||
) external returns (bool);
|
||||
}
|
||||
|
||||
24
packages/contracts/test/helper/MerkleProofGenerator.mjs
Normal file
24
packages/contracts/test/helper/MerkleProofGenerator.mjs
Normal file
@@ -0,0 +1,24 @@
|
||||
import { ethers } from "ethers";
|
||||
import { generateMerkleProof } from "@privacy-pool-core/sdk";
|
||||
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const leaf = BigInt(args[0]);
|
||||
const leaves = args.slice(1).map(BigInt);
|
||||
|
||||
const proof = generateMerkleProof(leaves, leaf);
|
||||
|
||||
proof.index = Object.is(proof.index, NaN) ? 0 : proof.index;
|
||||
|
||||
// Convert proof to ABI-encoded bytes
|
||||
const abiCoder = new ethers.AbiCoder();
|
||||
const encodedProof = abiCoder.encode(
|
||||
["uint256", "uint256", "uint256[]"],
|
||||
[proof.root, proof.index, proof.siblings],
|
||||
);
|
||||
|
||||
// Write to stdout as hex string
|
||||
process.stdout.write(encodedProof);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
65
packages/contracts/test/helper/RagequitProofGenerator.mjs
Normal file
65
packages/contracts/test/helper/RagequitProofGenerator.mjs
Normal file
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { PrivacyPoolSDK, Circuits } from "@privacy-pool-core/sdk";
|
||||
import { encodeAbiParameters } from "viem";
|
||||
|
||||
async function main() {
|
||||
// Get command line arguments
|
||||
const [value, label, nullifier, secret] = process.argv.slice(2).map(BigInt);
|
||||
|
||||
// Initialize SDK with circuits
|
||||
const circuits = new Circuits();
|
||||
const privacyPoolSDK = new PrivacyPoolSDK(circuits);
|
||||
|
||||
try {
|
||||
// Generate the commitment proof
|
||||
const { proof, publicSignals } = await privacyPoolSDK.proveCommitment(
|
||||
value,
|
||||
label,
|
||||
nullifier,
|
||||
secret,
|
||||
);
|
||||
|
||||
// Format the proof to match the Solidity struct
|
||||
const ragequitProof = {
|
||||
_pA: [BigInt(proof.pi_a[0]), BigInt(proof.pi_a[1])],
|
||||
_pB: [
|
||||
[BigInt(proof.pi_b[0][1]), BigInt(proof.pi_b[0][0])],
|
||||
[BigInt(proof.pi_b[1][1]), BigInt(proof.pi_b[1][0])],
|
||||
],
|
||||
_pC: [BigInt(proof.pi_c[0]), BigInt(proof.pi_c[1])],
|
||||
_pubSignals: [
|
||||
publicSignals[0], // commitment hash
|
||||
publicSignals[1], // precommitment hash
|
||||
publicSignals[2], // nullifier hash
|
||||
publicSignals[3], // value
|
||||
publicSignals[4], // label
|
||||
].map((x) => BigInt(x)),
|
||||
};
|
||||
|
||||
// ABI encode the proof
|
||||
const encodedProof = encodeAbiParameters(
|
||||
[
|
||||
{
|
||||
type: "tuple",
|
||||
components: [
|
||||
{ name: "_pA", type: "uint256[2]" },
|
||||
{ name: "_pB", type: "uint256[2][2]" },
|
||||
{ name: "_pC", type: "uint256[2]" },
|
||||
{ name: "_pubSignals", type: "uint256[5]" },
|
||||
],
|
||||
},
|
||||
],
|
||||
[ragequitProof],
|
||||
);
|
||||
|
||||
// Output the encoded proof directly to stdout
|
||||
process.stdout.write(encodedProof);
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error("Error generating proof:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
122
packages/contracts/test/helper/WithdrawalProofGenerator.mjs
Normal file
122
packages/contracts/test/helper/WithdrawalProofGenerator.mjs
Normal file
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { ethers } from "ethers";
|
||||
import {
|
||||
PrivacyPoolSDK,
|
||||
Circuits,
|
||||
getCommitment,
|
||||
} from "@privacy-pool-core/sdk";
|
||||
import { encodeAbiParameters } from "viem";
|
||||
|
||||
function padSiblings(siblings, treeDepth) {
|
||||
const paddedSiblings = [...siblings];
|
||||
while (paddedSiblings.length < treeDepth) {
|
||||
paddedSiblings.push(0n);
|
||||
}
|
||||
return paddedSiblings;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const [
|
||||
existingValue,
|
||||
label,
|
||||
existingNullifier,
|
||||
existingSecret,
|
||||
newNullifier,
|
||||
newSecret,
|
||||
withdrawnValue,
|
||||
context,
|
||||
stateMerkleProofHex,
|
||||
stateTreeDepth,
|
||||
aspMerkleProofHex,
|
||||
aspTreeDepth,
|
||||
] = process.argv.slice(2);
|
||||
|
||||
const circuits = new Circuits();
|
||||
const sdk = new PrivacyPoolSDK(circuits);
|
||||
|
||||
// Decode the Merkle proofs
|
||||
const abiCoder = new ethers.AbiCoder();
|
||||
const stateMerkleProof = abiCoder.decode(
|
||||
["uint256", "uint256", "uint256[]"],
|
||||
stateMerkleProofHex,
|
||||
);
|
||||
const aspMerkleProof = abiCoder.decode(
|
||||
["uint256", "uint256", "uint256[]"],
|
||||
aspMerkleProofHex,
|
||||
);
|
||||
|
||||
const commitment = getCommitment(
|
||||
existingValue,
|
||||
label,
|
||||
existingNullifier,
|
||||
existingSecret,
|
||||
);
|
||||
|
||||
// Pad siblings arrays to required length
|
||||
const paddedStateSiblings = padSiblings(stateMerkleProof[2], 32);
|
||||
const paddedAspSiblings = padSiblings(aspMerkleProof[2], 32);
|
||||
|
||||
const { proof, publicSignals } = await sdk.proveWithdrawal(commitment, {
|
||||
context,
|
||||
withdrawalAmount: withdrawnValue,
|
||||
stateMerkleProof: {
|
||||
root: stateMerkleProof[0],
|
||||
leaf: commitment.hash,
|
||||
index: stateMerkleProof[1],
|
||||
siblings: paddedStateSiblings,
|
||||
},
|
||||
aspMerkleProof: {
|
||||
root: aspMerkleProof[0],
|
||||
leaf: commitment.hash,
|
||||
index: aspMerkleProof[1],
|
||||
siblings: paddedAspSiblings,
|
||||
},
|
||||
stateRoot: stateMerkleProof[0],
|
||||
stateTreeDepth: parseInt(stateTreeDepth),
|
||||
aspRoot: aspMerkleProof[0],
|
||||
aspTreeDepth: parseInt(aspTreeDepth),
|
||||
newSecret,
|
||||
newNullifier,
|
||||
});
|
||||
|
||||
const withdrawalProof = {
|
||||
_pA: [BigInt(proof.pi_a[0]), BigInt(proof.pi_a[1])],
|
||||
_pB: [
|
||||
[BigInt(proof.pi_b[0][1]), BigInt(proof.pi_b[0][0])],
|
||||
[BigInt(proof.pi_b[1][1]), BigInt(proof.pi_b[1][0])],
|
||||
],
|
||||
_pC: [BigInt(proof.pi_c[0]), BigInt(proof.pi_c[1])],
|
||||
_pubSignals: [
|
||||
publicSignals[0], // new commitment hash
|
||||
publicSignals[1], // existing nullifier hash
|
||||
publicSignals[2], // withdrawn value
|
||||
publicSignals[3], // state root
|
||||
publicSignals[4], // state depth
|
||||
publicSignals[5], // asp root
|
||||
publicSignals[6], // asp depth
|
||||
publicSignals[7], // context
|
||||
].map((x) => BigInt(x)),
|
||||
};
|
||||
|
||||
const encodedProof = encodeAbiParameters(
|
||||
[
|
||||
{
|
||||
type: "tuple",
|
||||
components: [
|
||||
{ name: "_pA", type: "uint256[2]" },
|
||||
{ name: "_pB", type: "uint256[2][2]" },
|
||||
{ name: "_pC", type: "uint256[2]" },
|
||||
{ name: "_pubSignals", type: "uint256[8]" },
|
||||
],
|
||||
},
|
||||
],
|
||||
[withdrawalProof],
|
||||
);
|
||||
|
||||
// Write to stdout as hex string
|
||||
process.stdout.write(encodedProof);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -1,113 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.28;
|
||||
|
||||
import {IntegrationBase} from '../IntegrationBase.sol';
|
||||
import {IEntrypoint} from 'contracts/Entrypoint.sol';
|
||||
import {IPrivacyPool} from 'contracts/PrivacyPool.sol';
|
||||
|
||||
import {ProofLib} from 'contracts/lib/ProofLib.sol';
|
||||
import {IVerifier} from 'interfaces/IVerifier.sol';
|
||||
|
||||
import {IERC20} from '@oz/interfaces/IERC20.sol';
|
||||
|
||||
contract IntegrationERC20DepositFullDirectWithdrawal is IntegrationBase {
|
||||
function test_ERC20DepositFullDirectWithdrawal() public {
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
DEPOSIT
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
// Generate deposit params
|
||||
DepositParams memory _params = _generateDefaultDepositParams(100 ether, _VETTING_FEE_BPS, _daiPool);
|
||||
|
||||
// Deal DAI to Alice
|
||||
deal(address(_DAI), _ALICE, _params.amount);
|
||||
|
||||
// Approve entrypoint
|
||||
vm.startPrank(_ALICE);
|
||||
_DAI.approve(address(_entrypoint), _params.amount);
|
||||
|
||||
// Expect deposit event from privacy pool
|
||||
vm.expectEmit(address(_daiPool));
|
||||
emit IPrivacyPool.Deposited(_ALICE, _params.commitment, _params.label, _params.amountAfterFee, _params.commitment);
|
||||
|
||||
// Expect deposit event from entrypoint
|
||||
vm.expectEmit(address(_entrypoint));
|
||||
emit IEntrypoint.Deposited(_ALICE, _daiPool, _params.commitment, _params.amountAfterFee);
|
||||
|
||||
// Assert balances
|
||||
uint256 _aliceInitialBalance = _DAI.balanceOf(_ALICE);
|
||||
uint256 _entrypointInitialBalance = _DAI.balanceOf(address(_entrypoint));
|
||||
uint256 _daiPoolInitialBalance = _DAI.balanceOf(address(_daiPool));
|
||||
|
||||
// Add the commitment to the shadow merkle tree
|
||||
_insertIntoShadowMerkleTree(_params.commitment);
|
||||
|
||||
// Deposit DAI
|
||||
_entrypoint.deposit(_DAI, _params.amount, _params.precommitment);
|
||||
vm.stopPrank();
|
||||
|
||||
// Assert balances
|
||||
assertEq(_DAI.balanceOf(_ALICE), _aliceInitialBalance - _params.amount, 'Alice balance mismatch');
|
||||
assertEq(
|
||||
_DAI.balanceOf(address(_entrypoint)), _entrypointInitialBalance + _params.fee, 'Entrypoint balance mismatch'
|
||||
);
|
||||
assertEq(
|
||||
_DAI.balanceOf(address(_daiPool)), _daiPoolInitialBalance + _params.amountAfterFee, 'EthPool balance mismatch'
|
||||
);
|
||||
|
||||
// Assert deposit info
|
||||
(address _depositor, uint256 _value, uint256 _cooldownExpiry) = _daiPool.deposits(_params.label);
|
||||
assertEq(_depositor, _ALICE, 'Incorrect depositor');
|
||||
assertEq(_value, _params.amountAfterFee, 'Incorrect deposit value');
|
||||
assertEq(_cooldownExpiry, block.timestamp + 1 weeks, 'Incorrect deposit cooldown expiry');
|
||||
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
WITHDRAW
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
// Insert leaf into shadow asp merkle tree
|
||||
_insertIntoShadowASPMerkleTree(_DEFAULT_ASP_ROOT);
|
||||
|
||||
// Data is left empty given that the withdrawal is direct
|
||||
(IPrivacyPool.Withdrawal memory _withdrawal, ProofLib.WithdrawProof memory _proof) = _generateWithdrawalParams(
|
||||
WithdrawalParams({
|
||||
processor: _ALICE,
|
||||
recipient: address(0),
|
||||
feeRecipient: address(0),
|
||||
feeBps: 0,
|
||||
scope: _params.scope,
|
||||
withdrawnValue: _params.amountAfterFee,
|
||||
nullifier: _params.nullifier
|
||||
})
|
||||
);
|
||||
|
||||
// Push ASP root
|
||||
vm.prank(_POSTMAN);
|
||||
// pubSignals[3] is the ASPRoot
|
||||
_entrypoint.updateRoot(_proof.pubSignals[3], bytes32('IPFS_HASH'));
|
||||
|
||||
// TODO: remove once we have a verifier
|
||||
vm.mockCall(
|
||||
address(_WITHDRAWAL_VERIFIER),
|
||||
abi.encodeWithSignature('verifyProof((uint256[2],uint256[2][2],uint256[2],uint256[8]))', _proof),
|
||||
abi.encode(true)
|
||||
);
|
||||
|
||||
// Expect withdrawal event from privacy pool
|
||||
vm.expectEmit(address(_daiPool));
|
||||
// pubSignals[6] is the existingNullifierHash
|
||||
// pubSignals[7] is the newCommitmentHash
|
||||
emit IPrivacyPool.Withdrawn(_ALICE, _params.amountAfterFee, _proof.pubSignals[6], _proof.pubSignals[7]);
|
||||
|
||||
// Withdraw DAI
|
||||
vm.prank(_ALICE);
|
||||
_daiPool.withdraw(_withdrawal, _proof);
|
||||
|
||||
// Assert balances
|
||||
assertEq(_DAI.balanceOf(_ALICE), _aliceInitialBalance - _params.fee, 'Alice balance mismatch');
|
||||
assertEq(
|
||||
_DAI.balanceOf(address(_entrypoint)), _entrypointInitialBalance + _params.fee, 'Entrypoint balance mismatch'
|
||||
);
|
||||
assertEq(_DAI.balanceOf(address(_daiPool)), _daiPoolInitialBalance, 'EthPool balance mismatch');
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.28;
|
||||
|
||||
import {IntegrationBase} from '../IntegrationBase.sol';
|
||||
import {IEntrypoint} from 'contracts/Entrypoint.sol';
|
||||
import {IPrivacyPool} from 'contracts/PrivacyPool.sol';
|
||||
|
||||
import {ProofLib} from 'contracts/lib/ProofLib.sol';
|
||||
import {IVerifier} from 'interfaces/IVerifier.sol';
|
||||
|
||||
import {IERC20} from '@oz/interfaces/IERC20.sol';
|
||||
|
||||
contract IntegrationERC20DepositFullRelayedWithdrawal is IntegrationBase {
|
||||
function test_ERC20DepositFullRelayedWithdrawal() public {
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
DEPOSIT
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
// Generate deposit params
|
||||
DepositParams memory _params = _generateDefaultDepositParams(100 ether, _VETTING_FEE_BPS, _daiPool);
|
||||
|
||||
// Deal DAI to Alice
|
||||
deal(address(_DAI), _ALICE, _params.amount);
|
||||
|
||||
// Approve entrypoint
|
||||
vm.startPrank(_ALICE);
|
||||
_DAI.approve(address(_entrypoint), _params.amount);
|
||||
|
||||
// Expect deposit event from privacy pool
|
||||
vm.expectEmit(address(_daiPool));
|
||||
emit IPrivacyPool.Deposited(_ALICE, _params.commitment, _params.label, _params.amountAfterFee, _params.commitment);
|
||||
|
||||
// Expect deposit event from entrypoint
|
||||
vm.expectEmit(address(_entrypoint));
|
||||
emit IEntrypoint.Deposited(_ALICE, _daiPool, _params.commitment, _params.amountAfterFee);
|
||||
|
||||
// Assert balances
|
||||
uint256 _aliceInitialBalance = _DAI.balanceOf(_ALICE);
|
||||
uint256 _entrypointInitialBalance = _DAI.balanceOf(address(_entrypoint));
|
||||
uint256 _daiPoolInitialBalance = _DAI.balanceOf(address(_daiPool));
|
||||
uint256 _relayerInitialBalance = _DAI.balanceOf(_RELAYER);
|
||||
|
||||
// Add the commitment to the shadow merkle tree
|
||||
_insertIntoShadowMerkleTree(_params.commitment);
|
||||
|
||||
// Deposit DAI
|
||||
_entrypoint.deposit(_DAI, _params.amount, _params.precommitment);
|
||||
vm.stopPrank();
|
||||
|
||||
// Assert balances
|
||||
assertEq(_DAI.balanceOf(_ALICE), _aliceInitialBalance - _params.amount, 'Alice balance mismatch');
|
||||
assertEq(
|
||||
_DAI.balanceOf(address(_entrypoint)), _entrypointInitialBalance + _params.fee, 'Entrypoint balance mismatch'
|
||||
);
|
||||
assertEq(
|
||||
_DAI.balanceOf(address(_daiPool)), _daiPoolInitialBalance + _params.amountAfterFee, 'EthPool balance mismatch'
|
||||
);
|
||||
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
WITHDRAW
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
// Insert leaf into shadow asp merkle tree
|
||||
_insertIntoShadowASPMerkleTree(_DEFAULT_ASP_ROOT);
|
||||
|
||||
// Generate withdrawal params
|
||||
(IPrivacyPool.Withdrawal memory _withdrawal, ProofLib.WithdrawProof memory _proof) = _generateWithdrawalParams(
|
||||
WithdrawalParams({
|
||||
processor: address(_entrypoint),
|
||||
recipient: _ALICE,
|
||||
feeRecipient: _RELAYER,
|
||||
feeBps: _RELAY_FEE_BPS,
|
||||
scope: _params.scope,
|
||||
withdrawnValue: _params.amountAfterFee,
|
||||
nullifier: _params.nullifier
|
||||
})
|
||||
);
|
||||
|
||||
uint256 _receivedAmount = _deductFee(_params.amountAfterFee, _RELAY_FEE_BPS);
|
||||
|
||||
// Push ASP root
|
||||
vm.prank(_POSTMAN);
|
||||
// pubSignals[3] is the ASPRoot
|
||||
_entrypoint.updateRoot(_proof.pubSignals[3], bytes32('IPFS_HASH'));
|
||||
|
||||
// TODO: remove once we have a verifier
|
||||
vm.mockCall(
|
||||
address(_WITHDRAWAL_VERIFIER),
|
||||
abi.encodeWithSignature('verifyProof((uint256[2],uint256[2][2],uint256[2],uint256[8]))', _proof),
|
||||
abi.encode(true)
|
||||
);
|
||||
|
||||
// Expect withdrawal event from privacy pool
|
||||
vm.expectEmit(address(_daiPool));
|
||||
// pubSignals[6] is the existingNullifierHash
|
||||
// pubSignals[7] is the newCommitmentHash
|
||||
emit IPrivacyPool.Withdrawn(
|
||||
address(_entrypoint), _params.amountAfterFee, _proof.pubSignals[6], _proof.pubSignals[7]
|
||||
);
|
||||
|
||||
// Expect withdrawal event from entrypoint
|
||||
vm.expectEmit(address(_entrypoint));
|
||||
emit IEntrypoint.WithdrawalRelayed(
|
||||
_RELAYER, _ALICE, _DAI, _params.amountAfterFee, _params.amountAfterFee - _receivedAmount
|
||||
);
|
||||
|
||||
// Withdraw DAI
|
||||
vm.prank(_RELAYER);
|
||||
_entrypoint.relay(_withdrawal, _proof);
|
||||
|
||||
// Assert balances
|
||||
assertEq(_DAI.balanceOf(_ALICE), _aliceInitialBalance - _params.amount + _receivedAmount, 'Alice balance mismatch');
|
||||
assertEq(
|
||||
_DAI.balanceOf(address(_entrypoint)), _entrypointInitialBalance + _params.fee, 'Entrypoint balance mismatch'
|
||||
);
|
||||
assertEq(_DAI.balanceOf(address(_daiPool)), _daiPoolInitialBalance, 'EthPool balance mismatch');
|
||||
assertEq(
|
||||
_DAI.balanceOf(_RELAYER),
|
||||
_relayerInitialBalance + _params.amountAfterFee - _receivedAmount,
|
||||
'Relayer balance mismatch'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.28;
|
||||
|
||||
import {IntegrationBase} from '../IntegrationBase.sol';
|
||||
import {IEntrypoint} from 'contracts/Entrypoint.sol';
|
||||
import {IPrivacyPool} from 'contracts/PrivacyPool.sol';
|
||||
|
||||
import {ProofLib} from 'contracts/lib/ProofLib.sol';
|
||||
import {IVerifier} from 'interfaces/IVerifier.sol';
|
||||
|
||||
import {IERC20} from '@oz/interfaces/IERC20.sol';
|
||||
|
||||
contract IntegrationERC20DepositPartialDirectWithdrawal is IntegrationBase {
|
||||
function test_ERC20DepositPartialDirectWithdrawal() public {
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
DEPOSIT
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
// Generate deposit params
|
||||
DepositParams memory _params = _generateDefaultDepositParams(100 ether, _VETTING_FEE_BPS, _daiPool);
|
||||
deal(address(_DAI), _ALICE, _params.amount);
|
||||
vm.startPrank(_ALICE);
|
||||
_DAI.approve(address(_entrypoint), _params.amount);
|
||||
|
||||
// Expect deposit event from privacy pool
|
||||
vm.expectEmit(address(_daiPool));
|
||||
emit IPrivacyPool.Deposited(_ALICE, _params.commitment, _params.label, _params.amountAfterFee, _params.commitment);
|
||||
|
||||
// Expect deposit event from entrypoint
|
||||
vm.expectEmit(address(_entrypoint));
|
||||
emit IEntrypoint.Deposited(_ALICE, _daiPool, _params.commitment, _params.amountAfterFee);
|
||||
|
||||
// Assert balances
|
||||
uint256 _aliceInitialBalance = _DAI.balanceOf(_ALICE);
|
||||
uint256 _entrypointInitialBalance = _DAI.balanceOf(address(_entrypoint));
|
||||
uint256 _daiPoolInitialBalance = _DAI.balanceOf(address(_daiPool));
|
||||
|
||||
// Add the commitment to the shadow merkle tree
|
||||
_insertIntoShadowMerkleTree(_params.commitment);
|
||||
|
||||
// Deposit DAI
|
||||
_entrypoint.deposit(_DAI, _params.amount, _params.precommitment);
|
||||
vm.stopPrank();
|
||||
|
||||
// Assert balances
|
||||
assertEq(_DAI.balanceOf(_ALICE), _aliceInitialBalance - _params.amount, 'Alice balance mismatch');
|
||||
assertEq(
|
||||
_DAI.balanceOf(address(_entrypoint)), _entrypointInitialBalance + _params.fee, 'Entrypoint balance mismatch'
|
||||
);
|
||||
assertEq(
|
||||
_DAI.balanceOf(address(_daiPool)), _daiPoolInitialBalance + _params.amountAfterFee, 'EthPool balance mismatch'
|
||||
);
|
||||
|
||||
// Assert deposit info
|
||||
(address _depositor, uint256 _value, uint256 _cooldownExpiry) = _daiPool.deposits(_params.label);
|
||||
assertEq(_depositor, _ALICE, 'Incorrect depositor');
|
||||
assertEq(_value, _params.amountAfterFee, 'Incorrect deposit value');
|
||||
assertEq(_cooldownExpiry, block.timestamp + 1 weeks, 'Incorrect deposit cooldown expiry');
|
||||
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
WITHDRAW
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
uint256 _withdrawnValue = _params.amountAfterFee / 2;
|
||||
|
||||
// Insert leaf into shadow asp merkle tree
|
||||
_insertIntoShadowASPMerkleTree(_DEFAULT_ASP_ROOT);
|
||||
|
||||
// Data is left empty given that the withdrawal is direct
|
||||
(IPrivacyPool.Withdrawal memory _withdrawal, ProofLib.WithdrawProof memory _proof) = _generateWithdrawalParams(
|
||||
WithdrawalParams({
|
||||
processor: _ALICE,
|
||||
recipient: address(0),
|
||||
feeRecipient: address(0),
|
||||
feeBps: 0,
|
||||
scope: _params.scope,
|
||||
withdrawnValue: _withdrawnValue,
|
||||
nullifier: _params.nullifier
|
||||
})
|
||||
);
|
||||
|
||||
// Push ASP root
|
||||
vm.prank(_POSTMAN);
|
||||
// pubSignals[3] is the ASPRoot
|
||||
_entrypoint.updateRoot(_proof.pubSignals[3], bytes32('IPFS_HASH'));
|
||||
|
||||
// TODO: remove once we have a verifier
|
||||
vm.mockCall(
|
||||
address(_WITHDRAWAL_VERIFIER),
|
||||
abi.encodeWithSignature('verifyProof((uint256[2],uint256[2][2],uint256[2],uint256[8]))', _proof),
|
||||
abi.encode(true)
|
||||
);
|
||||
|
||||
vm.expectEmit(address(_daiPool));
|
||||
// pubSignals[6] is the existingNullifierHash
|
||||
// pubSignals[7] is the newCommitmentHash
|
||||
emit IPrivacyPool.Withdrawn(_ALICE, _withdrawnValue, _proof.pubSignals[6], _proof.pubSignals[7]);
|
||||
|
||||
// Withdraw DAI
|
||||
vm.prank(_ALICE);
|
||||
_daiPool.withdraw(_withdrawal, _proof);
|
||||
|
||||
// Assert balances
|
||||
assertEq(_DAI.balanceOf(_ALICE), _aliceInitialBalance - _params.amount + _withdrawnValue, 'Alice balance mismatch');
|
||||
assertEq(
|
||||
_DAI.balanceOf(address(_entrypoint)), _entrypointInitialBalance + _params.fee, 'Entrypoint balance mismatch'
|
||||
);
|
||||
assertEq(
|
||||
_DAI.balanceOf(address(_daiPool)),
|
||||
_daiPoolInitialBalance + _params.amountAfterFee - _withdrawnValue,
|
||||
'EthPool balance mismatch'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.28;
|
||||
|
||||
import {IntegrationBase} from '../IntegrationBase.sol';
|
||||
import {IEntrypoint} from 'contracts/Entrypoint.sol';
|
||||
import {IPrivacyPool} from 'contracts/PrivacyPool.sol';
|
||||
|
||||
import {ProofLib} from 'contracts/lib/ProofLib.sol';
|
||||
import {IVerifier} from 'interfaces/IVerifier.sol';
|
||||
|
||||
import {IERC20} from '@oz/interfaces/IERC20.sol';
|
||||
|
||||
contract IntegrationERC20DepositPartialRelayedWithdrawal is IntegrationBase {
|
||||
function test_ERC20DepositPartialRelayedWithdrawal() public {
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
DEPOSIT
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
// Generate deposit params
|
||||
DepositParams memory _params = _generateDefaultDepositParams(100 ether, _VETTING_FEE_BPS, _daiPool);
|
||||
deal(address(_DAI), _ALICE, _params.amount);
|
||||
|
||||
// Approve entrypoint
|
||||
vm.startPrank(_ALICE);
|
||||
_DAI.approve(address(_entrypoint), _params.amount);
|
||||
|
||||
// Expect deposit event from privacy pool
|
||||
vm.expectEmit(address(_daiPool));
|
||||
emit IPrivacyPool.Deposited(_ALICE, _params.commitment, _params.label, _params.amountAfterFee, _params.commitment);
|
||||
|
||||
// Expect deposit event from entrypoint
|
||||
vm.expectEmit(address(_entrypoint));
|
||||
emit IEntrypoint.Deposited(_ALICE, _daiPool, _params.commitment, _params.amountAfterFee);
|
||||
|
||||
// Assert balances
|
||||
uint256 _aliceInitialBalance = _DAI.balanceOf(_ALICE);
|
||||
uint256 _entrypointInitialBalance = _DAI.balanceOf(address(_entrypoint));
|
||||
uint256 _daiPoolInitialBalance = _DAI.balanceOf(address(_daiPool));
|
||||
uint256 _relayerInitialBalance = _DAI.balanceOf(_RELAYER);
|
||||
|
||||
// Add the commitment to the shadow merkle tree
|
||||
_insertIntoShadowMerkleTree(_params.commitment);
|
||||
|
||||
// Deposit DAI
|
||||
_entrypoint.deposit(_DAI, _params.amount, _params.precommitment);
|
||||
vm.stopPrank();
|
||||
|
||||
// Assert balances
|
||||
assertEq(_DAI.balanceOf(_ALICE), _aliceInitialBalance - _params.amount, 'Alice balance mismatch');
|
||||
assertEq(
|
||||
_DAI.balanceOf(address(_entrypoint)), _entrypointInitialBalance + _params.fee, 'Entrypoint balance mismatch'
|
||||
);
|
||||
assertEq(
|
||||
_DAI.balanceOf(address(_daiPool)), _daiPoolInitialBalance + _params.amountAfterFee, 'EthPool balance mismatch'
|
||||
);
|
||||
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
WITHDRAW
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
// Withdraw half of the deposit
|
||||
uint256 _withdrawnValue = _params.amountAfterFee / 2;
|
||||
|
||||
// Insert leaf into shadow asp merkle tree
|
||||
_insertIntoShadowASPMerkleTree(_DEFAULT_ASP_ROOT);
|
||||
|
||||
// Generate withdrawal params
|
||||
(IPrivacyPool.Withdrawal memory _withdrawal, ProofLib.WithdrawProof memory _proof) = _generateWithdrawalParams(
|
||||
WithdrawalParams({
|
||||
processor: address(_entrypoint),
|
||||
recipient: _ALICE,
|
||||
feeRecipient: _RELAYER,
|
||||
feeBps: _RELAY_FEE_BPS,
|
||||
scope: _params.scope,
|
||||
withdrawnValue: _withdrawnValue,
|
||||
nullifier: _params.nullifier
|
||||
})
|
||||
);
|
||||
|
||||
// Deduct relay fee
|
||||
uint256 _receivedAmount = _deductFee(_withdrawnValue, _RELAY_FEE_BPS);
|
||||
|
||||
// Push ASP root
|
||||
vm.prank(_POSTMAN);
|
||||
// pubSignals[3] is the ASPRoot
|
||||
_entrypoint.updateRoot(_proof.pubSignals[3], bytes32('IPFS_HASH'));
|
||||
|
||||
// TODO: remove once we have a verifier
|
||||
vm.mockCall(
|
||||
address(_WITHDRAWAL_VERIFIER),
|
||||
abi.encodeWithSignature('verifyProof((uint256[2],uint256[2][2],uint256[2],uint256[8]))', _proof),
|
||||
abi.encode(true)
|
||||
);
|
||||
|
||||
// Expect withdrawal event from privacy pool
|
||||
vm.expectEmit(address(_daiPool));
|
||||
// pubSignals[6] is the existingNullifierHash
|
||||
emit IPrivacyPool.Withdrawn(address(_entrypoint), _withdrawnValue, _proof.pubSignals[6], _proof.pubSignals[7]);
|
||||
|
||||
// Expect withdrawal event from entrypoint
|
||||
vm.expectEmit(address(_entrypoint));
|
||||
emit IEntrypoint.WithdrawalRelayed(_RELAYER, _ALICE, _DAI, _withdrawnValue, _withdrawnValue - _receivedAmount);
|
||||
|
||||
// Withdraw DAI
|
||||
vm.prank(_RELAYER);
|
||||
_entrypoint.relay(_withdrawal, _proof);
|
||||
|
||||
// Assert balances
|
||||
assertEq(_DAI.balanceOf(_ALICE), _aliceInitialBalance - _params.amount + _receivedAmount, 'Alice balance mismatch');
|
||||
assertEq(
|
||||
_DAI.balanceOf(address(_entrypoint)), _entrypointInitialBalance + _params.fee, 'Entrypoint balance mismatch'
|
||||
);
|
||||
assertEq(_DAI.balanceOf(address(_daiPool)), _daiPoolInitialBalance + _withdrawnValue, 'EthPool balance mismatch');
|
||||
assertEq(
|
||||
_DAI.balanceOf(_RELAYER), _relayerInitialBalance + _withdrawnValue - _receivedAmount, 'Relayer balance mismatch'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.28;
|
||||
|
||||
import {IntegrationBase} from '../IntegrationBase.sol';
|
||||
import {IEntrypoint} from 'contracts/Entrypoint.sol';
|
||||
import {IPrivacyPool} from 'contracts/PrivacyPool.sol';
|
||||
|
||||
import {ProofLib} from 'contracts/lib/ProofLib.sol';
|
||||
import {IState} from 'interfaces/IState.sol';
|
||||
|
||||
import {IERC20} from '@oz/interfaces/IERC20.sol';
|
||||
|
||||
contract IntegrationERC20DepositRagequit is IntegrationBase {
|
||||
function test_ERC20DepositRagequit() public {
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
DEPOSIT
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
// Generate deposit params
|
||||
DepositParams memory _params = _generateDefaultDepositParams(100 ether, _VETTING_FEE_BPS, _daiPool);
|
||||
|
||||
// Deal DAI to Alice
|
||||
deal(address(_DAI), _ALICE, _params.amount);
|
||||
|
||||
// Approve entrypoint
|
||||
vm.startPrank(_ALICE);
|
||||
_DAI.approve(address(_entrypoint), _params.amount);
|
||||
|
||||
// Expect deposit event from privacy pool
|
||||
vm.expectEmit(address(_daiPool));
|
||||
emit IPrivacyPool.Deposited(_ALICE, _params.commitment, _params.label, _params.amountAfterFee, _params.commitment);
|
||||
|
||||
// Expect deposit event from entrypoint
|
||||
vm.expectEmit(address(_entrypoint));
|
||||
emit IEntrypoint.Deposited(_ALICE, _daiPool, _params.commitment, _params.amountAfterFee);
|
||||
|
||||
// Assert balances
|
||||
uint256 _aliceInitialBalance = _DAI.balanceOf(_ALICE);
|
||||
uint256 _entrypointInitialBalance = _DAI.balanceOf(address(_entrypoint));
|
||||
uint256 _daiPoolInitialBalance = _DAI.balanceOf(address(_daiPool));
|
||||
|
||||
// Add the commitment to the shadow merkle tree
|
||||
_insertIntoShadowMerkleTree(_params.commitment);
|
||||
|
||||
// Deposit DAI
|
||||
_entrypoint.deposit(IERC20(_DAI), _params.amount, _params.precommitment);
|
||||
vm.stopPrank();
|
||||
|
||||
// Assert balances
|
||||
assertEq(_DAI.balanceOf(_ALICE), _aliceInitialBalance - _params.amount, 'Alice balance mismatch');
|
||||
assertEq(
|
||||
_DAI.balanceOf(address(_entrypoint)), _entrypointInitialBalance + _params.fee, 'Entrypoint balance mismatch'
|
||||
);
|
||||
assertEq(
|
||||
_DAI.balanceOf(address(_daiPool)), _daiPoolInitialBalance + _params.amountAfterFee, 'EthPool balance mismatch'
|
||||
);
|
||||
|
||||
// Assert deposit info
|
||||
(address _depositor, uint256 _value, uint256 _cooldownExpiry) = _daiPool.deposits(_params.label);
|
||||
assertEq(_depositor, _ALICE, 'Incorrect depositor');
|
||||
assertEq(_value, _params.amountAfterFee, 'Incorrect deposit value');
|
||||
assertEq(_cooldownExpiry, block.timestamp + 1 weeks, 'Incorrect deposit cooldown expiry');
|
||||
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
RAGEQUIT
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
// Generate ragequit proof
|
||||
ProofLib.RagequitProof memory _ragequitProof = _generateRagequitProof(
|
||||
_params.commitment, _params.precommitment, _params.nullifier, _params.amountAfterFee, _params.label
|
||||
);
|
||||
|
||||
// TODO: remove when we have a verifier
|
||||
vm.mockCall(
|
||||
address(_RAGEQUIT_VERIFIER),
|
||||
abi.encodeWithSignature('verifyProof((uint256[2],uint256[2][2],uint256[2],uint256[5]))', _ragequitProof),
|
||||
abi.encode(true)
|
||||
);
|
||||
|
||||
// Expect Ragequit initiated event from privacy pool
|
||||
vm.expectEmit(address(_daiPool));
|
||||
emit IPrivacyPool.Ragequit(_ALICE, _params.commitment, _params.label, _params.amountAfterFee);
|
||||
|
||||
// Initiate Ragequit
|
||||
vm.prank(_ALICE);
|
||||
_daiPool.ragequit(_ragequitProof);
|
||||
|
||||
assertTrue(_daiPool.nullifierHashes(_hashNullifier(_params.nullifier)), 'Nullifier not spent');
|
||||
|
||||
// Assert balances
|
||||
assertEq(_DAI.balanceOf(_ALICE), _aliceInitialBalance - _params.fee, 'Alice balance mismatch');
|
||||
assertEq(
|
||||
_DAI.balanceOf(address(_entrypoint)), _entrypointInitialBalance + _params.fee, 'Entrypoint balance mismatch'
|
||||
);
|
||||
assertEq(_DAI.balanceOf(address(_daiPool)), _daiPoolInitialBalance, 'EthPool balance mismatch');
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.28;
|
||||
|
||||
import {IntegrationBase} from '../IntegrationBase.sol';
|
||||
import {IEntrypoint} from 'contracts/Entrypoint.sol';
|
||||
import {IPrivacyPool} from 'contracts/PrivacyPool.sol';
|
||||
|
||||
import {ProofLib} from 'contracts/lib/ProofLib.sol';
|
||||
import {IVerifier} from 'interfaces/IVerifier.sol';
|
||||
|
||||
contract IntegrationEthDepositFullDirectWithdrawal is IntegrationBase {
|
||||
function test_EthDepositFullDirectWithdrawal() public {
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
DEPOSIT
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
// Generate deposit params
|
||||
DepositParams memory _params = _generateDefaultDepositParams(100 ether, _VETTING_FEE_BPS, _ethPool);
|
||||
|
||||
// Deal ETH to Alice
|
||||
deal(_ALICE, _params.amount);
|
||||
|
||||
// Expect deposit event from privacy pool
|
||||
vm.expectEmit(address(_ethPool));
|
||||
emit IPrivacyPool.Deposited(_ALICE, _params.commitment, _params.label, _params.amountAfterFee, _params.commitment);
|
||||
|
||||
// Expect deposit event from entrypoint
|
||||
vm.expectEmit(address(_entrypoint));
|
||||
emit IEntrypoint.Deposited(_ALICE, _ethPool, _params.commitment, _params.amountAfterFee);
|
||||
|
||||
// Assert balances
|
||||
uint256 _aliceInitialBalance = _ALICE.balance;
|
||||
uint256 _entrypointInitialBalance = address(_entrypoint).balance;
|
||||
uint256 _ethPoolInitialBalance = address(_ethPool).balance;
|
||||
|
||||
// Add the commitment to the shadow merkle tree
|
||||
_insertIntoShadowMerkleTree(_params.commitment);
|
||||
|
||||
// Deposit ETH
|
||||
vm.prank(_ALICE);
|
||||
_entrypoint.deposit{value: _params.amount}(_params.precommitment);
|
||||
|
||||
// Assert balances
|
||||
assertEq(_ALICE.balance, _aliceInitialBalance - _params.amount, 'Alice balance mismatch');
|
||||
assertEq(address(_entrypoint).balance, _entrypointInitialBalance + _params.fee, 'Entrypoint balance mismatch');
|
||||
assertEq(address(_ethPool).balance, _ethPoolInitialBalance + _params.amountAfterFee, 'EthPool balance mismatch');
|
||||
|
||||
// Assert deposit info
|
||||
(address _depositor, uint256 _value, uint256 _cooldownExpiry) = _ethPool.deposits(_params.label);
|
||||
assertEq(_depositor, _ALICE, 'Incorrect depositor');
|
||||
assertEq(_value, _params.amountAfterFee, 'Incorrect deposit value');
|
||||
assertEq(_cooldownExpiry, block.timestamp + 1 weeks, 'Incorrect deposit cooldown expiry');
|
||||
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
WITHDRAW
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
// Insert leaf into shadow asp merkle tree
|
||||
_insertIntoShadowASPMerkleTree(_DEFAULT_ASP_ROOT);
|
||||
|
||||
// Generate withdrawal params
|
||||
// Data is left empty given that the withdrawal is direct
|
||||
(IPrivacyPool.Withdrawal memory _withdrawal, ProofLib.WithdrawProof memory _proof) = _generateWithdrawalParams(
|
||||
WithdrawalParams({
|
||||
processor: _ALICE,
|
||||
recipient: address(0),
|
||||
feeRecipient: address(0),
|
||||
feeBps: 0,
|
||||
scope: _params.scope,
|
||||
withdrawnValue: _params.amountAfterFee,
|
||||
nullifier: _params.nullifier
|
||||
})
|
||||
);
|
||||
|
||||
// Push ASP root
|
||||
vm.prank(_POSTMAN);
|
||||
// pubSignals[3] is the ASPRoot
|
||||
_entrypoint.updateRoot(_proof.pubSignals[3], bytes32('IPFS_HASH'));
|
||||
|
||||
// TODO: remove once we have a verifier
|
||||
vm.mockCall(
|
||||
address(_WITHDRAWAL_VERIFIER),
|
||||
abi.encodeWithSignature('verifyProof((uint256[2],uint256[2][2],uint256[2],uint256[8]))', _proof),
|
||||
abi.encode(true)
|
||||
);
|
||||
|
||||
// Expect withdrawal event from privacy pool
|
||||
vm.expectEmit(address(_ethPool));
|
||||
// pubSignals[6] is the existingNullifierHash
|
||||
// pubSignals[7] is the newCommitmentHash
|
||||
emit IPrivacyPool.Withdrawn(_ALICE, _params.amountAfterFee, _proof.pubSignals[6], _proof.pubSignals[7]);
|
||||
|
||||
// Withdraw ETH
|
||||
vm.prank(_ALICE);
|
||||
_ethPool.withdraw(_withdrawal, _proof);
|
||||
|
||||
// Assert balances
|
||||
assertEq(_ALICE.balance, _aliceInitialBalance - _params.fee, 'Alice balance mismatch');
|
||||
assertEq(address(_entrypoint).balance, _entrypointInitialBalance + _params.fee, 'Entrypoint balance mismatch');
|
||||
assertEq(address(_ethPool).balance, _ethPoolInitialBalance, 'EthPool balance mismatch');
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.28;
|
||||
|
||||
import {IntegrationBase} from '../IntegrationBase.sol';
|
||||
import {IEntrypoint} from 'contracts/Entrypoint.sol';
|
||||
import {IPrivacyPool} from 'contracts/PrivacyPool.sol';
|
||||
|
||||
import {ProofLib} from 'contracts/lib/ProofLib.sol';
|
||||
import {IVerifier} from 'interfaces/IVerifier.sol';
|
||||
|
||||
contract IntegrationEthDepositFullRelayedWithdrawal is IntegrationBase {
|
||||
function test_EthDepositFullRelayedWithdrawal() public {
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
DEPOSIT
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
// Generate deposit params
|
||||
DepositParams memory _params = _generateDefaultDepositParams(100 ether, _VETTING_FEE_BPS, _ethPool);
|
||||
|
||||
// Deal ETH to Alice
|
||||
deal(_ALICE, _params.amount);
|
||||
|
||||
// Expect deposit event from privacy pool
|
||||
vm.expectEmit(address(_ethPool));
|
||||
emit IPrivacyPool.Deposited(_ALICE, _params.commitment, _params.label, _params.amountAfterFee, _params.commitment);
|
||||
|
||||
// Expect deposit event from entrypoint
|
||||
vm.expectEmit(address(_entrypoint));
|
||||
emit IEntrypoint.Deposited(_ALICE, _ethPool, _params.commitment, _params.amountAfterFee);
|
||||
|
||||
// Assert balances
|
||||
uint256 _aliceInitialBalance = _ALICE.balance;
|
||||
uint256 _entrypointInitialBalance = address(_entrypoint).balance;
|
||||
uint256 _ethPoolInitialBalance = address(_ethPool).balance;
|
||||
uint256 _relayerInitialBalance = _RELAYER.balance;
|
||||
|
||||
// Add the commitment to the shadow merkle tree
|
||||
_insertIntoShadowMerkleTree(_params.commitment);
|
||||
|
||||
// Deposit ETH
|
||||
vm.prank(_ALICE);
|
||||
_entrypoint.deposit{value: _params.amount}(_params.precommitment);
|
||||
|
||||
// Assert balances
|
||||
assertEq(_ALICE.balance, _aliceInitialBalance - _params.amount, 'Alice balance mismatch');
|
||||
assertEq(address(_entrypoint).balance, _entrypointInitialBalance + _params.fee, 'Entrypoint balance mismatch');
|
||||
assertEq(address(_ethPool).balance, _ethPoolInitialBalance + _params.amountAfterFee, 'EthPool balance mismatch');
|
||||
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
WITHDRAW
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
// Insert leaf into shadow asp merkle tree
|
||||
_insertIntoShadowASPMerkleTree(_DEFAULT_ASP_ROOT);
|
||||
|
||||
// Generate withdrawal params
|
||||
(IPrivacyPool.Withdrawal memory _withdrawal, ProofLib.WithdrawProof memory _proof) = _generateWithdrawalParams(
|
||||
WithdrawalParams({
|
||||
processor: address(_entrypoint),
|
||||
recipient: _ALICE,
|
||||
feeRecipient: _RELAYER,
|
||||
feeBps: _RELAY_FEE_BPS,
|
||||
scope: _params.scope,
|
||||
withdrawnValue: _params.amountAfterFee,
|
||||
nullifier: _params.nullifier
|
||||
})
|
||||
);
|
||||
|
||||
// Calculate received amount
|
||||
uint256 _receivedAmount = _deductFee(_params.amountAfterFee, _RELAY_FEE_BPS);
|
||||
|
||||
// Push ASP root
|
||||
vm.prank(_POSTMAN);
|
||||
// pubSignals[3] is the ASPRoot
|
||||
_entrypoint.updateRoot(_proof.pubSignals[3], bytes32('IPFS_HASH'));
|
||||
|
||||
// TODO: remove once we have a verifier
|
||||
vm.mockCall(
|
||||
address(_WITHDRAWAL_VERIFIER),
|
||||
abi.encodeWithSignature('verifyProof((uint256[2],uint256[2][2],uint256[2],uint256[8]))', _proof),
|
||||
abi.encode(true)
|
||||
);
|
||||
|
||||
// Expect withdrawal event from privacy pool
|
||||
vm.expectEmit(address(_ethPool));
|
||||
// pubSignals[6] is the existingNullifierHash
|
||||
// pubSignals[7] is the newCommitmentHash
|
||||
emit IPrivacyPool.Withdrawn(
|
||||
address(_entrypoint), _params.amountAfterFee, _proof.pubSignals[6], _proof.pubSignals[7]
|
||||
);
|
||||
|
||||
// Expect withdrawal event from entrypoint
|
||||
vm.expectEmit(address(_entrypoint));
|
||||
emit IEntrypoint.WithdrawalRelayed(
|
||||
_RELAYER, _ALICE, _ETH, _params.amountAfterFee, _params.amountAfterFee - _receivedAmount
|
||||
);
|
||||
|
||||
// Withdraw ETH
|
||||
vm.prank(_RELAYER);
|
||||
_entrypoint.relay(_withdrawal, _proof);
|
||||
|
||||
// Assert balances
|
||||
assertEq(_ALICE.balance, _aliceInitialBalance - _params.amount + _receivedAmount, 'Alice balance mismatch');
|
||||
assertEq(address(_entrypoint).balance, _entrypointInitialBalance + _params.fee, 'Entrypoint balance mismatch');
|
||||
assertEq(address(_ethPool).balance, _ethPoolInitialBalance, 'EthPool balance mismatch');
|
||||
assertEq(
|
||||
_RELAYER.balance, _relayerInitialBalance + _params.amountAfterFee - _receivedAmount, 'Relayer balance mismatch'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.28;
|
||||
|
||||
import {IntegrationBase} from '../IntegrationBase.sol';
|
||||
import {IEntrypoint} from 'contracts/Entrypoint.sol';
|
||||
import {IPrivacyPool} from 'contracts/PrivacyPool.sol';
|
||||
|
||||
import {ProofLib} from 'contracts/lib/ProofLib.sol';
|
||||
import {IVerifier} from 'interfaces/IVerifier.sol';
|
||||
|
||||
contract IntegrationEthDepositPartialDirectWithdrawal is IntegrationBase {
|
||||
function test_EthDepositPartialDirectWithdrawal() public {
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
DEPOSIT
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
// Generate deposit params
|
||||
DepositParams memory _params = _generateDefaultDepositParams(100 ether, _VETTING_FEE_BPS, _ethPool);
|
||||
|
||||
// Deal ETH to Alice
|
||||
deal(_ALICE, _params.amount);
|
||||
|
||||
// Expect deposit event from privacy pool
|
||||
vm.expectEmit(address(_ethPool));
|
||||
emit IPrivacyPool.Deposited(_ALICE, _params.commitment, _params.label, _params.amountAfterFee, _params.commitment);
|
||||
|
||||
// Expect deposit event from entrypoint
|
||||
vm.expectEmit(address(_entrypoint));
|
||||
emit IEntrypoint.Deposited(_ALICE, _ethPool, _params.commitment, _params.amountAfterFee);
|
||||
|
||||
// Assert balances
|
||||
uint256 _aliceInitialBalance = _ALICE.balance;
|
||||
uint256 _entrypointInitialBalance = address(_entrypoint).balance;
|
||||
uint256 _ethPoolInitialBalance = address(_ethPool).balance;
|
||||
|
||||
// Add the commitment to the shadow merkle tree
|
||||
_insertIntoShadowMerkleTree(_params.commitment);
|
||||
|
||||
// Deposit ETH
|
||||
vm.prank(_ALICE);
|
||||
_entrypoint.deposit{value: _params.amount}(_params.precommitment);
|
||||
|
||||
// Assert balances
|
||||
assertEq(_ALICE.balance, _aliceInitialBalance - _params.amount, 'Alice balance mismatch');
|
||||
assertEq(address(_entrypoint).balance, _entrypointInitialBalance + _params.fee, 'Entrypoint balance mismatch');
|
||||
assertEq(address(_ethPool).balance, _ethPoolInitialBalance + _params.amountAfterFee, 'EthPool balance mismatch');
|
||||
|
||||
// Assert deposit info
|
||||
(address _depositor, uint256 _value, uint256 _cooldownExpiry) = _ethPool.deposits(_params.label);
|
||||
assertEq(_depositor, _ALICE, 'Incorrect depositor');
|
||||
assertEq(_value, _params.amountAfterFee, 'Incorrect deposit value');
|
||||
assertEq(_cooldownExpiry, block.timestamp + 1 weeks, 'Incorrect deposit cooldown expiry');
|
||||
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
WITHDRAW
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
// Withdraw half of the deposit
|
||||
uint256 _withdrawnValue = _params.amountAfterFee / 2;
|
||||
|
||||
// Insert leaf into shadow asp merkle tree
|
||||
_insertIntoShadowASPMerkleTree(_DEFAULT_ASP_ROOT);
|
||||
|
||||
// Data is left empty given that the withdrawal is direct
|
||||
(IPrivacyPool.Withdrawal memory _withdrawal, ProofLib.WithdrawProof memory _proof) = _generateWithdrawalParams(
|
||||
WithdrawalParams({
|
||||
processor: _ALICE,
|
||||
recipient: address(0),
|
||||
feeRecipient: address(0),
|
||||
feeBps: 0,
|
||||
scope: _params.scope,
|
||||
withdrawnValue: _withdrawnValue,
|
||||
nullifier: _params.nullifier
|
||||
})
|
||||
);
|
||||
|
||||
// Push ASP root
|
||||
vm.prank(_POSTMAN);
|
||||
// pubSignals[3] is the ASPRoot
|
||||
_entrypoint.updateRoot(_proof.pubSignals[3], bytes32('IPFS_HASH'));
|
||||
|
||||
// TODO: remove once we have a verifier
|
||||
vm.mockCall(
|
||||
address(_WITHDRAWAL_VERIFIER),
|
||||
abi.encodeWithSignature('verifyProof((uint256[2],uint256[2][2],uint256[2],uint256[8]))', _proof),
|
||||
abi.encode(true)
|
||||
);
|
||||
|
||||
// Expect withdrawal event from privacy pool
|
||||
vm.expectEmit(address(_ethPool));
|
||||
// pubSignals[6] is the existingNullifierHash
|
||||
// pubSignals[7] is the newCommitmentHash
|
||||
emit IPrivacyPool.Withdrawn(_ALICE, _withdrawnValue, _proof.pubSignals[6], _proof.pubSignals[7]);
|
||||
|
||||
// Withdraw ETH
|
||||
vm.prank(_ALICE);
|
||||
_ethPool.withdraw(_withdrawal, _proof);
|
||||
|
||||
// Assert balances
|
||||
assertEq(_ALICE.balance, _aliceInitialBalance - _params.amount + _withdrawnValue, 'Alice balance mismatch');
|
||||
assertEq(address(_entrypoint).balance, _entrypointInitialBalance + _params.fee, 'Entrypoint balance mismatch');
|
||||
assertEq(
|
||||
address(_ethPool).balance,
|
||||
_ethPoolInitialBalance + _params.amountAfterFee - _withdrawnValue,
|
||||
'EthPool balance mismatch'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.28;
|
||||
|
||||
import {IntegrationBase} from '../IntegrationBase.sol';
|
||||
import {IEntrypoint} from 'contracts/Entrypoint.sol';
|
||||
import {IPrivacyPool} from 'contracts/PrivacyPool.sol';
|
||||
|
||||
import {ProofLib} from 'contracts/lib/ProofLib.sol';
|
||||
import {IVerifier} from 'interfaces/IVerifier.sol';
|
||||
|
||||
contract IntegrationEthDepositPartialRelayedWithdrawal is IntegrationBase {
|
||||
function test_EthDepositPartialRelayedWithdrawal() public {
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
DEPOSIT
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
// Generate deposit params
|
||||
DepositParams memory _params = _generateDefaultDepositParams(100 ether, _VETTING_FEE_BPS, _ethPool);
|
||||
|
||||
// Deal ETH to Alice
|
||||
deal(_ALICE, _params.amount);
|
||||
|
||||
// Expect deposit event from privacy pool
|
||||
vm.expectEmit(address(_ethPool));
|
||||
emit IPrivacyPool.Deposited(_ALICE, _params.commitment, _params.label, _params.amountAfterFee, _params.commitment);
|
||||
|
||||
// Expect deposit event from entrypoint
|
||||
vm.expectEmit(address(_entrypoint));
|
||||
emit IEntrypoint.Deposited(_ALICE, _ethPool, _params.commitment, _params.amountAfterFee);
|
||||
|
||||
uint256 _aliceInitialBalance = _ALICE.balance;
|
||||
uint256 _entrypointInitialBalance = address(_entrypoint).balance;
|
||||
uint256 _ethPoolInitialBalance = address(_ethPool).balance;
|
||||
uint256 _relayerInitialBalance = _RELAYER.balance;
|
||||
|
||||
// Add the commitment to the shadow merkle tree
|
||||
_insertIntoShadowMerkleTree(_params.commitment);
|
||||
|
||||
// Deposit ETH
|
||||
vm.prank(_ALICE);
|
||||
_entrypoint.deposit{value: _params.amount}(_params.precommitment);
|
||||
|
||||
// Assert balances
|
||||
assertEq(_ALICE.balance, _aliceInitialBalance - _params.amount, 'Alice balance mismatch');
|
||||
assertEq(address(_entrypoint).balance, _entrypointInitialBalance + _params.fee, 'Entrypoint balance mismatch');
|
||||
assertEq(address(_ethPool).balance, _ethPoolInitialBalance + _params.amountAfterFee, 'EthPool balance mismatch');
|
||||
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
WITHDRAW
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
// Withdraw partial amount
|
||||
uint256 _withdrawnValue = _params.amountAfterFee / 2;
|
||||
|
||||
// Insert leaf into shadow asp merkle tree
|
||||
_insertIntoShadowASPMerkleTree(_DEFAULT_ASP_ROOT);
|
||||
|
||||
// Generate withdrawal params
|
||||
(IPrivacyPool.Withdrawal memory _withdrawal, ProofLib.WithdrawProof memory _proof) = _generateWithdrawalParams(
|
||||
WithdrawalParams({
|
||||
processor: address(_entrypoint),
|
||||
recipient: _ALICE,
|
||||
feeRecipient: _RELAYER,
|
||||
feeBps: _RELAY_FEE_BPS,
|
||||
scope: _params.scope,
|
||||
// Notice we withdraw half of the deposit
|
||||
withdrawnValue: _withdrawnValue,
|
||||
nullifier: _params.nullifier
|
||||
})
|
||||
);
|
||||
|
||||
// Push ASP root
|
||||
vm.prank(_POSTMAN);
|
||||
// pubSignals[3] is the ASPRoot
|
||||
_entrypoint.updateRoot(_proof.pubSignals[3], bytes32('IPFS_HASH'));
|
||||
|
||||
// Calculate received amount
|
||||
uint256 _receivedAmount = _deductFee(_withdrawnValue, _RELAY_FEE_BPS);
|
||||
|
||||
// TODO: remove once we have a verifier
|
||||
vm.mockCall(
|
||||
address(_WITHDRAWAL_VERIFIER),
|
||||
abi.encodeWithSignature('verifyProof((uint256[2],uint256[2][2],uint256[2],uint256[8]))', _proof),
|
||||
abi.encode(true)
|
||||
);
|
||||
|
||||
// Expect withdrawal event from privacy pool
|
||||
vm.expectEmit(address(_ethPool));
|
||||
// pubSignals[6] is the existingNullifierHash
|
||||
// pubSignals[7] is the newCommitmentHash
|
||||
emit IPrivacyPool.Withdrawn(address(_entrypoint), _withdrawnValue, _proof.pubSignals[6], _proof.pubSignals[7]);
|
||||
|
||||
// Expect withdrawal event from entrypoint
|
||||
vm.expectEmit(address(_entrypoint));
|
||||
emit IEntrypoint.WithdrawalRelayed(_RELAYER, _ALICE, _ETH, _withdrawnValue, _withdrawnValue - _receivedAmount);
|
||||
|
||||
// Withdraw ETH
|
||||
vm.prank(_RELAYER);
|
||||
_entrypoint.relay(_withdrawal, _proof);
|
||||
|
||||
// Assert balances
|
||||
assertEq(_ALICE.balance, _aliceInitialBalance - _params.amount + _receivedAmount, 'Alice balance mismatch');
|
||||
assertEq(address(_entrypoint).balance, _entrypointInitialBalance + _params.fee, 'Entrypoint balance mismatch');
|
||||
assertEq(address(_ethPool).balance, _ethPoolInitialBalance + _withdrawnValue, 'EthPool balance mismatch');
|
||||
assertEq(_RELAYER.balance, _relayerInitialBalance + _withdrawnValue - _receivedAmount, 'Relayer balance mismatch');
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.28;
|
||||
|
||||
import {IntegrationBase} from '../IntegrationBase.sol';
|
||||
import {IEntrypoint} from 'contracts/Entrypoint.sol';
|
||||
import {IPrivacyPool} from 'contracts/PrivacyPool.sol';
|
||||
|
||||
import {ProofLib} from 'contracts/lib/ProofLib.sol';
|
||||
import {IState} from 'interfaces/IState.sol';
|
||||
|
||||
contract IntegrationEthDepositRagequit is IntegrationBase {
|
||||
function test_EthDepositRagequit() public {
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
DEPOSIT
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
// Generate deposit params
|
||||
DepositParams memory _params = _generateDefaultDepositParams(100 ether, _VETTING_FEE_BPS, _ethPool);
|
||||
deal(_ALICE, _params.amount);
|
||||
|
||||
// Expect deposit event from privacy pool
|
||||
vm.expectEmit(address(_ethPool));
|
||||
emit IPrivacyPool.Deposited(_ALICE, _params.commitment, _params.label, _params.amountAfterFee, _params.commitment);
|
||||
|
||||
// Expect deposit event from entrypoint
|
||||
vm.expectEmit(address(_entrypoint));
|
||||
emit IEntrypoint.Deposited(_ALICE, _ethPool, _params.commitment, _params.amountAfterFee);
|
||||
|
||||
uint256 _aliceInitialBalance = _ALICE.balance;
|
||||
uint256 _entrypointInitialBalance = address(_entrypoint).balance;
|
||||
uint256 _ethPoolInitialBalance = address(_ethPool).balance;
|
||||
|
||||
// Add the commitment to the shadow merkle tree
|
||||
_insertIntoShadowMerkleTree(_params.commitment);
|
||||
|
||||
// Deposit ETH
|
||||
vm.prank(_ALICE);
|
||||
_entrypoint.deposit{value: _params.amount}(_params.precommitment);
|
||||
|
||||
// Assert balances
|
||||
assertEq(_ALICE.balance, _aliceInitialBalance - _params.amount, 'Alice balance mismatch');
|
||||
assertEq(address(_entrypoint).balance, _entrypointInitialBalance + _params.fee, 'Entrypoint balance mismatch');
|
||||
assertEq(address(_ethPool).balance, _ethPoolInitialBalance + _params.amountAfterFee, 'EthPool balance mismatch');
|
||||
|
||||
// Assert deposit data
|
||||
(address _depositor, uint256 _value, uint256 _cooldownExpiry) = _ethPool.deposits(_params.label);
|
||||
assertEq(_depositor, _ALICE, 'Incorrect depositor');
|
||||
assertEq(_value, _params.amountAfterFee, 'Incorrect deposit value');
|
||||
assertEq(_cooldownExpiry, block.timestamp + 1 weeks, 'Incorrect deposit cooldown expiry');
|
||||
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
RAGEQUIT
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
// Generate ragequit proof
|
||||
ProofLib.RagequitProof memory _ragequitProof = _generateRagequitProof(
|
||||
_params.commitment, _params.precommitment, _params.nullifier, _params.amountAfterFee, _params.label
|
||||
);
|
||||
|
||||
// TODO: remove when we have a verifier
|
||||
vm.mockCall(
|
||||
address(_RAGEQUIT_VERIFIER),
|
||||
abi.encodeWithSignature('verifyProof((uint256[2],uint256[2][2],uint256[2],uint256[5]))', _ragequitProof),
|
||||
abi.encode(true)
|
||||
);
|
||||
|
||||
// Expect Ragequit initiated event from privacy pool
|
||||
vm.expectEmit(address(_ethPool));
|
||||
emit IPrivacyPool.Ragequit(_ALICE, _params.commitment, _params.label, _params.amountAfterFee);
|
||||
|
||||
// Initiate Ragequit
|
||||
vm.prank(_ALICE);
|
||||
_ethPool.ragequit(_ragequitProof);
|
||||
|
||||
assertTrue(_ethPool.nullifierHashes(_hashNullifier(_params.nullifier)), 'Nullifier not spent');
|
||||
|
||||
// Assert balances
|
||||
assertEq(_ALICE.balance, _aliceInitialBalance - _params.fee, 'Alice balance mismatch');
|
||||
assertEq(address(_entrypoint).balance, _entrypointInitialBalance + _params.fee, 'Entrypoint balance mismatch');
|
||||
assertEq(address(_ethPool).balance, _ethPoolInitialBalance, 'EthPool balance mismatch');
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,9 @@ import {IPrivacyPool} from 'contracts/PrivacyPool.sol';
|
||||
import {IPrivacyPoolComplex, PrivacyPoolComplex} from 'contracts/implementations/PrivacyPoolComplex.sol';
|
||||
import {IPrivacyPoolSimple, PrivacyPoolSimple} from 'contracts/implementations/PrivacyPoolSimple.sol';
|
||||
|
||||
import {CommitmentVerifier} from 'contracts/verifiers/CommitmentVerifier.sol';
|
||||
import {WithdrawalVerifier} from 'contracts/verifiers/WithdrawalVerifier.sol';
|
||||
|
||||
import {UnsafeUpgrades} from '@upgrades/Upgrades.sol';
|
||||
|
||||
import {IERC20} from '@oz/interfaces/IERC20.sol';
|
||||
@@ -24,193 +27,448 @@ import {Constants} from 'test/helper/Constants.sol';
|
||||
contract IntegrationBase is Test {
|
||||
using InternalLeanIMT for LeanIMTData;
|
||||
|
||||
struct DepositParams {
|
||||
uint256 amount;
|
||||
uint256 amountAfterFee;
|
||||
uint256 fee;
|
||||
uint256 secret;
|
||||
uint256 nullifier;
|
||||
uint256 precommitment;
|
||||
uint256 nonce;
|
||||
uint256 scope;
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
STRUCTS
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
struct Commitment {
|
||||
uint256 hash;
|
||||
uint256 label;
|
||||
uint256 commitment;
|
||||
uint256 value;
|
||||
uint256 precommitment;
|
||||
uint256 nullifier;
|
||||
uint256 secret;
|
||||
IERC20 asset;
|
||||
}
|
||||
|
||||
struct DepositParams {
|
||||
address depositor;
|
||||
IERC20 asset;
|
||||
uint256 amount;
|
||||
string nullifier;
|
||||
string secret;
|
||||
}
|
||||
|
||||
struct WithdrawalParams {
|
||||
address processor;
|
||||
uint256 withdrawnAmount;
|
||||
string newNullifier;
|
||||
string newSecret;
|
||||
address recipient;
|
||||
address feeRecipient;
|
||||
uint256 feeBps;
|
||||
uint256 scope;
|
||||
uint256 withdrawnValue;
|
||||
uint256 nullifier;
|
||||
Commitment commitment;
|
||||
bytes4 revertReason;
|
||||
}
|
||||
|
||||
struct WithdrawalProofParams {
|
||||
uint256 existingCommitment;
|
||||
uint256 withdrawnValue;
|
||||
uint256 context;
|
||||
uint256 label;
|
||||
uint256 existingValue;
|
||||
uint256 existingNullifier;
|
||||
uint256 existingSecret;
|
||||
uint256 newNullifier;
|
||||
uint256 newSecret;
|
||||
}
|
||||
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
STATE VARIABLES
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
uint256 internal constant _FORK_BLOCK = 18_920_905;
|
||||
|
||||
// Core protocol contracts
|
||||
IEntrypoint internal _entrypoint;
|
||||
IPrivacyPoolSimple internal _ethPool;
|
||||
IPrivacyPoolComplex internal _daiPool;
|
||||
IPrivacyPool internal _ethPool;
|
||||
IPrivacyPool internal _daiPool;
|
||||
|
||||
// Groth16 Verifiers
|
||||
CommitmentVerifier internal _commitmentVerifier;
|
||||
WithdrawalVerifier internal _withdrawalVerifier;
|
||||
|
||||
// Assets
|
||||
IERC20 internal constant _ETH = IERC20(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
|
||||
IERC20 internal constant _DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F);
|
||||
|
||||
// Mirrored Merkle Trees
|
||||
LeanIMTData internal _shadowMerkleTree;
|
||||
uint256[] internal _merkleLeaves;
|
||||
LeanIMTData internal _shadowASPMerkleTree;
|
||||
uint256[] internal _aspLeaves;
|
||||
|
||||
// Snark Scalar Field
|
||||
uint256 internal constant SNARK_SCALAR_FIELD =
|
||||
21_888_242_871_839_275_222_246_405_745_257_275_088_548_364_400_416_034_343_698_204_186_575_808_495_617;
|
||||
|
||||
// Pranked addresses
|
||||
address internal immutable _OWNER = makeAddr('OWNER');
|
||||
address internal immutable _POSTMAN = makeAddr('POSTMAN');
|
||||
address internal immutable _WITHDRAWAL_VERIFIER = makeAddr('WITHDRAWAL_VERIFIER');
|
||||
address internal immutable _RAGEQUIT_VERIFIER = makeAddr('RAGEQUIT_VERIFIER');
|
||||
address internal immutable _RELAYER = makeAddr('RELAYER');
|
||||
address internal immutable _ALICE = makeAddr('ALICE');
|
||||
address internal immutable _BOB = makeAddr('BOB');
|
||||
|
||||
// Asset parameters
|
||||
uint256 internal constant _MIN_DEPOSIT = 1;
|
||||
uint256 internal constant _VETTING_FEE_BPS = 100; // 1%
|
||||
uint256 internal constant _RELAY_FEE_BPS = 100; // 1%
|
||||
|
||||
uint256 internal constant _DEFAULT_NULLIFIER = uint256(keccak256('NULLIFIER'));
|
||||
uint256 internal constant _DEFAULT_SECRET = uint256(keccak256('SECRET'));
|
||||
uint256 internal constant _DEFAULT_NULLIFIER = uint256(keccak256('NULLIFIER')) % Constants.SNARK_SCALAR_FIELD;
|
||||
uint256 internal constant _DEFAULT_SECRET = uint256(keccak256('SECRET')) % Constants.SNARK_SCALAR_FIELD;
|
||||
uint256 internal constant _DEFAULT_ASP_ROOT = uint256(keccak256('ASP_ROOT')) % Constants.SNARK_SCALAR_FIELD;
|
||||
uint256 internal constant _DEFAULT_NEW_COMMITMENT_HASH =
|
||||
uint256(keccak256('NEW_COMMITMENT_HASH')) % Constants.SNARK_SCALAR_FIELD;
|
||||
bytes4 internal constant NONE = 0xb4dc0dee;
|
||||
|
||||
function setUp() public {
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
SETUP
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
function setUp() public virtual {
|
||||
vm.createSelectFork(vm.rpcUrl('mainnet'));
|
||||
_deployContracts();
|
||||
_registerPools();
|
||||
}
|
||||
|
||||
function _deployContracts() internal {
|
||||
vm.startPrank(_OWNER);
|
||||
// Deploy Groth16 ragequit verifier
|
||||
_commitmentVerifier = new CommitmentVerifier();
|
||||
|
||||
// Deploy Groth16 withdrawal verifier
|
||||
_withdrawalVerifier = new WithdrawalVerifier();
|
||||
|
||||
// Deploy Entrypoint
|
||||
address _impl = address(new Entrypoint());
|
||||
|
||||
_entrypoint = Entrypoint(
|
||||
payable(UnsafeUpgrades.deployUUPSProxy(_impl, abi.encodeCall(Entrypoint.initialize, (_OWNER, _POSTMAN))))
|
||||
);
|
||||
|
||||
// Deploy ETH Pool
|
||||
_ethPool = new PrivacyPoolSimple(address(_entrypoint), address(_WITHDRAWAL_VERIFIER), address(_RAGEQUIT_VERIFIER));
|
||||
_ethPool = IPrivacyPool(
|
||||
address(new PrivacyPoolSimple(address(_entrypoint), address(_withdrawalVerifier), address(_commitmentVerifier)))
|
||||
);
|
||||
|
||||
// Deploy DAI Pool
|
||||
_daiPool = new PrivacyPoolComplex(
|
||||
address(_entrypoint), address(_WITHDRAWAL_VERIFIER), address(_RAGEQUIT_VERIFIER), address(_DAI)
|
||||
_daiPool = IPrivacyPool(
|
||||
address(
|
||||
new PrivacyPoolComplex(
|
||||
address(_entrypoint), address(_withdrawalVerifier), address(_commitmentVerifier), address(_DAI)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function _registerPools() internal {
|
||||
vm.startPrank(_OWNER);
|
||||
// Register ETH pool
|
||||
_entrypoint.registerPool(_ETH, IPrivacyPool(_ethPool), _MIN_DEPOSIT, _VETTING_FEE_BPS);
|
||||
|
||||
// Register DAI pool
|
||||
_entrypoint.registerPool(_DAI, IPrivacyPool(_daiPool), _MIN_DEPOSIT, _VETTING_FEE_BPS);
|
||||
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
function _deductFee(uint256 _amount, uint256 _feeBps) internal pure returns (uint256 _amountAfterFee) {
|
||||
return _amount - (_amount * _feeBps) / 10_000;
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
DEPOSIT
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
function _deposit(DepositParams memory _params) internal returns (Commitment memory _commitment) {
|
||||
// Deal the asset to the depositor
|
||||
_deal(_params.depositor, _params.asset, _params.amount);
|
||||
|
||||
if (_params.asset != _ETH) {
|
||||
vm.prank(_params.depositor);
|
||||
_params.asset.approve(address(_entrypoint), _params.amount);
|
||||
}
|
||||
|
||||
function _hashNullifier(uint256 _nullifier) internal pure returns (uint256) {
|
||||
return PoseidonT2.hash([_nullifier]);
|
||||
// Define pool to deposit to
|
||||
IPrivacyPool _pool = _params.asset == _ETH ? _ethPool : _daiPool;
|
||||
|
||||
uint256 _currentNonce = _pool.nonce();
|
||||
|
||||
// Compute deposit parameters
|
||||
_commitment.asset = _params.asset;
|
||||
_commitment.nullifier = _genSecretBySeed(_params.nullifier);
|
||||
_commitment.secret = _genSecretBySeed(_params.secret);
|
||||
_commitment.label =
|
||||
uint256(keccak256(abi.encodePacked(_pool.SCOPE(), ++_currentNonce))) % Constants.SNARK_SCALAR_FIELD;
|
||||
_commitment.value = _deductFee(_params.amount, _VETTING_FEE_BPS);
|
||||
_commitment.precommitment = _hashPrecommitment(_commitment.nullifier, _commitment.secret);
|
||||
_commitment.hash = _hashCommitment(_commitment.value, _commitment.label, _commitment.precommitment);
|
||||
|
||||
// Calculate Entrypoint fee
|
||||
uint256 _fee = _params.amount - _commitment.value;
|
||||
|
||||
// Update mirrored trees
|
||||
_insertIntoShadowMerkleTree(_commitment.hash);
|
||||
_insertIntoShadowASPMerkleTree(_commitment.label);
|
||||
|
||||
// Fetch balances before deposit
|
||||
uint256 _depositorInitialBalance = _balance(_params.depositor, _params.asset);
|
||||
uint256 _entrypointInitialBalance = _balance(address(_entrypoint), _params.asset);
|
||||
uint256 _poolInitialBalance = _balance(address(_pool), _params.asset);
|
||||
|
||||
// Expect Pool event emission
|
||||
vm.expectEmit(address(_pool));
|
||||
emit IPrivacyPool.Deposited(
|
||||
_params.depositor, _commitment.hash, _commitment.label, _commitment.value, _shadowMerkleTree._root()
|
||||
);
|
||||
|
||||
// Expect Entrypoint event emission
|
||||
vm.expectEmit(address(_entrypoint));
|
||||
emit IEntrypoint.Deposited(_params.depositor, _pool, _commitment.hash, _commitment.value);
|
||||
|
||||
// Deposit
|
||||
vm.prank(_params.depositor);
|
||||
if (_params.asset == _ETH) {
|
||||
_entrypoint.deposit{value: _params.amount}(_commitment.precommitment);
|
||||
} else {
|
||||
_entrypoint.deposit(_params.asset, _params.amount, _commitment.precommitment);
|
||||
}
|
||||
|
||||
function _hashPrecommitment(uint256 _nullifier, uint256 _secret) internal pure returns (uint256) {
|
||||
return PoseidonT3.hash([_nullifier, _secret]);
|
||||
// Check balance changes
|
||||
assertEq(
|
||||
_balance(_params.depositor, _params.asset), _depositorInitialBalance - _params.amount, 'User balance mismatch'
|
||||
);
|
||||
assertEq(
|
||||
_balance(address(_entrypoint), _params.asset), _entrypointInitialBalance + _fee, 'Entrypoint balance mismatch'
|
||||
);
|
||||
assertEq(_balance(address(_pool), _params.asset), _poolInitialBalance + _commitment.value, 'Pool balance mismatch');
|
||||
|
||||
// Check deposit stored values
|
||||
(address _depositor, uint256 _value, uint256 _cooldownExpiry) = _pool.deposits(_commitment.label);
|
||||
assertEq(_depositor, _params.depositor, 'Incorrect depositor');
|
||||
assertEq(_value, _commitment.value, 'Incorrect deposit value');
|
||||
assertEq(_cooldownExpiry, block.timestamp + 1 weeks, 'Incorrect deposit cooldown expiry');
|
||||
}
|
||||
|
||||
function _hashCommitment(uint256 _amount, uint256 _label, uint256 _precommitment) internal pure returns (uint256) {
|
||||
return PoseidonT4.hash([_amount, _label, _precommitment]);
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
WITHDRAWAL METHODS
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
function _selfWithdraw(WithdrawalParams memory _params) internal returns (Commitment memory _commitment) {
|
||||
IPrivacyPool _pool = _params.commitment.asset == _ETH ? _ethPool : _daiPool;
|
||||
|
||||
IPrivacyPool.Withdrawal memory _withdrawal =
|
||||
IPrivacyPool.Withdrawal({processooor: _params.recipient, scope: _pool.SCOPE(), data: ''});
|
||||
|
||||
_commitment = _withdraw(_params.recipient, _pool, _withdrawal, _params, true);
|
||||
}
|
||||
|
||||
function _generateDefaultDepositParams(
|
||||
uint256 _amount,
|
||||
uint256 _feeBps,
|
||||
IPrivacyPool _pool
|
||||
) internal view returns (DepositParams memory _params) {
|
||||
return _generateDepositParams(_amount, _feeBps, _DEFAULT_NULLIFIER, _DEFAULT_SECRET, _pool);
|
||||
function _withdrawThroughRelayer(WithdrawalParams memory _params) internal returns (Commitment memory _commitment) {
|
||||
IPrivacyPool _pool = _params.commitment.asset == _ETH ? _ethPool : _daiPool;
|
||||
|
||||
IPrivacyPool.Withdrawal memory _withdrawal = IPrivacyPool.Withdrawal({
|
||||
processooor: address(_entrypoint),
|
||||
scope: _pool.SCOPE(),
|
||||
data: abi.encode(_params.recipient, _RELAYER, _VETTING_FEE_BPS)
|
||||
});
|
||||
|
||||
_commitment = _withdraw(_RELAYER, _pool, _withdrawal, _params, false);
|
||||
}
|
||||
|
||||
function _generateDepositParams(
|
||||
uint256 _amount,
|
||||
uint256 _feeBps,
|
||||
uint256 _nullifier,
|
||||
uint256 _secret,
|
||||
IPrivacyPool _pool
|
||||
) internal view returns (DepositParams memory _params) {
|
||||
_params.amount = _amount;
|
||||
_params.amountAfterFee = _deductFee(_amount, _feeBps);
|
||||
_params.fee = _amount - _params.amountAfterFee;
|
||||
_params.secret = _secret;
|
||||
_params.nullifier = _nullifier;
|
||||
_params.precommitment = _hashPrecommitment(_params.nullifier, _params.secret);
|
||||
_params.nonce = _pool.nonce();
|
||||
_params.scope = _pool.SCOPE();
|
||||
_params.label = uint256(keccak256(abi.encodePacked(_params.scope, ++_params.nonce)));
|
||||
_params.commitment = _hashCommitment(_params.amountAfterFee, _params.label, _params.precommitment);
|
||||
}
|
||||
function _withdraw(
|
||||
address _caller,
|
||||
IPrivacyPool _pool,
|
||||
IPrivacyPool.Withdrawal memory _withdrawal,
|
||||
WithdrawalParams memory _params,
|
||||
bool _direct
|
||||
) private returns (Commitment memory _commitment) {
|
||||
// Compute context hash
|
||||
uint256 _context = uint256(keccak256(abi.encode(_withdrawal, _pool.SCOPE()))) % SNARK_SCALAR_FIELD;
|
||||
|
||||
function _generateWithdrawalParams(WithdrawalParams memory _params)
|
||||
internal
|
||||
view
|
||||
returns (IPrivacyPool.Withdrawal memory _withdrawal, ProofLib.WithdrawProof memory _proof)
|
||||
{
|
||||
bytes memory _feeData = abi.encode(
|
||||
IEntrypoint.FeeData({
|
||||
recipient: _params.recipient,
|
||||
feeRecipient: _params.feeRecipient,
|
||||
relayFeeBPS: _params.feeBps
|
||||
_commitment.value = _params.commitment.value - _params.withdrawnAmount;
|
||||
_commitment.label = _params.commitment.label;
|
||||
_commitment.nullifier = _genSecretBySeed(_params.newNullifier);
|
||||
_commitment.secret = _genSecretBySeed(_params.newSecret);
|
||||
_commitment.precommitment = _hashPrecommitment(_commitment.nullifier, _commitment.secret);
|
||||
_commitment.hash = _hashCommitment(_commitment.value, _commitment.label, _commitment.precommitment);
|
||||
_commitment.asset = _params.commitment.asset;
|
||||
|
||||
// Generate withdrawal proof
|
||||
ProofLib.WithdrawProof memory _proof = _generateWithdrawalProof(
|
||||
WithdrawalProofParams({
|
||||
existingCommitment: _params.commitment.hash,
|
||||
withdrawnValue: _params.withdrawnAmount,
|
||||
context: _context,
|
||||
label: _params.commitment.label,
|
||||
existingValue: _params.commitment.value,
|
||||
existingNullifier: _params.commitment.nullifier,
|
||||
existingSecret: _params.commitment.secret,
|
||||
newNullifier: _commitment.nullifier,
|
||||
newSecret: _commitment.secret
|
||||
})
|
||||
);
|
||||
_withdrawal = IPrivacyPool.Withdrawal(_params.processor, _params.scope, _feeData);
|
||||
uint256 _context = uint256(keccak256(abi.encode(_withdrawal, _params.scope)));
|
||||
uint256 _stateRoot = _shadowMerkleTree._root();
|
||||
uint256 _aspRoot = _shadowASPMerkleTree._root();
|
||||
uint256 _newCommitmentHash = uint256(keccak256('NEW_COMMITMENT_HASH')) % Constants.SNARK_SCALAR_FIELD;
|
||||
uint256 _nullifierHash = _hashNullifier(_params.nullifier);
|
||||
|
||||
_proof = ProofLib.WithdrawProof({
|
||||
pA: [uint256(0), uint256(0)],
|
||||
pB: [[uint256(0), uint256(0)], [uint256(0), uint256(0)]],
|
||||
pC: [uint256(0), uint256(0)],
|
||||
pubSignals: [
|
||||
_params.withdrawnValue,
|
||||
_stateRoot,
|
||||
uint256(0), // pubSignals[2] is the stateTreeDepth
|
||||
_aspRoot,
|
||||
uint256(0), // pubSignals[4] is the ASPTreeDepth
|
||||
_context, // calculation: uint256(keccak256(abi.encode(_withdrawal, _params.scope)));
|
||||
_nullifierHash,
|
||||
_newCommitmentHash
|
||||
]
|
||||
});
|
||||
// Process withdrawal
|
||||
vm.prank(_caller);
|
||||
if (_params.revertReason != NONE) vm.expectRevert(_params.revertReason);
|
||||
if (_direct) {
|
||||
_pool.withdraw(_withdrawal, _proof);
|
||||
} else {
|
||||
_entrypoint.relay(_withdrawal, _proof);
|
||||
}
|
||||
|
||||
_insertIntoShadowMerkleTree(_commitment.hash);
|
||||
}
|
||||
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
RAGEQUIT
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
function _ragequit(address _depositor, Commitment memory _commitment) internal {
|
||||
IPrivacyPool _pool = _commitment.asset == _ETH ? _ethPool : _daiPool;
|
||||
|
||||
// Generate ragequit proof
|
||||
ProofLib.RagequitProof memory _ragequitProof =
|
||||
_generateRagequitProof(_commitment.value, _commitment.label, _commitment.nullifier, _commitment.secret);
|
||||
|
||||
// Initiate Ragequit
|
||||
vm.prank(_depositor);
|
||||
_pool.ragequit(_ragequitProof);
|
||||
}
|
||||
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
MERKLE TREE OPERATIONS
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
function _insertIntoShadowMerkleTree(uint256 _leaf) private {
|
||||
_shadowMerkleTree._insert(_leaf);
|
||||
_merkleLeaves.push(_leaf);
|
||||
}
|
||||
|
||||
function _insertIntoShadowASPMerkleTree(uint256 _leaf) private {
|
||||
_shadowASPMerkleTree._insert(_leaf);
|
||||
_aspLeaves.push(_leaf);
|
||||
}
|
||||
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
PROOF GENERATION
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
function _generateRagequitProof(
|
||||
uint256 _commitmentHash,
|
||||
uint256 _precommitmentHash,
|
||||
uint256 _nullifier,
|
||||
uint256 _value,
|
||||
uint256 _label
|
||||
) internal pure returns (ProofLib.RagequitProof memory _proof) {
|
||||
uint256 _nullifierHash = PoseidonT2.hash([_nullifier]);
|
||||
return ProofLib.RagequitProof({
|
||||
pA: [uint256(0), uint256(0)],
|
||||
pB: [[uint256(0), uint256(0)], [uint256(0), uint256(0)]],
|
||||
pC: [uint256(0), uint256(0)],
|
||||
pubSignals: [
|
||||
_commitmentHash, // pubSignals[0] is the commitmentHash
|
||||
_precommitmentHash, // pubSignals[1] is the precommitmentHash
|
||||
_nullifierHash, // pubSignals[2] is the nullifierHash
|
||||
_value, // pubSignals[3] is the value
|
||||
_label // pubSignals[4] is the label
|
||||
]
|
||||
});
|
||||
uint256 _label,
|
||||
uint256 _nullifier,
|
||||
uint256 _secret
|
||||
) private returns (ProofLib.RagequitProof memory _proof) {
|
||||
// Generate real proof using the helper script
|
||||
string[] memory _inputs = new string[](5);
|
||||
_inputs[0] = vm.toString(_value);
|
||||
_inputs[1] = vm.toString(_label);
|
||||
_inputs[2] = vm.toString(_nullifier);
|
||||
_inputs[3] = vm.toString(_secret);
|
||||
|
||||
// Call the ProofGenerator script using ts-node
|
||||
string[] memory _scriptArgs = new string[](2);
|
||||
_scriptArgs[0] = 'node';
|
||||
_scriptArgs[1] = 'test/helper/RagequitProofGenerator.mjs';
|
||||
bytes memory _proofData = vm.ffi(_concat(_scriptArgs, _inputs));
|
||||
|
||||
// Decode the ABI-encoded proof directly
|
||||
_proof = abi.decode(_proofData, (ProofLib.RagequitProof));
|
||||
}
|
||||
|
||||
function _insertIntoShadowMerkleTree(uint256 _leaf) internal {
|
||||
_shadowMerkleTree._insert(_leaf);
|
||||
function _generateWithdrawalProof(WithdrawalProofParams memory _params)
|
||||
private
|
||||
returns (ProofLib.WithdrawProof memory _proof)
|
||||
{
|
||||
bytes memory _stateMerkleProof = _generateMerkleProof(_merkleLeaves, _params.existingCommitment);
|
||||
bytes memory _aspMerkleProof = _generateMerkleProof(_aspLeaves, _params.label);
|
||||
|
||||
string[] memory _inputs = new string[](12);
|
||||
_inputs[0] = vm.toString(_params.existingValue);
|
||||
_inputs[1] = vm.toString(_params.label);
|
||||
_inputs[2] = vm.toString(_params.existingNullifier);
|
||||
_inputs[3] = vm.toString(_params.existingSecret);
|
||||
_inputs[4] = vm.toString(_params.newNullifier);
|
||||
_inputs[5] = vm.toString(_params.newSecret);
|
||||
_inputs[6] = vm.toString(_params.withdrawnValue);
|
||||
_inputs[7] = vm.toString(_params.context);
|
||||
_inputs[8] = vm.toString(_stateMerkleProof);
|
||||
_inputs[9] = vm.toString(_shadowMerkleTree.depth);
|
||||
_inputs[10] = vm.toString(_aspMerkleProof);
|
||||
_inputs[11] = vm.toString(_shadowASPMerkleTree.depth);
|
||||
|
||||
// Call the ProofGenerator script using node
|
||||
string[] memory _scriptArgs = new string[](2);
|
||||
_scriptArgs[0] = 'node';
|
||||
_scriptArgs[1] = 'test/helper/WithdrawalProofGenerator.mjs';
|
||||
bytes memory _proofData = vm.ffi(_concat(_scriptArgs, _inputs));
|
||||
|
||||
// Decode the ABI-encoded proof directly
|
||||
_proof = abi.decode(_proofData, (ProofLib.WithdrawProof));
|
||||
}
|
||||
|
||||
function _insertIntoShadowASPMerkleTree(uint256 _leaf) internal {
|
||||
_shadowASPMerkleTree._insert(_leaf);
|
||||
function _generateMerkleProof(uint256[] storage _leaves, uint256 _leaf) private returns (bytes memory _proof) {
|
||||
uint256 _leavesAmt = _leaves.length;
|
||||
string[] memory inputs = new string[](_leavesAmt + 1);
|
||||
inputs[0] = vm.toString(_leaf);
|
||||
|
||||
for (uint256 i = 0; i < _leavesAmt; i++) {
|
||||
inputs[i + 1] = vm.toString(_leaves[i]);
|
||||
}
|
||||
|
||||
// Call the ProofGenerator script using node
|
||||
string[] memory scriptArgs = new string[](2);
|
||||
scriptArgs[0] = 'node';
|
||||
scriptArgs[1] = 'test/helper/MerkleProofGenerator.mjs';
|
||||
_proof = vm.ffi(_concat(scriptArgs, inputs));
|
||||
}
|
||||
|
||||
/*///////////////////////////////////////////////////////////////
|
||||
UTILS
|
||||
//////////////////////////////////////////////////////////////*/
|
||||
|
||||
function _concat(string[] memory _arr1, string[] memory _arr2) internal pure returns (string[] memory) {
|
||||
string[] memory returnArr = new string[](_arr1.length + _arr2.length);
|
||||
uint256 i;
|
||||
for (; i < _arr1.length;) {
|
||||
returnArr[i] = _arr1[i];
|
||||
unchecked {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
uint256 j;
|
||||
for (; j < _arr2.length;) {
|
||||
returnArr[i + j] = _arr2[j];
|
||||
unchecked {
|
||||
++j;
|
||||
}
|
||||
}
|
||||
return returnArr;
|
||||
}
|
||||
|
||||
function _deal(address _account, IERC20 _asset, uint256 _amount) private {
|
||||
if (_asset == _ETH) {
|
||||
deal(_account, _amount);
|
||||
} else {
|
||||
deal(address(_asset), _account, _amount);
|
||||
}
|
||||
}
|
||||
|
||||
function _balance(address _account, IERC20 _asset) private view returns (uint256 _bal) {
|
||||
if (_asset == _ETH) {
|
||||
_bal = _account.balance;
|
||||
} else {
|
||||
_bal = _asset.balanceOf(_account);
|
||||
}
|
||||
}
|
||||
|
||||
function _deductFee(uint256 _amount, uint256 _feeBps) private pure returns (uint256 _amountAfterFee) {
|
||||
_amountAfterFee = _amount - (_amount * _feeBps) / 10_000;
|
||||
}
|
||||
|
||||
function _hashNullifier(uint256 _nullifier) private pure returns (uint256 _nullifierHash) {
|
||||
_nullifierHash = PoseidonT2.hash([_nullifier]);
|
||||
}
|
||||
|
||||
function _hashPrecommitment(uint256 _nullifier, uint256 _secret) private pure returns (uint256 _precommitment) {
|
||||
_precommitment = PoseidonT3.hash([_nullifier, _secret]);
|
||||
}
|
||||
|
||||
function _hashCommitment(
|
||||
uint256 _amount,
|
||||
uint256 _label,
|
||||
uint256 _precommitment
|
||||
) private pure returns (uint256 _commitmentHash) {
|
||||
_commitmentHash = PoseidonT4.hash([_amount, _label, _precommitment]);
|
||||
}
|
||||
|
||||
function _genSecretBySeed(string memory _seed) private pure returns (uint256 _secret) {
|
||||
_secret = uint256(keccak256(bytes(_seed))) % Constants.SNARK_SCALAR_FIELD;
|
||||
}
|
||||
}
|
||||
|
||||
291
packages/contracts/test/integration/IntegrationERC20.t.sol
Normal file
291
packages/contracts/test/integration/IntegrationERC20.t.sol
Normal file
@@ -0,0 +1,291 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.28;
|
||||
|
||||
import {IntegrationBase} from './IntegrationBase.sol';
|
||||
import {InternalLeanIMT, LeanIMTData} from 'lean-imt/InternalLeanIMT.sol';
|
||||
|
||||
import {IPrivacyPool} from 'interfaces/IPrivacyPool.sol';
|
||||
|
||||
contract IntegrationERC20 is IntegrationBase {
|
||||
using InternalLeanIMT for LeanIMTData;
|
||||
|
||||
Commitment internal _commitment;
|
||||
|
||||
function test_fullDirectWithdrawal() public {
|
||||
// Alice deposits 5000 DAI
|
||||
_commitment = _deposit(
|
||||
DepositParams({depositor: _ALICE, asset: _DAI, amount: 5000 ether, nullifier: 'nullifier_1', secret: 'secret_1'})
|
||||
);
|
||||
|
||||
// Push ASP root with label included
|
||||
vm.prank(_POSTMAN);
|
||||
_entrypoint.updateRoot(_shadowASPMerkleTree._root(), bytes32('IPFS_HASH'));
|
||||
|
||||
// Bob withdraws the total amount of Alice's commitment
|
||||
_selfWithdraw(
|
||||
WithdrawalParams({
|
||||
withdrawnAmount: _commitment.value,
|
||||
newNullifier: 'nullifier_2',
|
||||
newSecret: 'secret_2',
|
||||
recipient: _BOB,
|
||||
commitment: _commitment,
|
||||
revertReason: NONE
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function test_fullRelayedWithdrawal() public {
|
||||
// Alice deposits 5000 DAI
|
||||
_commitment = _deposit(
|
||||
DepositParams({depositor: _ALICE, asset: _DAI, amount: 5000 ether, nullifier: 'nullifier_1', secret: 'secret_1'})
|
||||
);
|
||||
|
||||
// Push ASP root with label included
|
||||
vm.prank(_POSTMAN);
|
||||
_entrypoint.updateRoot(_shadowASPMerkleTree._root(), bytes32('IPFS_HASH'));
|
||||
|
||||
// Bob receives the total amount of Alice's commitment
|
||||
_withdrawThroughRelayer(
|
||||
WithdrawalParams({
|
||||
withdrawnAmount: _commitment.value,
|
||||
newNullifier: 'nullifier_2',
|
||||
newSecret: 'secret_2',
|
||||
recipient: _BOB,
|
||||
commitment: _commitment,
|
||||
revertReason: NONE
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function test_partialDirectWithdrawal() public {
|
||||
// Alice deposits 5000 DAI
|
||||
_commitment = _deposit(
|
||||
DepositParams({depositor: _ALICE, asset: _DAI, amount: 5000 ether, nullifier: 'nullifier_1', secret: 'secret_1'})
|
||||
);
|
||||
|
||||
// Push ASP root with label included
|
||||
vm.prank(_POSTMAN);
|
||||
_entrypoint.updateRoot(_shadowASPMerkleTree._root(), bytes32('IPFS_HASH'));
|
||||
|
||||
// Bob withdraws 2000 DAI of Alice's commitment
|
||||
_selfWithdraw(
|
||||
WithdrawalParams({
|
||||
withdrawnAmount: 2000 ether,
|
||||
newNullifier: 'nullifier_2',
|
||||
newSecret: 'secret_2',
|
||||
recipient: _BOB,
|
||||
commitment: _commitment,
|
||||
revertReason: NONE
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function test_multiplePartialDirectWithdrawals() public {
|
||||
// Alice deposits 5000 DAI
|
||||
_commitment = _deposit(
|
||||
DepositParams({depositor: _ALICE, asset: _DAI, amount: 5000 ether, nullifier: 'nullifier_1', secret: 'secret_1'})
|
||||
);
|
||||
|
||||
// Push ASP root with label included
|
||||
vm.prank(_POSTMAN);
|
||||
_entrypoint.updateRoot(_shadowASPMerkleTree._root(), bytes32('IPFS_HASH'));
|
||||
|
||||
// Withdraw 2000 DAI to Bob
|
||||
_commitment = _selfWithdraw(
|
||||
WithdrawalParams({
|
||||
withdrawnAmount: 2000 ether,
|
||||
newNullifier: 'nullifier_2',
|
||||
newSecret: 'secret_2',
|
||||
recipient: _BOB,
|
||||
commitment: _commitment,
|
||||
revertReason: NONE
|
||||
})
|
||||
);
|
||||
|
||||
// Withdraw 2000 DAI to Bob
|
||||
_commitment = _selfWithdraw(
|
||||
WithdrawalParams({
|
||||
withdrawnAmount: 2000 ether,
|
||||
newNullifier: 'nullifier_3',
|
||||
newSecret: 'secret_3',
|
||||
recipient: _BOB,
|
||||
commitment: _commitment,
|
||||
revertReason: NONE
|
||||
})
|
||||
);
|
||||
|
||||
// Withdraw 500 DAI to Bob
|
||||
_commitment = _selfWithdraw(
|
||||
WithdrawalParams({
|
||||
withdrawnAmount: 500 ether,
|
||||
newNullifier: 'nullifier_4',
|
||||
newSecret: 'secret_4',
|
||||
recipient: _BOB,
|
||||
commitment: _commitment,
|
||||
revertReason: NONE
|
||||
})
|
||||
);
|
||||
|
||||
// Withdraw remaining balance to Bob
|
||||
_commitment = _selfWithdraw(
|
||||
WithdrawalParams({
|
||||
withdrawnAmount: _commitment.value,
|
||||
newNullifier: 'nullifier_5',
|
||||
newSecret: 'secret_5',
|
||||
recipient: _BOB,
|
||||
commitment: _commitment,
|
||||
revertReason: NONE
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function test_partialRelayedWithdrawal() public {
|
||||
// Alice deposits 5000 DAI
|
||||
_commitment = _deposit(
|
||||
DepositParams({depositor: _ALICE, asset: _DAI, amount: 5000 ether, nullifier: 'nullifier_1', secret: 'secret_1'})
|
||||
);
|
||||
|
||||
// Push ASP root with label included
|
||||
vm.prank(_POSTMAN);
|
||||
_entrypoint.updateRoot(_shadowASPMerkleTree._root(), bytes32('IPFS_HASH'));
|
||||
|
||||
// Bob receives half of Alice's commitment
|
||||
_withdrawThroughRelayer(
|
||||
WithdrawalParams({
|
||||
withdrawnAmount: 2500 ether,
|
||||
newNullifier: 'nullifier_2',
|
||||
newSecret: 'secret_2',
|
||||
recipient: _BOB,
|
||||
commitment: _commitment,
|
||||
revertReason: NONE
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function test_multiplePartialRelayedWithdrawals() public {
|
||||
// Alice deposits 5000 DAI
|
||||
_commitment = _deposit(
|
||||
DepositParams({depositor: _ALICE, asset: _DAI, amount: 5000 ether, nullifier: 'nullifier_1', secret: 'secret_1'})
|
||||
);
|
||||
|
||||
// Push ASP root with label included
|
||||
vm.prank(_POSTMAN);
|
||||
_entrypoint.updateRoot(_shadowASPMerkleTree._root(), bytes32('IPFS_HASH'));
|
||||
|
||||
// Withdraw 2000 DAI to Bob
|
||||
_commitment = _withdrawThroughRelayer(
|
||||
WithdrawalParams({
|
||||
withdrawnAmount: 2000 ether,
|
||||
newNullifier: 'nullifier_2',
|
||||
newSecret: 'secret_2',
|
||||
recipient: _BOB,
|
||||
commitment: _commitment,
|
||||
revertReason: NONE
|
||||
})
|
||||
);
|
||||
|
||||
// Withdraw 2000 DAI to Bob
|
||||
_commitment = _withdrawThroughRelayer(
|
||||
WithdrawalParams({
|
||||
withdrawnAmount: 2000 ether,
|
||||
newNullifier: 'nullifier_3',
|
||||
newSecret: 'secret_3',
|
||||
recipient: _BOB,
|
||||
commitment: _commitment,
|
||||
revertReason: NONE
|
||||
})
|
||||
);
|
||||
|
||||
// Withdraw 500 DAI to Bob
|
||||
_commitment = _withdrawThroughRelayer(
|
||||
WithdrawalParams({
|
||||
withdrawnAmount: 500 ether,
|
||||
newNullifier: 'nullifier_4',
|
||||
newSecret: 'secret_4',
|
||||
recipient: _BOB,
|
||||
commitment: _commitment,
|
||||
revertReason: NONE
|
||||
})
|
||||
);
|
||||
|
||||
// Withdraw remaining balance to Bob
|
||||
_commitment = _withdrawThroughRelayer(
|
||||
WithdrawalParams({
|
||||
withdrawnAmount: _commitment.value,
|
||||
newNullifier: 'nullifier_5',
|
||||
newSecret: 'secret_5',
|
||||
recipient: _BOB,
|
||||
commitment: _commitment,
|
||||
revertReason: NONE
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function test_ragequit() public {
|
||||
// Alice deposits 5000 DAI
|
||||
_commitment = _deposit(
|
||||
DepositParams({depositor: _ALICE, asset: _DAI, amount: 5000 ether, nullifier: 'nullifier_1', secret: 'secret_1'})
|
||||
);
|
||||
|
||||
// Push ASP root without label
|
||||
vm.prank(_POSTMAN);
|
||||
_entrypoint.updateRoot(uint256(keccak256('some_root')) % SNARK_SCALAR_FIELD, bytes32('IPFS_HASH'));
|
||||
|
||||
// Fail to withdraw
|
||||
_withdrawThroughRelayer(
|
||||
WithdrawalParams({
|
||||
withdrawnAmount: 3000 ether,
|
||||
newNullifier: 'nullifier_2',
|
||||
newSecret: 'secret_2',
|
||||
recipient: _BOB,
|
||||
commitment: _commitment,
|
||||
revertReason: IPrivacyPool.IncorrectASPRoot.selector
|
||||
})
|
||||
);
|
||||
|
||||
// Ragequit full amount
|
||||
_ragequit(_ALICE, _commitment);
|
||||
}
|
||||
|
||||
function test_aspRemoval() public {
|
||||
// Alice deposits 5000 DAI
|
||||
_commitment = _deposit(
|
||||
DepositParams({depositor: _ALICE, asset: _DAI, amount: 5000 ether, nullifier: 'nullifier_1', secret: 'secret_1'})
|
||||
);
|
||||
|
||||
// Push ASP root with label included
|
||||
vm.prank(_POSTMAN);
|
||||
_entrypoint.updateRoot(_shadowASPMerkleTree._root(), bytes32('IPFS_HASH'));
|
||||
|
||||
// Withdraw 4000 DAI through relayer
|
||||
_commitment = _withdrawThroughRelayer(
|
||||
WithdrawalParams({
|
||||
withdrawnAmount: 4000 ether,
|
||||
newNullifier: 'nullifier_2',
|
||||
newSecret: 'secret_2',
|
||||
recipient: _BOB,
|
||||
commitment: _commitment,
|
||||
revertReason: NONE
|
||||
})
|
||||
);
|
||||
|
||||
// Remove label from ASP
|
||||
vm.prank(_POSTMAN);
|
||||
_entrypoint.updateRoot(uint256(keccak256('some_root')) % SNARK_SCALAR_FIELD, bytes32('IPFS_HASH'));
|
||||
|
||||
// Fail to withdraw
|
||||
_withdrawThroughRelayer(
|
||||
WithdrawalParams({
|
||||
withdrawnAmount: 500 ether,
|
||||
newNullifier: 'nullifier_2',
|
||||
newSecret: 'secret_2',
|
||||
recipient: _BOB,
|
||||
commitment: _commitment,
|
||||
revertReason: IPrivacyPool.IncorrectASPRoot.selector
|
||||
})
|
||||
);
|
||||
|
||||
// Ragequit
|
||||
_ragequit(_ALICE, _commitment);
|
||||
}
|
||||
}
|
||||
315
packages/contracts/test/integration/IntegrationETH.t.sol
Normal file
315
packages/contracts/test/integration/IntegrationETH.t.sol
Normal file
@@ -0,0 +1,315 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.28;
|
||||
|
||||
import {IntegrationBase} from './IntegrationBase.sol';
|
||||
import {InternalLeanIMT, LeanIMTData} from 'lean-imt/InternalLeanIMT.sol';
|
||||
|
||||
import {IPrivacyPool} from 'interfaces/IPrivacyPool.sol';
|
||||
|
||||
contract IntegrationETH is IntegrationBase {
|
||||
using InternalLeanIMT for LeanIMTData;
|
||||
|
||||
Commitment internal _commitment;
|
||||
|
||||
function test_fullDirectWithdrawal() public {
|
||||
// Alice deposits 100 ETH
|
||||
_commitment = _deposit(
|
||||
DepositParams({depositor: _ALICE, asset: _ETH, amount: 100 ether, nullifier: 'nullifier_1', secret: 'secret_1'})
|
||||
);
|
||||
|
||||
// Push ASP root with label included
|
||||
vm.prank(_POSTMAN);
|
||||
_entrypoint.updateRoot(_shadowASPMerkleTree._root(), bytes32('IPFS_HASH'));
|
||||
|
||||
// Bob withdraws the total amount of Alice's commitment
|
||||
_selfWithdraw(
|
||||
WithdrawalParams({
|
||||
withdrawnAmount: _commitment.value,
|
||||
newNullifier: 'nullifier_2',
|
||||
newSecret: 'secret_2',
|
||||
recipient: _BOB,
|
||||
commitment: _commitment,
|
||||
revertReason: NONE
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function test_fullRelayedWithdrawal() public {
|
||||
// Alice deposits 100 ETH
|
||||
_commitment = _deposit(
|
||||
DepositParams({depositor: _ALICE, asset: _ETH, amount: 100 ether, nullifier: 'nullifier_1', secret: 'secret_1'})
|
||||
);
|
||||
|
||||
// Push ASP root with label included
|
||||
vm.prank(_POSTMAN);
|
||||
_entrypoint.updateRoot(_shadowASPMerkleTree._root(), bytes32('IPFS_HASH'));
|
||||
|
||||
// Bob receives the total amount of Alice's commitment
|
||||
_withdrawThroughRelayer(
|
||||
WithdrawalParams({
|
||||
withdrawnAmount: _commitment.value,
|
||||
newNullifier: 'nullifier_2',
|
||||
newSecret: 'secret_2',
|
||||
recipient: _BOB,
|
||||
commitment: _commitment,
|
||||
revertReason: NONE
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function test_partialDirectWithdrawal() public {
|
||||
// Alice deposits 100 ETH
|
||||
_commitment = _deposit(
|
||||
DepositParams({depositor: _ALICE, asset: _ETH, amount: 100 ether, nullifier: 'nullifier_1', secret: 'secret_1'})
|
||||
);
|
||||
|
||||
// Push ASP root with label included
|
||||
vm.prank(_POSTMAN);
|
||||
_entrypoint.updateRoot(_shadowASPMerkleTree._root(), bytes32('IPFS_HASH'));
|
||||
|
||||
// Bob withdraws the total amount of Alice's commitment
|
||||
_selfWithdraw(
|
||||
WithdrawalParams({
|
||||
withdrawnAmount: 20 ether,
|
||||
newNullifier: 'nullifier_2',
|
||||
newSecret: 'secret_2',
|
||||
recipient: _BOB,
|
||||
commitment: _commitment,
|
||||
revertReason: NONE
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function test_multiplePartialDirectWithdrawals() public {
|
||||
// Alice deposits 100 ETH
|
||||
_commitment = _deposit(
|
||||
DepositParams({depositor: _ALICE, asset: _ETH, amount: 100 ether, nullifier: 'nullifier_1', secret: 'secret_1'})
|
||||
);
|
||||
|
||||
// Push ASP root with label included
|
||||
vm.prank(_POSTMAN);
|
||||
_entrypoint.updateRoot(_shadowASPMerkleTree._root(), bytes32('IPFS_HASH'));
|
||||
|
||||
// Withdraw 20 ETH to Bob
|
||||
_commitment = _selfWithdraw(
|
||||
WithdrawalParams({
|
||||
withdrawnAmount: 20 ether,
|
||||
newNullifier: 'nullifier_2',
|
||||
newSecret: 'secret_2',
|
||||
recipient: _BOB,
|
||||
commitment: _commitment,
|
||||
revertReason: NONE
|
||||
})
|
||||
);
|
||||
|
||||
// Withdraw 20 ETH to Bob
|
||||
_commitment = _selfWithdraw(
|
||||
WithdrawalParams({
|
||||
withdrawnAmount: 20 ether,
|
||||
newNullifier: 'nullifier_3',
|
||||
newSecret: 'secret_3',
|
||||
recipient: _BOB,
|
||||
commitment: _commitment,
|
||||
revertReason: NONE
|
||||
})
|
||||
);
|
||||
|
||||
// Withdraw 20 ETH to Bob
|
||||
_commitment = _selfWithdraw(
|
||||
WithdrawalParams({
|
||||
withdrawnAmount: 20 ether,
|
||||
newNullifier: 'nullifier_4',
|
||||
newSecret: 'secret_4',
|
||||
recipient: _BOB,
|
||||
commitment: _commitment,
|
||||
revertReason: NONE
|
||||
})
|
||||
);
|
||||
|
||||
// Withdraw 20 ETH to Bob
|
||||
_commitment = _selfWithdraw(
|
||||
WithdrawalParams({
|
||||
withdrawnAmount: 20 ether,
|
||||
newNullifier: 'nullifier_5',
|
||||
newSecret: 'secret_5',
|
||||
recipient: _BOB,
|
||||
commitment: _commitment,
|
||||
revertReason: NONE
|
||||
})
|
||||
);
|
||||
|
||||
// Withdraw remaining balance to Bob
|
||||
_commitment = _selfWithdraw(
|
||||
WithdrawalParams({
|
||||
withdrawnAmount: _commitment.value,
|
||||
newNullifier: 'nullifier_6',
|
||||
newSecret: 'secret_6',
|
||||
recipient: _BOB,
|
||||
commitment: _commitment,
|
||||
revertReason: NONE
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function test_partialRelayedWithdrawal() public {
|
||||
// Alice deposits 100 ETH
|
||||
_commitment = _deposit(
|
||||
DepositParams({depositor: _ALICE, asset: _ETH, amount: 100 ether, nullifier: 'nullifier_1', secret: 'secret_1'})
|
||||
);
|
||||
|
||||
// Push ASP root with label included
|
||||
vm.prank(_POSTMAN);
|
||||
_entrypoint.updateRoot(_shadowASPMerkleTree._root(), bytes32('IPFS_HASH'));
|
||||
|
||||
// Bob receives the total amount of Alice's commitment
|
||||
_withdrawThroughRelayer(
|
||||
WithdrawalParams({
|
||||
withdrawnAmount: 40 ether,
|
||||
newNullifier: 'nullifier_2',
|
||||
newSecret: 'secret_2',
|
||||
recipient: _BOB,
|
||||
commitment: _commitment,
|
||||
revertReason: NONE
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function test_multiplePartialRelayedWithdrawals() public {
|
||||
// Alice deposits 100 ETH
|
||||
_commitment = _deposit(
|
||||
DepositParams({depositor: _ALICE, asset: _ETH, amount: 100 ether, nullifier: 'nullifier_1', secret: 'secret_1'})
|
||||
);
|
||||
|
||||
// Push ASP root with label included
|
||||
vm.prank(_POSTMAN);
|
||||
_entrypoint.updateRoot(_shadowASPMerkleTree._root(), bytes32('IPFS_HASH'));
|
||||
|
||||
// Withdraw 20 ETH to Bob
|
||||
_commitment = _withdrawThroughRelayer(
|
||||
WithdrawalParams({
|
||||
withdrawnAmount: 20 ether,
|
||||
newNullifier: 'nullifier_2',
|
||||
newSecret: 'secret_2',
|
||||
recipient: _BOB,
|
||||
commitment: _commitment,
|
||||
revertReason: NONE
|
||||
})
|
||||
);
|
||||
|
||||
// Withdraw 20 ETH to Bob
|
||||
_commitment = _withdrawThroughRelayer(
|
||||
WithdrawalParams({
|
||||
withdrawnAmount: 20 ether,
|
||||
newNullifier: 'nullifier_3',
|
||||
newSecret: 'secret_3',
|
||||
recipient: _BOB,
|
||||
commitment: _commitment,
|
||||
revertReason: NONE
|
||||
})
|
||||
);
|
||||
|
||||
// Withdraw 20 ETH to Bob
|
||||
_commitment = _withdrawThroughRelayer(
|
||||
WithdrawalParams({
|
||||
withdrawnAmount: 20 ether,
|
||||
newNullifier: 'nullifier_4',
|
||||
newSecret: 'secret_4',
|
||||
recipient: _BOB,
|
||||
commitment: _commitment,
|
||||
revertReason: NONE
|
||||
})
|
||||
);
|
||||
|
||||
// Withdraw 20 ETH to Bob
|
||||
_commitment = _withdrawThroughRelayer(
|
||||
WithdrawalParams({
|
||||
withdrawnAmount: 20 ether,
|
||||
newNullifier: 'nullifier_5',
|
||||
newSecret: 'secret_5',
|
||||
recipient: _BOB,
|
||||
commitment: _commitment,
|
||||
revertReason: NONE
|
||||
})
|
||||
);
|
||||
|
||||
// Withdraw remaining balance to Bob
|
||||
_commitment = _withdrawThroughRelayer(
|
||||
WithdrawalParams({
|
||||
withdrawnAmount: _commitment.value,
|
||||
newNullifier: 'nullifier_6',
|
||||
newSecret: 'secret_6',
|
||||
recipient: _BOB,
|
||||
commitment: _commitment,
|
||||
revertReason: NONE
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function test_ragequit() public {
|
||||
// Alice deposits 100 ETH
|
||||
_commitment = _deposit(
|
||||
DepositParams({depositor: _ALICE, asset: _ETH, amount: 100 ether, nullifier: 'nullifier_1', secret: 'secret_1'})
|
||||
);
|
||||
|
||||
// Push ASP root without label
|
||||
vm.prank(_POSTMAN);
|
||||
_entrypoint.updateRoot(uint256(keccak256('some_root')) % SNARK_SCALAR_FIELD, bytes32('IPFS_HASH'));
|
||||
|
||||
// Fail to withdraw
|
||||
_withdrawThroughRelayer(
|
||||
WithdrawalParams({
|
||||
withdrawnAmount: 40 ether,
|
||||
newNullifier: 'nullifier_2',
|
||||
newSecret: 'secret_2',
|
||||
recipient: _BOB,
|
||||
commitment: _commitment,
|
||||
revertReason: IPrivacyPool.IncorrectASPRoot.selector
|
||||
})
|
||||
);
|
||||
|
||||
// Ragequit full amount
|
||||
_ragequit(_ALICE, _commitment);
|
||||
}
|
||||
|
||||
function test_aspRemoval() public {
|
||||
// Alice deposits 100 ETH
|
||||
_commitment = _deposit(
|
||||
DepositParams({depositor: _ALICE, asset: _ETH, amount: 100 ether, nullifier: 'nullifier_1', secret: 'secret_1'})
|
||||
);
|
||||
|
||||
// Push ASP root with label included
|
||||
vm.prank(_POSTMAN);
|
||||
_entrypoint.updateRoot(_shadowASPMerkleTree._root(), bytes32('IPFS_HASH'));
|
||||
|
||||
// Withdraw 40 ETH through relayer
|
||||
_commitment = _withdrawThroughRelayer(
|
||||
WithdrawalParams({
|
||||
withdrawnAmount: 40 ether,
|
||||
newNullifier: 'nullifier_2',
|
||||
newSecret: 'secret_2',
|
||||
recipient: _BOB,
|
||||
commitment: _commitment,
|
||||
revertReason: NONE
|
||||
})
|
||||
);
|
||||
|
||||
// Remove label from ASP
|
||||
vm.prank(_POSTMAN);
|
||||
_entrypoint.updateRoot(uint256(keccak256('some_root')) % SNARK_SCALAR_FIELD, bytes32('IPFS_HASH'));
|
||||
|
||||
// Fail to withdraw
|
||||
_withdrawThroughRelayer(
|
||||
WithdrawalParams({
|
||||
withdrawnAmount: 40 ether,
|
||||
newNullifier: 'nullifier_2',
|
||||
newSecret: 'secret_2',
|
||||
recipient: _BOB,
|
||||
commitment: _commitment,
|
||||
revertReason: IPrivacyPool.IncorrectASPRoot.selector
|
||||
})
|
||||
);
|
||||
|
||||
// Ragequit
|
||||
_ragequit(_ALICE, _commitment);
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ contract PrivacyPoolERC20ForTest {
|
||||
address internal _asset;
|
||||
|
||||
function withdraw(IPrivacyPool.Withdrawal calldata, ProofLib.WithdrawProof calldata _proof) external {
|
||||
uint256 _amount = _proof.pubSignals[0];
|
||||
uint256 _amount = _proof.pubSignals[2];
|
||||
IERC20(_asset).transfer(msg.sender, _amount);
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ contract PrivacyPoolETHForTest {
|
||||
error ETHTransferFailed();
|
||||
|
||||
function withdraw(IPrivacyPool.Withdrawal calldata, ProofLib.WithdrawProof calldata _proof) external {
|
||||
uint256 _amount = _proof.pubSignals[0];
|
||||
uint256 _amount = _proof.pubSignals[2];
|
||||
(bool success,) = msg.sender.call{value: _amount}('');
|
||||
if (!success) revert ETHTransferFailed();
|
||||
}
|
||||
@@ -441,7 +441,7 @@ contract UnitRelay is UnitEntrypoint {
|
||||
// Configure withdrawal parameters within valid bounds
|
||||
_params.feeBPS = bound(_params.feeBPS, 0, 10_000);
|
||||
_params.amount = bound(_params.amount, 1, 1e30);
|
||||
_proof.pubSignals[0] = _params.amount;
|
||||
_proof.pubSignals[2] = _params.amount;
|
||||
|
||||
// Construct withdrawal data with fee distribution details
|
||||
bytes memory _data = abi.encode(
|
||||
@@ -530,7 +530,7 @@ contract UnitRelay is UnitEntrypoint {
|
||||
// Set up withdrawal parameters within valid bounds
|
||||
_params.feeBPS = bound(_params.feeBPS, 0, 10_000);
|
||||
_params.amount = bound(_params.amount, 1, 1e30);
|
||||
_proof.pubSignals[0] = _params.amount;
|
||||
_proof.pubSignals[2] = _params.amount;
|
||||
|
||||
// Construct withdrawal data with fee distribution
|
||||
bytes memory _data = abi.encode(
|
||||
@@ -604,7 +604,7 @@ contract UnitRelay is UnitEntrypoint {
|
||||
// Set up withdrawal parameters within valid bounds
|
||||
_params.feeBPS = bound(_params.feeBPS, 0, 10_000);
|
||||
_params.amount = bound(_params.amount, 1, 1e30);
|
||||
_proof.pubSignals[0] = _params.amount;
|
||||
_proof.pubSignals[2] = _params.amount;
|
||||
|
||||
// Construct withdrawal data with fee distribution
|
||||
bytes memory _data = abi.encode(
|
||||
@@ -636,7 +636,7 @@ contract UnitRelay is UnitEntrypoint {
|
||||
ProofLib.WithdrawProof memory _proof
|
||||
) external {
|
||||
// Set withdrawal amount to zero
|
||||
_proof.pubSignals[0] = 0;
|
||||
_proof.pubSignals[2] = 0;
|
||||
_withdrawal.processooor = address(_entrypoint);
|
||||
|
||||
// Expect revert due to invalid withdrawal amount
|
||||
@@ -654,7 +654,7 @@ contract UnitRelay is UnitEntrypoint {
|
||||
ProofLib.WithdrawProof memory _proof
|
||||
) external {
|
||||
// Ensure non-zero withdrawal amount
|
||||
vm.assume(_proof.pubSignals[0] != 0);
|
||||
vm.assume(_proof.pubSignals[2] != 0);
|
||||
_withdrawal.processooor = address(_entrypoint);
|
||||
|
||||
// Expect revert due to pool not found
|
||||
@@ -680,7 +680,7 @@ contract UnitRelay is UnitEntrypoint {
|
||||
// Configure withdrawal parameters
|
||||
_params.feeBPS = bound(_params.feeBPS, 0, 10_000);
|
||||
_params.amount = bound(_params.amount, 1, 1e30);
|
||||
_proof.pubSignals[0] = _params.amount;
|
||||
_proof.pubSignals[2] = _params.amount;
|
||||
|
||||
// Construct withdrawal data with invalid processooor
|
||||
bytes memory _data = abi.encode(
|
||||
|
||||
@@ -109,18 +109,19 @@ contract UnitPrivacyPool is Test {
|
||||
}
|
||||
|
||||
modifier givenValidProof(IPrivacyPool.Withdrawal memory _w, ProofLib.WithdrawProof memory _p) {
|
||||
_p.pubSignals[0] = bound(_p.pubSignals[0], 1, type(uint256).max);
|
||||
_p.pubSignals[1] = bound(_p.pubSignals[1], 1, type(uint256).max);
|
||||
_p.pubSignals[3] = bound(_p.pubSignals[2], 1, type(uint256).max);
|
||||
_p.pubSignals[6] = bound(_p.pubSignals[6], 1, type(uint256).max);
|
||||
_p.pubSignals[7] = bound(_p.pubSignals[7], 1, Constants.SNARK_SCALAR_FIELD - 1);
|
||||
_p.pubSignals[2] = bound(_p.pubSignals[2], 1, type(uint256).max) % Constants.SNARK_SCALAR_FIELD;
|
||||
_p.pubSignals[3] = bound(_p.pubSignals[3], 1, type(uint256).max) % Constants.SNARK_SCALAR_FIELD;
|
||||
_p.pubSignals[5] = bound(_p.pubSignals[5], 1, type(uint256).max) % Constants.SNARK_SCALAR_FIELD;
|
||||
_p.pubSignals[1] = bound(_p.pubSignals[6], 1, type(uint256).max) % Constants.SNARK_SCALAR_FIELD;
|
||||
_p.pubSignals[0] = bound(_p.pubSignals[7], 1, Constants.SNARK_SCALAR_FIELD - 1);
|
||||
|
||||
_p.pubSignals[5] = uint256(keccak256(abi.encode(_w, _scope)));
|
||||
_p.pubSignals[7] = uint256(keccak256(abi.encode(_w, _scope))) % Constants.SNARK_SCALAR_FIELD;
|
||||
|
||||
_;
|
||||
}
|
||||
|
||||
modifier givenKnownStateRoot(uint256 _stateRoot) {
|
||||
vm.assume(_stateRoot != 0);
|
||||
_pool.mockKnownStateRoot(_stateRoot);
|
||||
_;
|
||||
}
|
||||
@@ -162,7 +163,7 @@ contract UnitPrivacyPool is Test {
|
||||
|
||||
function setUp() public {
|
||||
_pool = new PoolForTest(_ENTRYPOINT, _WITHDRAWAL_VERIFIER, _RAGEQUIT_VERIFIER, _ASSET);
|
||||
_scope = uint256(keccak256(abi.encodePacked(address(_pool), block.chainid, _ASSET)));
|
||||
_scope = uint256(keccak256(abi.encodePacked(address(_pool), block.chainid, _ASSET))) % Constants.SNARK_SCALAR_FIELD;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////
|
||||
@@ -206,7 +207,7 @@ contract UnitConstructor is UnitPrivacyPool {
|
||||
|
||||
// Deploy new pool and compute its scope
|
||||
_pool = new PoolForTest(_entrypoint, _withdrawalVerifier, _ragequitVerifier, _asset);
|
||||
_scope = uint256(keccak256(abi.encodePacked(address(_pool), block.chainid, _asset)));
|
||||
_scope = uint256(keccak256(abi.encodePacked(address(_pool), block.chainid, _asset))) % Constants.SNARK_SCALAR_FIELD;
|
||||
|
||||
// Verify all constructor parameters are set correctly
|
||||
assertEq(address(_pool.ENTRYPOINT()), _entrypoint, 'Entrypoint address should match constructor input');
|
||||
@@ -263,7 +264,7 @@ contract UnitDeposit is UnitPrivacyPool {
|
||||
|
||||
// Calculate expected values for deposit
|
||||
uint256 _nonce = _pool.nonce();
|
||||
uint256 _label = uint256(keccak256(abi.encodePacked(_scope, _nonce + 1)));
|
||||
uint256 _label = uint256(keccak256(abi.encodePacked(_scope, _nonce + 1))) % Constants.SNARK_SCALAR_FIELD;
|
||||
uint256 _commitment = PoseidonT4.hash([_amount, _label, _precommitmentHash]);
|
||||
uint256 _newRoot = _pool.insertLeafInShadowTree(_commitment);
|
||||
|
||||
@@ -418,15 +419,17 @@ contract UnitWithdraw is UnitPrivacyPool {
|
||||
{
|
||||
_mockAndExpect(
|
||||
_WITHDRAWAL_VERIFIER,
|
||||
abi.encodeWithSignature('verifyProof((uint256[2],uint256[2][2],uint256[2],uint256[8]))', _p),
|
||||
abi.encodeWithSignature(
|
||||
'verifyProof(uint256[2],uint256[2][2],uint256[2],uint256[8])', _p.pA, _p.pB, _p.pC, _p.pubSignals
|
||||
),
|
||||
abi.encode(true)
|
||||
);
|
||||
|
||||
vm.expectEmit(address(_pool));
|
||||
emit PoolForTest.Pushed(_w.processooor, _p.pubSignals[0]);
|
||||
emit PoolForTest.Pushed(_w.processooor, _p.pubSignals[2]);
|
||||
|
||||
vm.expectEmit(address(_pool));
|
||||
emit IPrivacyPool.Withdrawn(_w.processooor, _p.pubSignals[0], _p.pubSignals[6], _p.pubSignals[7]);
|
||||
emit IPrivacyPool.Withdrawn(_w.processooor, _p.pubSignals[2], _p.pubSignals[1], _p.pubSignals[0]);
|
||||
|
||||
_pool.withdraw(_w, _p);
|
||||
|
||||
@@ -445,7 +448,9 @@ contract UnitWithdraw is UnitPrivacyPool {
|
||||
{
|
||||
_mockAndExpect(
|
||||
_WITHDRAWAL_VERIFIER,
|
||||
abi.encodeWithSignature('verifyProof((uint256[2],uint256[2][2],uint256[2],uint256[8]))', _p),
|
||||
abi.encodeWithSignature(
|
||||
'verifyProof(uint256[2],uint256[2][2],uint256[2],uint256[8])', _p.pA, _p.pB, _p.pC, _p.pubSignals
|
||||
),
|
||||
abi.encode(true)
|
||||
);
|
||||
_pool.mockNullifierStatus(_p.existingNullifierHash(), true);
|
||||
@@ -498,7 +503,7 @@ contract UnitWithdraw is UnitPrivacyPool {
|
||||
) external givenCallerIsProcessooor(_w.processooor) givenValidProof(_w, _p) {
|
||||
// Setup proof with mismatched context
|
||||
vm.assume(_unknownContext != uint256(keccak256(abi.encode(_w, _scope))));
|
||||
_p.pubSignals[5] = _unknownContext;
|
||||
_p.pubSignals[7] = _unknownContext;
|
||||
|
||||
// Expect revert due to context mismatch
|
||||
vm.expectRevert(IPrivacyPool.ContextMismatch.selector);
|
||||
@@ -538,7 +543,9 @@ contract UnitRagequit is UnitPrivacyPool {
|
||||
) external givenCallerIsOriginalDepositor(_depositor, _p.label()) givenCommitmentExistsInState(_p.commitmentHash()) {
|
||||
_mockAndExpect(
|
||||
_RAGEQUIT_VERIFIER,
|
||||
abi.encodeWithSignature('verifyProof((uint256[2],uint256[2][2],uint256[2],uint256[5]))', _p),
|
||||
abi.encodeWithSignature(
|
||||
'verifyProof(uint256[2],uint256[2][2],uint256[2],uint256[5])', _p.pA, _p.pB, _p.pC, _p.pubSignals
|
||||
),
|
||||
abi.encode(true)
|
||||
);
|
||||
|
||||
@@ -576,7 +583,9 @@ contract UnitRagequit is UnitPrivacyPool {
|
||||
) external givenCallerIsOriginalDepositor(_depositor, _p.label()) givenCommitmentExistsInState(_p.commitmentHash()) {
|
||||
_mockAndExpect(
|
||||
_RAGEQUIT_VERIFIER,
|
||||
abi.encodeWithSignature('verifyProof((uint256[2],uint256[2][2],uint256[2],uint256[5]))', _p),
|
||||
abi.encodeWithSignature(
|
||||
'verifyProof(uint256[2],uint256[2][2],uint256[2],uint256[5])', _p.pA, _p.pB, _p.pC, _p.pubSignals
|
||||
),
|
||||
abi.encode(false)
|
||||
);
|
||||
vm.expectRevert(IPrivacyPool.InvalidProof.selector);
|
||||
@@ -592,7 +601,9 @@ contract UnitRagequit is UnitPrivacyPool {
|
||||
) external givenCallerIsOriginalDepositor(_depositor, _p.label()) {
|
||||
_mockAndExpect(
|
||||
_RAGEQUIT_VERIFIER,
|
||||
abi.encodeWithSignature('verifyProof((uint256[2],uint256[2][2],uint256[2],uint256[5]))', _p),
|
||||
abi.encodeWithSignature(
|
||||
'verifyProof(uint256[2],uint256[2][2],uint256[2],uint256[5])', _p.pA, _p.pB, _p.pC, _p.pubSignals
|
||||
),
|
||||
abi.encode(true)
|
||||
);
|
||||
|
||||
@@ -609,7 +620,9 @@ contract UnitRagequit is UnitPrivacyPool {
|
||||
) external givenCallerIsOriginalDepositor(_depositor, _p.label()) givenCommitmentExistsInState(_p.commitmentHash()) {
|
||||
_mockAndExpect(
|
||||
_RAGEQUIT_VERIFIER,
|
||||
abi.encodeWithSignature('verifyProof((uint256[2],uint256[2][2],uint256[2],uint256[5]))', _p),
|
||||
abi.encodeWithSignature(
|
||||
'verifyProof(uint256[2],uint256[2][2],uint256[2],uint256[5])', _p.pA, _p.pB, _p.pC, _p.pubSignals
|
||||
),
|
||||
abi.encode(true)
|
||||
);
|
||||
_pool.mockNullifierStatus(_p.nullifierHash(), true);
|
||||
|
||||
@@ -7,6 +7,8 @@ import {Test} from 'forge-std/Test.sol';
|
||||
import {IERC20} from '@oz/token/ERC20/IERC20.sol';
|
||||
import {IPrivacyPool} from 'interfaces/IPrivacyPool.sol';
|
||||
|
||||
import {Constants} from 'test/helper/Constants.sol';
|
||||
|
||||
/**
|
||||
* @notice Test contract for the PrivacyPoolComplex
|
||||
*/
|
||||
@@ -45,7 +47,7 @@ contract UnitPrivacyPoolComplex is Test {
|
||||
|
||||
function setUp() public {
|
||||
_pool = new ComplexPoolForTest(_ENTRYPOINT, _WITHDRAWAL_VERIFIER, _RAGEQUIT_VERIFIER, _ASSET);
|
||||
_scope = uint256(keccak256(abi.encodePacked(address(_pool), block.chainid, _ASSET)));
|
||||
_scope = uint256(keccak256(abi.encodePacked(address(_pool), block.chainid, _ASSET))) % Constants.SNARK_SCALAR_FIELD;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////
|
||||
@@ -78,7 +80,7 @@ contract UnitConstructor is UnitPrivacyPoolComplex {
|
||||
);
|
||||
|
||||
_pool = new ComplexPoolForTest(_entrypoint, _withdrawalVerifier, _ragequitVerifier, _asset);
|
||||
_scope = uint256(keccak256(abi.encodePacked(address(_pool), block.chainid, _asset)));
|
||||
_scope = uint256(keccak256(abi.encodePacked(address(_pool), block.chainid, _asset))) % Constants.SNARK_SCALAR_FIELD;
|
||||
assertEq(address(_pool.ENTRYPOINT()), _entrypoint, 'Entrypoint address should match constructor input');
|
||||
assertEq(
|
||||
address(_pool.WITHDRAWAL_VERIFIER()),
|
||||
|
||||
@@ -3,6 +3,7 @@ pragma solidity 0.8.28;
|
||||
|
||||
import {IPrivacyPoolSimple, PrivacyPoolSimple} from 'contracts/implementations/PrivacyPoolSimple.sol';
|
||||
import {Test} from 'forge-std/Test.sol';
|
||||
import {Constants} from 'test/helper/Constants.sol';
|
||||
|
||||
import {IPrivacyPool} from 'interfaces/IPrivacyPool.sol';
|
||||
|
||||
@@ -43,7 +44,7 @@ contract UnitPrivacyPoolSimple is Test {
|
||||
|
||||
function setUp() public {
|
||||
_pool = new SimplePoolForTest(_ENTRYPOINT, _WITHDRAWAL_VERIFIER, _RAGEQUIT_VERIFIER);
|
||||
_scope = uint256(keccak256(abi.encodePacked(address(_pool), block.chainid, _ASSET)));
|
||||
_scope = uint256(keccak256(abi.encodePacked(address(_pool), block.chainid, _ASSET))) % Constants.SNARK_SCALAR_FIELD;
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////
|
||||
@@ -72,7 +73,7 @@ contract UnitConstructor is UnitPrivacyPoolSimple {
|
||||
vm.assume(_entrypoint != address(0) && _withdrawalVerifier != address(0) && _ragequitVerifier != address(0));
|
||||
|
||||
_pool = new SimplePoolForTest(_entrypoint, _withdrawalVerifier, _ragequitVerifier);
|
||||
_scope = uint256(keccak256(abi.encodePacked(address(_pool), block.chainid, _ASSET)));
|
||||
_scope = uint256(keccak256(abi.encodePacked(address(_pool), block.chainid, _ASSET))) % Constants.SNARK_SCALAR_FIELD;
|
||||
assertEq(address(_pool.ENTRYPOINT()), _entrypoint, 'Entrypoint address should match constructor input');
|
||||
assertEq(
|
||||
address(_pool.WITHDRAWAL_VERIFIER()),
|
||||
|
||||
21
packages/contracts/tsconfig.json
Normal file
21
packages/contracts/tsconfig.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ES2020",
|
||||
"moduleResolution": "bundler",
|
||||
"esModuleInterop": true,
|
||||
"allowJs": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true
|
||||
},
|
||||
"ts-node": {
|
||||
"esm": true,
|
||||
"experimentalSpecifierResolution": "node"
|
||||
},
|
||||
"include": ["test/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"name": "@privacy-pool-core/sdk",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"description": "Typescript SDK for the Privacy Pool protocol",
|
||||
"repository": "https://github.com/defi-wonderland/privacy-pool-core",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
#!/bin/bash
|
||||
|
||||
CIRCUITS=("merkleTree" "commitment" "withdraw")
|
||||
BUILD_DIR="../circuits/build"
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import * as snarkjs from "snarkjs";
|
||||
import { CircuitName, CircuitsInterface, CircuitSignals } from "../interfaces/circuits.interface.js";
|
||||
import {
|
||||
CircuitName,
|
||||
CircuitsInterface,
|
||||
CircuitSignals,
|
||||
} from "../interfaces/circuits.interface.js";
|
||||
import { Commitment, CommitmentProof } from "../types/commitment.js";
|
||||
import { ErrorCode, ProofError } from "../errors/base.error.js";
|
||||
|
||||
@@ -24,7 +28,7 @@ export class CommitmentService {
|
||||
value: bigint,
|
||||
label: bigint,
|
||||
nullifier: bigint,
|
||||
secret: bigint
|
||||
secret: bigint,
|
||||
): Promise<CommitmentProof> {
|
||||
try {
|
||||
const inputSignals: CircuitSignals = {
|
||||
@@ -37,11 +41,16 @@ export class CommitmentService {
|
||||
const wasm = await this.circuits.getWasm(CircuitName.Commitment);
|
||||
const zkey = await this.circuits.getProvingKey(CircuitName.Commitment);
|
||||
|
||||
const { proof, publicSignals } = await snarkjs.groth16.fullProve(inputSignals, wasm, zkey);
|
||||
const { proof, publicSignals } = await snarkjs.groth16.fullProve(
|
||||
inputSignals,
|
||||
wasm,
|
||||
zkey,
|
||||
);
|
||||
|
||||
return { proof, publicSignals };
|
||||
} catch (error) {
|
||||
throw ProofError.generationFailed({
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
inputSignals: { value, label, nullifier },
|
||||
});
|
||||
}
|
||||
@@ -55,19 +64,20 @@ export class CommitmentService {
|
||||
* @returns Promise resolving to boolean indicating proof validity
|
||||
* @throws {ProofError} If verification fails
|
||||
*/
|
||||
public async verifyCommitment(
|
||||
{ proof, publicSignals }: CommitmentProof
|
||||
): Promise<boolean> {
|
||||
public async verifyCommitment({
|
||||
proof,
|
||||
publicSignals,
|
||||
}: CommitmentProof): Promise<boolean> {
|
||||
try {
|
||||
const vkeyBuff = await this.circuits.getVerificationKey(
|
||||
CircuitName.Commitment
|
||||
CircuitName.Commitment,
|
||||
);
|
||||
const vkey = JSON.parse(new TextDecoder("utf-8").decode(vkeyBuff));
|
||||
|
||||
return await snarkjs.groth16.verify(vkey, publicSignals, proof);
|
||||
} catch (error) {
|
||||
throw ProofError.verificationFailed({
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { CommitmentService } from "./commitment.service.js";
|
||||
import { WithdrawalService } from "./withdrawal.service.js";
|
||||
import { CircuitsInterface } from "../interfaces/circuits.interface.js";
|
||||
import {
|
||||
Commitment,
|
||||
CommitmentProof,
|
||||
} from "../types/commitment.js";
|
||||
import { Commitment, CommitmentProof } from "../types/commitment.js";
|
||||
import {
|
||||
Withdrawal,
|
||||
WithdrawalPayload,
|
||||
@@ -36,9 +33,14 @@ export class PrivacyPoolSDK {
|
||||
value: bigint,
|
||||
label: bigint,
|
||||
nullifier: bigint,
|
||||
secret: bigint
|
||||
secret: bigint,
|
||||
): Promise<CommitmentProof> {
|
||||
return this.commitmentService.proveCommitment(value, label, nullifier, secret);
|
||||
return this.commitmentService.proveCommitment(
|
||||
value,
|
||||
label,
|
||||
nullifier,
|
||||
secret,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,9 +48,7 @@ export class PrivacyPoolSDK {
|
||||
*
|
||||
* @param proof - The proof to verify
|
||||
*/
|
||||
public async verifyCommitment(
|
||||
proof: CommitmentProof
|
||||
): Promise<boolean> {
|
||||
public async verifyCommitment(proof: CommitmentProof): Promise<boolean> {
|
||||
return this.commitmentService.verifyCommitment(proof);
|
||||
}
|
||||
|
||||
@@ -62,13 +62,8 @@ export class PrivacyPoolSDK {
|
||||
public async proveWithdrawal(
|
||||
commitment: Commitment,
|
||||
input: WithdrawalProofInput,
|
||||
withdrawal: Withdrawal,
|
||||
): Promise<WithdrawalPayload> {
|
||||
return this.withdrawalService.proveWithdrawal(
|
||||
commitment,
|
||||
input,
|
||||
withdrawal,
|
||||
);
|
||||
return this.withdrawalService.proveWithdrawal(commitment, input);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,7 +72,7 @@ export class PrivacyPoolSDK {
|
||||
* @param withdrawalPayload - The withdrawal payload to verify
|
||||
*/
|
||||
public async verifyWithdrawal(
|
||||
withdrawalPayload: WithdrawalPayload
|
||||
withdrawalPayload: WithdrawalPayload,
|
||||
): Promise<boolean> {
|
||||
return this.withdrawalService.verifyWithdrawal(withdrawalPayload);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import * as snarkjs from "snarkjs";
|
||||
import { keccak256, encodeAbiParameters } from "viem";
|
||||
import { CircuitName, CircuitsInterface, CircuitSignals } from "../interfaces/circuits.interface.js";
|
||||
import {
|
||||
CircuitName,
|
||||
CircuitsInterface,
|
||||
CircuitSignals,
|
||||
} from "../interfaces/circuits.interface.js";
|
||||
import { Commitment } from "../types/commitment.js";
|
||||
import {
|
||||
Withdrawal,
|
||||
@@ -27,11 +31,9 @@ export class WithdrawalService {
|
||||
public async proveWithdrawal(
|
||||
commitment: Commitment,
|
||||
input: WithdrawalProofInput,
|
||||
withdrawal: Withdrawal,
|
||||
): Promise<WithdrawalPayload> {
|
||||
try {
|
||||
const context = this.calculateContext(withdrawal);
|
||||
const inputSignals = this.prepareInputSignals(commitment, input, context);
|
||||
const inputSignals = this.prepareInputSignals(commitment, input);
|
||||
|
||||
const wasm = await this.circuits.getWasm(CircuitName.Withdraw);
|
||||
const zkey = await this.circuits.getProvingKey(CircuitName.Withdraw);
|
||||
@@ -45,13 +47,10 @@ export class WithdrawalService {
|
||||
return {
|
||||
proof,
|
||||
publicSignals,
|
||||
withdrawal,
|
||||
};
|
||||
} catch (error) {
|
||||
throw ProofError.generationFailed({
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
commitment: commitment.hash,
|
||||
withdrawal: withdrawal.procesooor,
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -64,20 +63,22 @@ export class WithdrawalService {
|
||||
* @throws {ProofError} If verification fails
|
||||
*/
|
||||
public async verifyWithdrawal(
|
||||
withdrawalPayload: WithdrawalPayload
|
||||
withdrawalPayload: WithdrawalPayload,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const vkeyBin = await this.circuits.getVerificationKey(CircuitName.Withdraw);
|
||||
const vkeyBin = await this.circuits.getVerificationKey(
|
||||
CircuitName.Withdraw,
|
||||
);
|
||||
const vkey = JSON.parse(new TextDecoder("utf-8").decode(vkeyBin));
|
||||
|
||||
return await snarkjs.groth16.verify(
|
||||
vkey,
|
||||
withdrawalPayload.publicSignals,
|
||||
withdrawalPayload.proof
|
||||
withdrawalPayload.proof,
|
||||
);
|
||||
} catch (error) {
|
||||
throw ProofError.verificationFailed({
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -107,8 +108,8 @@ export class WithdrawalService {
|
||||
data: `0x${Buffer.from(withdrawal.data).toString("hex")}`,
|
||||
},
|
||||
withdrawal.scope,
|
||||
]
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -118,16 +119,15 @@ export class WithdrawalService {
|
||||
private prepareInputSignals(
|
||||
commitment: Commitment,
|
||||
input: WithdrawalProofInput,
|
||||
context: string
|
||||
): Record<string, bigint | bigint[] | string> {
|
||||
return {
|
||||
// Public signals
|
||||
withdrawnValue: input.withdrawalAmount,
|
||||
stateRoot: input.stateRoot,
|
||||
stateTreeDepth: BigInt(input.stateMerkleProof.siblings.length),
|
||||
stateTreeDepth: input.stateTreeDepth,
|
||||
ASPRoot: input.aspRoot,
|
||||
ASPTreeDepth: BigInt(input.aspMerkleProof.siblings.length),
|
||||
context,
|
||||
ASPTreeDepth: input.aspTreeDepth,
|
||||
context: input.context,
|
||||
|
||||
// Private signals
|
||||
label: commitment.preimage.label,
|
||||
|
||||
@@ -4,7 +4,7 @@ export { InvalidRpcUrl } from "./internal.js";
|
||||
|
||||
export { BlockchainProvider } from "./internal.js";
|
||||
|
||||
export { type Circuits } from "./internal.js";
|
||||
export { Circuits } from "./circuits/index.js";
|
||||
|
||||
// This file is for re-exporting external dependencies that need to be available to consumers
|
||||
export type { LeanIMTMerkleProof } from '@zk-kit/lean-imt';
|
||||
|
||||
@@ -18,18 +18,20 @@ export interface Withdrawal {
|
||||
export interface WithdrawalPayload {
|
||||
readonly proof: Groth16Proof;
|
||||
readonly publicSignals: PublicSignals;
|
||||
readonly withdrawal: Withdrawal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Input parameters required for withdrawal proof generation.
|
||||
*/
|
||||
export interface WithdrawalProofInput {
|
||||
readonly context: bigint;
|
||||
readonly withdrawalAmount: bigint;
|
||||
readonly stateMerkleProof: LeanIMTMerkleProof<bigint>;
|
||||
readonly aspMerkleProof: LeanIMTMerkleProof<bigint>;
|
||||
readonly stateRoot: Hash;
|
||||
readonly stateTreeDepth: bigint;
|
||||
readonly aspRoot: Hash;
|
||||
readonly aspTreeDepth: bigint;
|
||||
readonly newSecret: Secret;
|
||||
readonly newNullifier: Secret;
|
||||
}
|
||||
@@ -146,6 +146,9 @@ describe("PrivacyPoolSDK", () => {
|
||||
aspRoot: BigInt(8) as Hash,
|
||||
newNullifier: BigInt(12) as Secret,
|
||||
newSecret: BigInt(13) as Secret,
|
||||
context: BigInt(1),
|
||||
stateTreeDepth: BigInt(32),
|
||||
aspTreeDepth: BigInt(32)
|
||||
};
|
||||
|
||||
const downloadArtifactsSpy = vi
|
||||
@@ -154,13 +157,11 @@ describe("PrivacyPoolSDK", () => {
|
||||
|
||||
const result = await sdk.proveWithdrawal(
|
||||
mockCommitment,
|
||||
withdrawalInput,
|
||||
withdrawal,
|
||||
withdrawalInput
|
||||
);
|
||||
|
||||
expect(result).toHaveProperty("proof", "mockProof");
|
||||
expect(result).toHaveProperty("publicSignals", "mockPublicSignals");
|
||||
expect(result).toHaveProperty("withdrawal", withdrawal);
|
||||
expect(downloadArtifactsSpy).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
@@ -197,13 +198,15 @@ describe("PrivacyPoolSDK", () => {
|
||||
aspRoot: BigInt(10) as Hash,
|
||||
newNullifier: BigInt(14) as Secret,
|
||||
newSecret: BigInt(15) as Secret,
|
||||
context: BigInt(1),
|
||||
stateTreeDepth: BigInt(32),
|
||||
aspTreeDepth: BigInt(32)
|
||||
};
|
||||
|
||||
await expect(
|
||||
sdk.proveWithdrawal(
|
||||
mockCommitment,
|
||||
withdrawalInput,
|
||||
mockWithdrawal,
|
||||
withdrawalInput
|
||||
),
|
||||
).rejects.toThrow(ProofError);
|
||||
});
|
||||
@@ -226,7 +229,6 @@ describe("PrivacyPoolSDK", () => {
|
||||
sdk.verifyWithdrawal({
|
||||
proof: {} as snarkjs.Groth16Proof,
|
||||
publicSignals: [],
|
||||
withdrawal: mockWithdrawal,
|
||||
}),
|
||||
).rejects.toThrow(ProofError);
|
||||
});
|
||||
@@ -240,7 +242,6 @@ describe("PrivacyPoolSDK", () => {
|
||||
const isValid = await sdk.verifyWithdrawal({
|
||||
proof: {} as snarkjs.Groth16Proof,
|
||||
publicSignals: [],
|
||||
withdrawal: mockWithdrawal,
|
||||
});
|
||||
expect(isValid).toBe(true);
|
||||
});
|
||||
|
||||
14
yarn.lock
14
yarn.lock
@@ -1053,6 +1053,13 @@
|
||||
dependencies:
|
||||
undici-types "~6.19.2"
|
||||
|
||||
"@types/node@^22.10.10":
|
||||
version "22.10.10"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.10.tgz#85fe89f8bf459dc57dfef1689bd5b52ad1af07e6"
|
||||
integrity sha512-X47y/mPNzxviAGY5TcYPtYL8JsY3kAq2n8fMmKoRCxq/c4v4pyGNCzM2R6+M5/umG4ZfHuT+sgqDYqWc9rJ6ww==
|
||||
dependencies:
|
||||
undici-types "~6.20.0"
|
||||
|
||||
"@types/resolve@1.20.2":
|
||||
version "1.20.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.2.tgz#97d26e00cd4a0423b4af620abecf3e6f442b7975"
|
||||
@@ -4489,7 +4496,7 @@ ts-api-utils@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.0.0.tgz#b9d7d5f7ec9f736f4d0f09758b8607979044a900"
|
||||
integrity sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==
|
||||
|
||||
ts-node@^10.9.1:
|
||||
ts-node@^10.9.1, ts-node@^10.9.2:
|
||||
version "10.9.2"
|
||||
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f"
|
||||
integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==
|
||||
@@ -4551,6 +4558,11 @@ typescript@5.5.4:
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba"
|
||||
integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==
|
||||
|
||||
typescript@^5.7.3:
|
||||
version "5.7.3"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.3.tgz#919b44a7dbb8583a9b856d162be24a54bf80073e"
|
||||
integrity sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==
|
||||
|
||||
underscore@1.12.1:
|
||||
version "1.12.1"
|
||||
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.12.1.tgz#7bb8cc9b3d397e201cf8553336d262544ead829e"
|
||||
|
||||
Reference in New Issue
Block a user