chore: integrate SDK into contracts tests (#47)

Co-authored-by: Lempire <61431140+lempire123@users.noreply.github.com>
This commit is contained in:
moebius
2025-01-28 16:19:32 +00:00
committed by GitHub
parent 7559502b36
commit e32e76d398
63 changed files with 85425 additions and 1380 deletions

View File

@@ -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

View File

@@ -104,7 +104,6 @@ dist
.tern-port
# builds
build
dist
# circuit-specific powers of tau are ignored

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -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;
});
});
}

View File

@@ -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;
}

Binary file not shown.

View 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"
]
]
}

Binary file not shown.

View 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"
]
]
}

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -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;
});
});
}

View File

@@ -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;
}

Binary file not shown.

View 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"
]
]
}

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -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;
});
});
}

View File

@@ -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;
}

View File

@@ -1,7 +1,8 @@
{
"commitment": {
"file": "commitment",
"template": "CommitmentHasher"
"template": "CommitmentHasher",
"pubs": ["value", "label"]
},
"merkleTree": {
"file": "merkleTree",

View File

@@ -1,6 +1,6 @@
{
"value": "1000000000000000000",
"label": "6131061687831202463895476385369370444950798926695050324331554981674031506552",
"nullifier": "5079170335209171",
"secret": "2270088679772029"
"value": "12",
"label": "115792089237316195423570985008687907853269984665640564039457584007913129639935",
"nullifier": "56",
"secret": "78"
}

View File

@@ -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": {

View File

@@ -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"
}
}

View File

@@ -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();

View File

@@ -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);
}

View File

@@ -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];
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);
}

View 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);

View 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);

View 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);

View File

@@ -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');
}
}

View File

@@ -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'
);
}
}

View File

@@ -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'
);
}
}

View File

@@ -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'
);
}
}

View File

@@ -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');
}
}

View File

@@ -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');
}
}

View File

@@ -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'
);
}
}

View File

@@ -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'
);
}
}

View File

@@ -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');
}
}

View File

@@ -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');
}
}

View File

@@ -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;
}
}

View 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);
}
}

View 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);
}
}

View File

@@ -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(

View File

@@ -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);

View File

@@ -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()),

View File

@@ -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()),

View 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"]
}

View File

@@ -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",

View File

@@ -1,4 +1,4 @@
#!/bin/sh
#!/bin/bash
CIRCUITS=("merkleTree" "commitment" "withdraw")
BUILD_DIR="../circuits/build"

View File

@@ -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",
});
}
}

View File

@@ -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);
}

View File

@@ -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,

View File

@@ -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';

View File

@@ -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;
}

View File

@@ -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);
});

View File

@@ -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"