keypair generation circuit and test

This commit is contained in:
Cathie So
2022-11-25 20:52:22 +08:00
parent 56f7b25c08
commit ce02b10055
44 changed files with 9972 additions and 568 deletions

View File

@@ -0,0 +1,107 @@
/*
Copyright 2018 0KIMS association.
This file is part of circom (Zero Knowledge Circuit Compiler).
circom is a free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
circom is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with circom. If not, see <https://www.gnu.org/licenses/>.
*/
pragma circom 2.0.0;
include "bitify.circom";
include "escalarmulfix.circom";
template BabyAdd() {
signal input x1;
signal input y1;
signal input x2;
signal input y2;
signal output xout;
signal output yout;
signal beta;
signal gamma;
signal delta;
signal tau;
var a = 168700;
var d = 168696;
beta <== x1*y2;
gamma <== y1*x2;
delta <== (-a*x1+y1)*(x2 + y2);
tau <== beta * gamma;
xout <-- (beta + gamma) / (1+ d*tau);
(1+ d*tau) * xout === (beta + gamma);
yout <-- (delta + a*beta - gamma) / (1-d*tau);
(1-d*tau)*yout === (delta + a*beta - gamma);
}
template BabyDbl() {
signal input x;
signal input y;
signal output xout;
signal output yout;
component adder = BabyAdd();
adder.x1 <== x;
adder.y1 <== y;
adder.x2 <== x;
adder.y2 <== y;
adder.xout ==> xout;
adder.yout ==> yout;
}
template BabyCheck() {
signal input x;
signal input y;
signal x2;
signal y2;
var a = 168700;
var d = 168696;
x2 <== x*x;
y2 <== y*y;
a*x2 + y2 === 1 + d*x2*y2;
}
// Extracts the public key from private key
template BabyPbk() {
signal input in;
signal output Ax;
signal output Ay;
var BASE8[2] = [
5299619240641551281634865583518297030282874472190772894086521144482721001553,
16950150798460657717958625567821834550301663161624707787222815936182638968203
];
component pvkBits = Num2Bits(253);
pvkBits.in <== in;
component mulFix = EscalarMulFix(253, BASE8);
var i;
for (i=0; i<253; i++) {
mulFix.e[i] <== pvkBits.out[i];
}
Ax <== mulFix.out[0];
Ay <== mulFix.out[1];
}

View File

@@ -0,0 +1,197 @@
/*
Copyright 2018 0KIMS association.
This file is part of circom (Zero Knowledge Circuit Compiler).
circom is a free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
circom is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with circom. If not, see <https://www.gnu.org/licenses/>.
*/
pragma circom 2.0.0;
include "montgomery.circom";
include "babyjub.circom";
include "comparators.circom";
template Multiplexor2() {
signal input sel;
signal input in[2][2];
signal output out[2];
out[0] <== (in[1][0] - in[0][0])*sel + in[0][0];
out[1] <== (in[1][1] - in[0][1])*sel + in[0][1];
}
template BitElementMulAny() {
signal input sel;
signal input dblIn[2];
signal input addIn[2];
signal output dblOut[2];
signal output addOut[2];
component doubler = MontgomeryDouble();
component adder = MontgomeryAdd();
component selector = Multiplexor2();
sel ==> selector.sel;
dblIn[0] ==> doubler.in[0];
dblIn[1] ==> doubler.in[1];
doubler.out[0] ==> adder.in1[0];
doubler.out[1] ==> adder.in1[1];
addIn[0] ==> adder.in2[0];
addIn[1] ==> adder.in2[1];
addIn[0] ==> selector.in[0][0];
addIn[1] ==> selector.in[0][1];
adder.out[0] ==> selector.in[1][0];
adder.out[1] ==> selector.in[1][1];
doubler.out[0] ==> dblOut[0];
doubler.out[1] ==> dblOut[1];
selector.out[0] ==> addOut[0];
selector.out[1] ==> addOut[1];
}
// p is montgomery point
// n must be <= 248
// returns out in twisted edwards
// Double is in montgomery to be linked;
template SegmentMulAny(n) {
signal input e[n];
signal input p[2];
signal output out[2];
signal output dbl[2];
component bits[n-1];
component e2m = Edwards2Montgomery();
p[0] ==> e2m.in[0];
p[1] ==> e2m.in[1];
var i;
bits[0] = BitElementMulAny();
e2m.out[0] ==> bits[0].dblIn[0];
e2m.out[1] ==> bits[0].dblIn[1];
e2m.out[0] ==> bits[0].addIn[0];
e2m.out[1] ==> bits[0].addIn[1];
e[1] ==> bits[0].sel;
for (i=1; i<n-1; i++) {
bits[i] = BitElementMulAny();
bits[i-1].dblOut[0] ==> bits[i].dblIn[0];
bits[i-1].dblOut[1] ==> bits[i].dblIn[1];
bits[i-1].addOut[0] ==> bits[i].addIn[0];
bits[i-1].addOut[1] ==> bits[i].addIn[1];
e[i+1] ==> bits[i].sel;
}
bits[n-2].dblOut[0] ==> dbl[0];
bits[n-2].dblOut[1] ==> dbl[1];
component m2e = Montgomery2Edwards();
bits[n-2].addOut[0] ==> m2e.in[0];
bits[n-2].addOut[1] ==> m2e.in[1];
component eadder = BabyAdd();
m2e.out[0] ==> eadder.x1;
m2e.out[1] ==> eadder.y1;
-p[0] ==> eadder.x2;
p[1] ==> eadder.y2;
component lastSel = Multiplexor2();
e[0] ==> lastSel.sel;
eadder.xout ==> lastSel.in[0][0];
eadder.yout ==> lastSel.in[0][1];
m2e.out[0] ==> lastSel.in[1][0];
m2e.out[1] ==> lastSel.in[1][1];
lastSel.out[0] ==> out[0];
lastSel.out[1] ==> out[1];
}
// This function assumes that p is in the subgroup and it is different to 0
template EscalarMulAny(n) {
signal input e[n]; // Input in binary format
signal input p[2]; // Point (Twisted format)
signal output out[2]; // Point (Twisted format)
var nsegments = (n-1)\148 +1;
var nlastsegment = n - (nsegments-1)*148;
component segments[nsegments];
component doublers[nsegments-1];
component m2e[nsegments-1];
component adders[nsegments-1];
component zeropoint = IsZero();
zeropoint.in <== p[0];
var s;
var i;
var nseg;
for (s=0; s<nsegments; s++) {
nseg = (s < nsegments-1) ? 148 : nlastsegment;
segments[s] = SegmentMulAny(nseg);
for (i=0; i<nseg; i++) {
e[s*148+i] ==> segments[s].e[i];
}
if (s==0) {
// force G8 point if input point is zero
segments[s].p[0] <== p[0] + (5299619240641551281634865583518297030282874472190772894086521144482721001553 - p[0])*zeropoint.out;
segments[s].p[1] <== p[1] + (16950150798460657717958625567821834550301663161624707787222815936182638968203 - p[1])*zeropoint.out;
} else {
doublers[s-1] = MontgomeryDouble();
m2e[s-1] = Montgomery2Edwards();
adders[s-1] = BabyAdd();
segments[s-1].dbl[0] ==> doublers[s-1].in[0];
segments[s-1].dbl[1] ==> doublers[s-1].in[1];
doublers[s-1].out[0] ==> m2e[s-1].in[0];
doublers[s-1].out[1] ==> m2e[s-1].in[1];
m2e[s-1].out[0] ==> segments[s].p[0];
m2e[s-1].out[1] ==> segments[s].p[1];
if (s==1) {
segments[s-1].out[0] ==> adders[s-1].x1;
segments[s-1].out[1] ==> adders[s-1].y1;
} else {
adders[s-2].xout ==> adders[s-1].x1;
adders[s-2].yout ==> adders[s-1].y1;
}
segments[s].out[0] ==> adders[s-1].x2;
segments[s].out[1] ==> adders[s-1].y2;
}
}
if (nsegments == 1) {
segments[0].out[0]*(1-zeropoint.out) ==> out[0];
segments[0].out[1]+(1-segments[0].out[1])*zeropoint.out ==> out[1];
} else {
adders[nsegments-2].xout*(1-zeropoint.out) ==> out[0];
adders[nsegments-2].yout+(1-adders[nsegments-2].yout)*zeropoint.out ==> out[1];
}
}

View File

@@ -0,0 +1,299 @@
/*
Copyright 2018 0KIMS association.
This file is part of circom (Zero Knowledge Circuit Compiler).
circom is a free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
circom is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with circom. If not, see <https://www.gnu.org/licenses/>.
*/
pragma circom 2.0.0;
include "mux3.circom";
include "montgomery.circom";
include "babyjub.circom";
/*
Window of 3 elements, it calculates
out = base + base*in[0] + 2*base*in[1] + 4*base*in[2]
out4 = 4*base
The result should be compensated.
*/
/*
The scalar is s = a0 + a1*2^3 + a2*2^6 + ...... + a81*2^243
First We calculate Q = B + 2^3*B + 2^6*B + ......... + 2^246*B
Then we calculate S1 = 2*2^246*B + (1 + a0)*B + (2^3 + a1)*B + .....+ (2^243 + a81)*B
And Finaly we compute the result: RES = SQ - Q
As you can see the input of the adders cannot be equal nor zero, except for the last
substraction that it's done in montgomery.
A good way to see it is that the accumulator input of the adder >= 2^247*B and the other input
is the output of the windows that it's going to be <= 2^246*B
*/
template WindowMulFix() {
signal input in[3];
signal input base[2];
signal output out[2];
signal output out8[2]; // Returns 8*Base (To be linked)
component mux = MultiMux3(2);
mux.s[0] <== in[0];
mux.s[1] <== in[1];
mux.s[2] <== in[2];
component dbl2 = MontgomeryDouble();
component adr3 = MontgomeryAdd();
component adr4 = MontgomeryAdd();
component adr5 = MontgomeryAdd();
component adr6 = MontgomeryAdd();
component adr7 = MontgomeryAdd();
component adr8 = MontgomeryAdd();
// in[0] -> 1*BASE
mux.c[0][0] <== base[0];
mux.c[1][0] <== base[1];
// in[1] -> 2*BASE
dbl2.in[0] <== base[0];
dbl2.in[1] <== base[1];
mux.c[0][1] <== dbl2.out[0];
mux.c[1][1] <== dbl2.out[1];
// in[2] -> 3*BASE
adr3.in1[0] <== base[0];
adr3.in1[1] <== base[1];
adr3.in2[0] <== dbl2.out[0];
adr3.in2[1] <== dbl2.out[1];
mux.c[0][2] <== adr3.out[0];
mux.c[1][2] <== adr3.out[1];
// in[3] -> 4*BASE
adr4.in1[0] <== base[0];
adr4.in1[1] <== base[1];
adr4.in2[0] <== adr3.out[0];
adr4.in2[1] <== adr3.out[1];
mux.c[0][3] <== adr4.out[0];
mux.c[1][3] <== adr4.out[1];
// in[4] -> 5*BASE
adr5.in1[0] <== base[0];
adr5.in1[1] <== base[1];
adr5.in2[0] <== adr4.out[0];
adr5.in2[1] <== adr4.out[1];
mux.c[0][4] <== adr5.out[0];
mux.c[1][4] <== adr5.out[1];
// in[5] -> 6*BASE
adr6.in1[0] <== base[0];
adr6.in1[1] <== base[1];
adr6.in2[0] <== adr5.out[0];
adr6.in2[1] <== adr5.out[1];
mux.c[0][5] <== adr6.out[0];
mux.c[1][5] <== adr6.out[1];
// in[6] -> 7*BASE
adr7.in1[0] <== base[0];
adr7.in1[1] <== base[1];
adr7.in2[0] <== adr6.out[0];
adr7.in2[1] <== adr6.out[1];
mux.c[0][6] <== adr7.out[0];
mux.c[1][6] <== adr7.out[1];
// in[7] -> 8*BASE
adr8.in1[0] <== base[0];
adr8.in1[1] <== base[1];
adr8.in2[0] <== adr7.out[0];
adr8.in2[1] <== adr7.out[1];
mux.c[0][7] <== adr8.out[0];
mux.c[1][7] <== adr8.out[1];
out8[0] <== adr8.out[0];
out8[1] <== adr8.out[1];
out[0] <== mux.out[0];
out[1] <== mux.out[1];
}
/*
This component does a multiplication of a escalar times a fix base
Signals:
e: The scalar in bits
base: the base point in edwards format
out: The result
dbl: Point in Edwards to be linked to the next segment.
*/
template SegmentMulFix(nWindows) {
signal input e[nWindows*3];
signal input base[2];
signal output out[2];
signal output dbl[2];
var i;
var j;
// Convert the base to montgomery
component e2m = Edwards2Montgomery();
e2m.in[0] <== base[0];
e2m.in[1] <== base[1];
component windows[nWindows];
component adders[nWindows];
component cadders[nWindows];
// In the last step we add an extra doubler so that numbers do not match.
component dblLast = MontgomeryDouble();
for (i=0; i<nWindows; i++) {
windows[i] = WindowMulFix();
cadders[i] = MontgomeryAdd();
if (i==0) {
windows[i].base[0] <== e2m.out[0];
windows[i].base[1] <== e2m.out[1];
cadders[i].in1[0] <== e2m.out[0];
cadders[i].in1[1] <== e2m.out[1];
} else {
windows[i].base[0] <== windows[i-1].out8[0];
windows[i].base[1] <== windows[i-1].out8[1];
cadders[i].in1[0] <== cadders[i-1].out[0];
cadders[i].in1[1] <== cadders[i-1].out[1];
}
for (j=0; j<3; j++) {
windows[i].in[j] <== e[3*i+j];
}
if (i<nWindows-1) {
cadders[i].in2[0] <== windows[i].out8[0];
cadders[i].in2[1] <== windows[i].out8[1];
} else {
dblLast.in[0] <== windows[i].out8[0];
dblLast.in[1] <== windows[i].out8[1];
cadders[i].in2[0] <== dblLast.out[0];
cadders[i].in2[1] <== dblLast.out[1];
}
}
for (i=0; i<nWindows; i++) {
adders[i] = MontgomeryAdd();
if (i==0) {
adders[i].in1[0] <== dblLast.out[0];
adders[i].in1[1] <== dblLast.out[1];
} else {
adders[i].in1[0] <== adders[i-1].out[0];
adders[i].in1[1] <== adders[i-1].out[1];
}
adders[i].in2[0] <== windows[i].out[0];
adders[i].in2[1] <== windows[i].out[1];
}
component m2e = Montgomery2Edwards();
component cm2e = Montgomery2Edwards();
m2e.in[0] <== adders[nWindows-1].out[0];
m2e.in[1] <== adders[nWindows-1].out[1];
cm2e.in[0] <== cadders[nWindows-1].out[0];
cm2e.in[1] <== cadders[nWindows-1].out[1];
component cAdd = BabyAdd();
cAdd.x1 <== m2e.out[0];
cAdd.y1 <== m2e.out[1];
cAdd.x2 <== -cm2e.out[0];
cAdd.y2 <== cm2e.out[1];
cAdd.xout ==> out[0];
cAdd.yout ==> out[1];
windows[nWindows-1].out8[0] ==> dbl[0];
windows[nWindows-1].out8[1] ==> dbl[1];
}
/*
This component multiplies a escalar times a fixed point BASE (twisted edwards format)
Signals
e: The escalar in binary format
out: The output point in twisted edwards
*/
template EscalarMulFix(n, BASE) {
signal input e[n]; // Input in binary format
signal output out[2]; // Point (Twisted format)
var nsegments = (n-1)\246 +1; // 249 probably would work. But I'm not sure and for security I keep 246
var nlastsegment = n - (nsegments-1)*249;
component segments[nsegments];
component m2e[nsegments-1];
component adders[nsegments-1];
var s;
var i;
var nseg;
var nWindows;
for (s=0; s<nsegments; s++) {
nseg = (s < nsegments-1) ? 249 : nlastsegment;
nWindows = ((nseg - 1)\3)+1;
segments[s] = SegmentMulFix(nWindows);
for (i=0; i<nseg; i++) {
segments[s].e[i] <== e[s*249+i];
}
for (i = nseg; i<nWindows*3; i++) {
segments[s].e[i] <== 0;
}
if (s==0) {
segments[s].base[0] <== BASE[0];
segments[s].base[1] <== BASE[1];
} else {
m2e[s-1] = Montgomery2Edwards();
adders[s-1] = BabyAdd();
segments[s-1].dbl[0] ==> m2e[s-1].in[0];
segments[s-1].dbl[1] ==> m2e[s-1].in[1];
m2e[s-1].out[0] ==> segments[s].base[0];
m2e[s-1].out[1] ==> segments[s].base[1];
if (s==1) {
segments[s-1].out[0] ==> adders[s-1].x1;
segments[s-1].out[1] ==> adders[s-1].y1;
} else {
adders[s-2].xout ==> adders[s-1].x1;
adders[s-2].yout ==> adders[s-1].y1;
}
segments[s].out[0] ==> adders[s-1].x2;
segments[s].out[1] ==> adders[s-1].y2;
}
}
if (nsegments == 1) {
segments[0].out[0] ==> out[0];
segments[0].out[1] ==> out[1];
} else {
adders[nsegments-2].xout ==> out[0];
adders[nsegments-2].yout ==> out[1];
}
}

View File

@@ -0,0 +1,156 @@
/*
Copyright 2018 0KIMS association.
This file is part of circom (Zero Knowledge Circuit Compiler).
circom is a free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
circom is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with circom. If not, see <https://www.gnu.org/licenses/>.
*/
pragma circom 2.0.0;
template MiMC7(nrounds) {
signal input x_in;
signal input k;
signal output out;
var c[91] = [
0,
20888961410941983456478427210666206549300505294776164667214940546594746570981,
15265126113435022738560151911929040668591755459209400716467504685752745317193,
8334177627492981984476504167502758309043212251641796197711684499645635709656,
1374324219480165500871639364801692115397519265181803854177629327624133579404,
11442588683664344394633565859260176446561886575962616332903193988751292992472,
2558901189096558760448896669327086721003508630712968559048179091037845349145,
11189978595292752354820141775598510151189959177917284797737745690127318076389,
3262966573163560839685415914157855077211340576201936620532175028036746741754,
17029914891543225301403832095880481731551830725367286980611178737703889171730,
4614037031668406927330683909387957156531244689520944789503628527855167665518,
19647356996769918391113967168615123299113119185942498194367262335168397100658,
5040699236106090655289931820723926657076483236860546282406111821875672148900,
2632385916954580941368956176626336146806721642583847728103570779270161510514,
17691411851977575435597871505860208507285462834710151833948561098560743654671,
11482807709115676646560379017491661435505951727793345550942389701970904563183,
8360838254132998143349158726141014535383109403565779450210746881879715734773,
12663821244032248511491386323242575231591777785787269938928497649288048289525,
3067001377342968891237590775929219083706800062321980129409398033259904188058,
8536471869378957766675292398190944925664113548202769136103887479787957959589,
19825444354178182240559170937204690272111734703605805530888940813160705385792,
16703465144013840124940690347975638755097486902749048533167980887413919317592,
13061236261277650370863439564453267964462486225679643020432589226741411380501,
10864774797625152707517901967943775867717907803542223029967000416969007792571,
10035653564014594269791753415727486340557376923045841607746250017541686319774,
3446968588058668564420958894889124905706353937375068998436129414772610003289,
4653317306466493184743870159523234588955994456998076243468148492375236846006,
8486711143589723036499933521576871883500223198263343024003617825616410932026,
250710584458582618659378487568129931785810765264752039738223488321597070280,
2104159799604932521291371026105311735948154964200596636974609406977292675173,
16313562605837709339799839901240652934758303521543693857533755376563489378839,
6032365105133504724925793806318578936233045029919447519826248813478479197288,
14025118133847866722315446277964222215118620050302054655768867040006542798474,
7400123822125662712777833064081316757896757785777291653271747396958201309118,
1744432620323851751204287974553233986555641872755053103823939564833813704825,
8316378125659383262515151597439205374263247719876250938893842106722210729522,
6739722627047123650704294650168547689199576889424317598327664349670094847386,
21211457866117465531949733809706514799713333930924902519246949506964470524162,
13718112532745211817410303291774369209520657938741992779396229864894885156527,
5264534817993325015357427094323255342713527811596856940387954546330728068658,
18884137497114307927425084003812022333609937761793387700010402412840002189451,
5148596049900083984813839872929010525572543381981952060869301611018636120248,
19799686398774806587970184652860783461860993790013219899147141137827718662674,
19240878651604412704364448729659032944342952609050243268894572835672205984837,
10546185249390392695582524554167530669949955276893453512788278945742408153192,
5507959600969845538113649209272736011390582494851145043668969080335346810411,
18177751737739153338153217698774510185696788019377850245260475034576050820091,
19603444733183990109492724100282114612026332366576932662794133334264283907557,
10548274686824425401349248282213580046351514091431715597441736281987273193140,
1823201861560942974198127384034483127920205835821334101215923769688644479957,
11867589662193422187545516240823411225342068709600734253659804646934346124945,
18718569356736340558616379408444812528964066420519677106145092918482774343613,
10530777752259630125564678480897857853807637120039176813174150229243735996839,
20486583726592018813337145844457018474256372770211860618687961310422228379031,
12690713110714036569415168795200156516217175005650145422920562694422306200486,
17386427286863519095301372413760745749282643730629659997153085139065756667205,
2216432659854733047132347621569505613620980842043977268828076165669557467682,
6309765381643925252238633914530877025934201680691496500372265330505506717193,
20806323192073945401862788605803131761175139076694468214027227878952047793390,
4037040458505567977365391535756875199663510397600316887746139396052445718861,
19948974083684238245321361840704327952464170097132407924861169241740046562673,
845322671528508199439318170916419179535949348988022948153107378280175750024,
16222384601744433420585982239113457177459602187868460608565289920306145389382,
10232118865851112229330353999139005145127746617219324244541194256766741433339,
6699067738555349409504843460654299019000594109597429103342076743347235369120,
6220784880752427143725783746407285094967584864656399181815603544365010379208,
6129250029437675212264306655559561251995722990149771051304736001195288083309,
10773245783118750721454994239248013870822765715268323522295722350908043393604,
4490242021765793917495398271905043433053432245571325177153467194570741607167,
19596995117319480189066041930051006586888908165330319666010398892494684778526,
837850695495734270707668553360118467905109360511302468085569220634750561083,
11803922811376367215191737026157445294481406304781326649717082177394185903907,
10201298324909697255105265958780781450978049256931478989759448189112393506592,
13564695482314888817576351063608519127702411536552857463682060761575100923924,
9262808208636973454201420823766139682381973240743541030659775288508921362724,
173271062536305557219323722062711383294158572562695717740068656098441040230,
18120430890549410286417591505529104700901943324772175772035648111937818237369,
20484495168135072493552514219686101965206843697794133766912991150184337935627,
19155651295705203459475805213866664350848604323501251939850063308319753686505,
11971299749478202793661982361798418342615500543489781306376058267926437157297,
18285310723116790056148596536349375622245669010373674803854111592441823052978,
7069216248902547653615508023941692395371990416048967468982099270925308100727,
6465151453746412132599596984628739550147379072443683076388208843341824127379,
16143532858389170960690347742477978826830511669766530042104134302796355145785,
19362583304414853660976404410208489566967618125972377176980367224623492419647,
1702213613534733786921602839210290505213503664731919006932367875629005980493,
10781825404476535814285389902565833897646945212027592373510689209734812292327,
4212716923652881254737947578600828255798948993302968210248673545442808456151,
7594017890037021425366623750593200398174488805473151513558919864633711506220,
18979889247746272055963929241596362599320706910852082477600815822482192194401,
13602139229813231349386885113156901793661719180900395818909719758150455500533
];
var t;
signal t2[nrounds];
signal t4[nrounds];
signal t6[nrounds];
signal t7[nrounds-1];
for (var i=0; i<nrounds; i++) {
t = (i==0) ? k+x_in : k + t7[i-1] + c[i];
t2[i] <== t*t;
t4[i] <== t2[i]*t2[i];
t6[i] <== t4[i]*t2[i];
if (i<nrounds-1) {
t7[i] <== t6[i]*t;
} else {
out <== t6[i]*t + k;
}
}
}
template MultiMiMC7(nInputs, nRounds) {
signal input in[nInputs];
signal input k;
signal output out;
signal r[nInputs +1];
component mims[nInputs];
r[0] <== k;
for (var i=0; i<nInputs; i++) {
mims[i] = MiMC7(nRounds);
mims[i].x_in <== in[i];
mims[i].k <== r[i];
r[i+1] <== r[i] + in[i] + mims[i].out;
}
out <== r[nInputs];
}

View File

@@ -0,0 +1,142 @@
/*
Copyright 2018 0KIMS association.
This file is part of circom (Zero Knowledge Circuit Compiler).
circom is a free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
circom is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with circom. If not, see <https://www.gnu.org/licenses/>.
*/
/*
Source: https://en.wikipedia.org/wiki/Montgomery_curve
1 + y 1 + y
[u, v] = [ ------- , ---------- ]
1 - y (1 - y)x
*/
pragma circom 2.0.0;
template Edwards2Montgomery() {
signal input in[2];
signal output out[2];
out[0] <-- (1 + in[1]) / (1 - in[1]);
out[1] <-- out[0] / in[0];
out[0] * (1-in[1]) === (1 + in[1]);
out[1] * in[0] === out[0];
}
/*
u u - 1
[x, y] = [ ---, ------- ]
v u + 1
*/
template Montgomery2Edwards() {
signal input in[2];
signal output out[2];
out[0] <-- in[0] / in[1];
out[1] <-- (in[0] - 1) / (in[0] + 1);
out[0] * in[1] === in[0];
out[1] * (in[0] + 1) === in[0] - 1;
}
/*
x2 - x1
lamda = ---------
y2 - y1
x3 + A + x1 + x2
x3 = B * lamda^2 - A - x1 -x2 => lamda^2 = ------------------
B
y3 = (2*x1 + x2 + A)*lamda - B*lamda^3 - y1 =>
=> y3 = lamda * ( 2*x1 + x2 + A - x3 - A - x1 - x2) - y1 =>
=> y3 = lamda * ( x1 - x3 ) - y1
----------
y2 - y1
lamda = ---------
x2 - x1
x3 = B * lamda^2 - A - x1 -x2
y3 = lamda * ( x1 - x3 ) - y1
*/
template MontgomeryAdd() {
signal input in1[2];
signal input in2[2];
signal output out[2];
var a = 168700;
var d = 168696;
var A = (2 * (a + d)) / (a - d);
var B = 4 / (a - d);
signal lamda;
lamda <-- (in2[1] - in1[1]) / (in2[0] - in1[0]);
lamda * (in2[0] - in1[0]) === (in2[1] - in1[1]);
out[0] <== B*lamda*lamda - A - in1[0] -in2[0];
out[1] <== lamda * (in1[0] - out[0]) - in1[1];
}
/*
x1_2 = x1*x1
3*x1_2 + 2*A*x1 + 1
lamda = ---------------------
2*B*y1
x3 = B * lamda^2 - A - x1 -x1
y3 = lamda * ( x1 - x3 ) - y1
*/
template MontgomeryDouble() {
signal input in[2];
signal output out[2];
var a = 168700;
var d = 168696;
var A = (2 * (a + d)) / (a - d);
var B = 4 / (a - d);
signal lamda;
signal x1_2;
x1_2 <== in[0] * in[0];
lamda <-- (3*x1_2 + 2*A*in[0] + 1 ) / (2*B*in[1]);
lamda * (2*B*in[1]) === (3*x1_2 + 2*A*in[0] + 1 );
out[0] <== B*lamda*lamda - A - 2*in[0];
out[1] <== lamda * (in[0] - out[0]) - in[1];
}

View File

@@ -0,0 +1,75 @@
/*
Copyright 2018 0KIMS association.
This file is part of circom (Zero Knowledge Circuit Compiler).
circom is a free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
circom is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
License for more details.
You should have received a copy of the GNU General Public License
along with circom. If not, see <https://www.gnu.org/licenses/>.
*/
pragma circom 2.0.0;
template MultiMux3(n) {
signal input c[n][8]; // Constants
signal input s[3]; // Selector
signal output out[n];
signal a210[n];
signal a21[n];
signal a20[n];
signal a2[n];
signal a10[n];
signal a1[n];
signal a0[n];
signal a[n];
// 4 constrains for the intermediary variables
signal s10;
s10 <== s[1] * s[0];
for (var i=0; i<n; i++) {
a210[i] <== ( c[i][ 7]-c[i][ 6]-c[i][ 5]+c[i][ 4] - c[i][ 3]+c[i][ 2]+c[i][ 1]-c[i][ 0] ) * s10;
a21[i] <== ( c[i][ 6]-c[i][ 4]-c[i][ 2]+c[i][ 0] ) * s[1];
a20[i] <== ( c[i][ 5]-c[i][ 4]-c[i][ 1]+c[i][ 0] ) * s[0];
a2[i] <== ( c[i][ 4]-c[i][ 0] );
a10[i] <== ( c[i][ 3]-c[i][ 2]-c[i][ 1]+c[i][ 0] ) * s10;
a1[i] <== ( c[i][ 2]-c[i][ 0] ) * s[1];
a0[i] <== ( c[i][ 1]-c[i][ 0] ) * s[0];
a[i] <== ( c[i][ 0] );
out[i] <== ( a210[i] + a21[i] + a20[i] + a2[i] ) * s[2] +
( a10[i] + a1[i] + a0[i] + a[i] );
}
}
template Mux3() {
var i;
signal input c[8]; // Constants
signal input s[3]; // Selector
signal output out;
component mux = MultiMux3(1);
for (i=0; i<8; i++) {
mux.c[0][i] <== c[i];
}
for (i=0; i<3; i++) {
s[i] ==> mux.s[i];
}
mux.out[0] ==> out;
}

View File

@@ -0,0 +1,29 @@
// from privacy-scaling-explorations/maci
pragma circom 2.0.0;
include "../circomlib/bitify.circom";
include "../circomlib/escalarmulany.circom";
template Ecdh() {
// Note: private key
// Needs to be hashed, and then pruned before
// supplying it to the circuit
signal input private_key;
signal input public_key[2];
signal output shared_key;
component privBits = Num2Bits(253);
privBits.in <== private_key;
component mulFix = EscalarMulAny(253);
mulFix.p[0] <== public_key[0];
mulFix.p[1] <== public_key[1];
for (var i = 0; i < 253; i++) {
mulFix.e[i] <== privBits.out[i];
}
shared_key <== mulFix.out[0];
}

View File

@@ -0,0 +1,74 @@
//from zk-ml/linear-regression-demo
pragma circom 2.0.0;
include "../circomlib/mimc.circom";
template EncryptBits(N) {
signal input plaintext[N];
signal input shared_key;
signal output out[N+1];
component mimc = MultiMiMC7(N, 91);
for (var i=0; i<N; i++) {
mimc.in[i] <== plaintext[i];
}
mimc.k <== 0;
out[0] <== mimc.out;
component hasher[N];
for(var i=0; i<N; i++) {
hasher[i] = MiMC7(91);
hasher[i].x_in <== shared_key;
hasher[i].k <== out[0] + i;
out[i+1] <== plaintext[i] + hasher[i].out;
}
}
template Encrypt() {
signal input plaintext;
signal input shared_key;
signal output out[2];
component mimc = MultiMiMC7(1, 91);
mimc.in[0] <== plaintext;
mimc.k <== 0;
out[0] <== mimc.out;
component hasher;
hasher = MiMC7(91);
hasher.x_in <== shared_key;
hasher.k <== out[0];
out[1] <== plaintext + hasher.out;
}
template DecryptBits(N) {
signal input message[N+1];
signal input shared_key;
signal output out[N];
component hasher[N];
// iv is message[0]
for(var i=0; i<N; i++) {
hasher[i] = MiMC7(91);
hasher[i].x_in <== shared_key;
hasher[i].k <== message[0] + i;
out[i] <== message[i+1] - hasher[i].out;
}
}
template Decrypt() {
signal input message[2];
signal input shared_key;
signal output out;
component hasher;
// iv is message[0]
hasher = MiMC7(91);
hasher.x_in <== shared_key;
hasher.k <== message[0];
out <== message[1] - hasher.out;
}

View File

@@ -0,0 +1,30 @@
// from privacy-scaling-explorations/maci
pragma circom 2.0.0;
include "../circomlib/bitify.circom";
include "../circomlib/escalarmulfix.circom";
template PublicKey() {
// Note: private key
// Needs to be hashed, and then pruned before
// supplying it to the circuit
signal input private_key;
signal output public_key[2];
component privBits = Num2Bits(253);
privBits.in <== private_key;
var BASE8[2] = [
5299619240641551281634865583518297030282874472190772894086521144482721001553,
16950150798460657717958625567821834550301663161624707787222815936182638968203
];
component mulFix = EscalarMulFix(253, BASE8);
for (var i = 0; i < 253; i++) {
mulFix.e[i] <== privBits.out[i];
}
public_key[0] <== mulFix.out[0];
public_key[1] <== mulFix.out[1];
}

2105
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -23,12 +23,15 @@
"author": "Cathie So, PhD",
"license": "GPL-3.0",
"devDependencies": {
"@types/node": "^18.11.9",
"blake-hash": "^2.0.0",
"chai": "^4.3.4",
"circom_tester": "0.0.5",
"circomlib": "^2.0.3",
"circomlib-matrix": "^1.0.1",
"circomlibjs": "^0.1.0",
"mocha": "^9.1.3"
"ethers": "^4.0.45",
"mocha": "^9.1.3",
"web3-utils": "^1.8.1"
}
}

View File

@@ -0,0 +1,5 @@
pragma circom 2.0.3;
include "../../circuits/crypto/ecdh.circom";
component main = Ecdh();

View File

@@ -0,0 +1,5 @@
pragma circom 2.0.3;
include "../../circuits/crypto/publickey_derivation.circom";
component main = PublicKey();

34
test/encryption.js Normal file
View File

@@ -0,0 +1,34 @@
const chai = require("chai");
const path = require("path");
const wasm_tester = require("circom_tester").wasm;
const F1Field = require("ffjavascript").F1Field;
const Scalar = require("ffjavascript").Scalar;
exports.p = Scalar.fromString("21888242871839275222246405745257275088548364400416034343698204186575808495617");
const Fr = new F1Field(exports.p);
const assert = chai.assert;
const { Keypair } = require("./modules/maci-domainobjs");
describe("crypto circuits test", function () {
this.timeout(100000000);
it("public key test", async () => {
const circuit = await wasm_tester(path.join(__dirname, "circuits", "publicKey_test.circom"));
const keypair = new Keypair();
const INPUT = {
'private_key': keypair.privKey.asCircuitInputs(),
}
const witness = await circuit.calculateWitness(INPUT, true);
//console.log(witness);
assert(Fr.eq(Fr.e(witness[0]), Fr.e(1)));
assert(Fr.eq(Fr.e(witness[1]), Fr.e(keypair.pubKey.rawPubKey[0])));
assert(Fr.eq(Fr.e(witness[2]), Fr.e(keypair.pubKey.rawPubKey[1])));
});
});

View File

@@ -0,0 +1,12 @@
exports.smt = require("./src/smt");
exports.eddsa = require("./src/eddsa");
exports.mimc7 = require("./src/mimc7");
exports.mimcsponge = require("./src/mimcsponge");
exports.babyJub = require("./src/babyjub");
exports.pedersenHash = require("./src/pedersenHash");
exports.SMT = require("./src/smt").SMT;
exports.SMTMemDB = require("./src/smt_memdb");
exports.poseidon = require("./src/poseidon");

238
test/modules/maci-crypto.js Normal file
View File

@@ -0,0 +1,238 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.unpackPubKey = exports.packPubKey = exports.bigInt2Buffer = exports.SNARK_FIELD_SIZE = exports.NOTHING_UP_MY_SLEEVE_PUBKEY = exports.NOTHING_UP_MY_SLEEVE = exports.formatPrivKeyForBabyJub = exports.unstringifyBigInts = exports.stringifyBigInts = exports.verifySignature = exports.hashLeftRight = exports.hash11 = exports.hash5 = exports.hashOne = exports.sign = exports.decrypt = exports.encrypt = exports.genEcdhSharedKey = exports.genKeypair = exports.genPubKey = exports.genPrivKey = exports.genRandomSalt = void 0;
const assert = require('assert');
const crypto = require("crypto");
const ethers = require("ethers");
const ff = require('ffjavascript');
const createBlakeHash = require('blake-hash');
const circomlib_0_5_1_1 = require("./circomlib-0.5.1");
const stringifyBigInts = ff.utils.stringifyBigInts;
exports.stringifyBigInts = stringifyBigInts;
const unstringifyBigInts = ff.utils.unstringifyBigInts;
exports.unstringifyBigInts = unstringifyBigInts;
const SNARK_FIELD_SIZE = BigInt('21888242871839275222246405745257275088548364400416034343698204186575808495617');
exports.SNARK_FIELD_SIZE = SNARK_FIELD_SIZE;
// A nothing-up-my-sleeve zero value
// Should be equal to 8370432830353022751713833565135785980866757267633941821328460903436894336785
const NOTHING_UP_MY_SLEEVE = BigInt(ethers.utils.solidityKeccak256(['bytes'], [ethers.utils.toUtf8Bytes('Maci')])) % SNARK_FIELD_SIZE;
exports.NOTHING_UP_MY_SLEEVE = NOTHING_UP_MY_SLEEVE;
// The pubkey is the first Pedersen base point from iden3's circomlib
// See https://github.com/iden3/circomlib/blob/d5ed1c3ce4ca137a6b3ca48bec4ac12c1b38957a/src/pedersen_printbases.js
const NOTHING_UP_MY_SLEEVE_PUBKEY = [
BigInt('10457101036533406547632367118273992217979173478358440826365724437999023779287'),
BigInt('19824078218392094440610104313265183977899662750282163392862422243483260492317')
];
exports.NOTHING_UP_MY_SLEEVE_PUBKEY = NOTHING_UP_MY_SLEEVE_PUBKEY;
/*
* Convert a BigInt to a Buffer
*/
const bigInt2Buffer = (i) => {
let hexStr = i.toString(16);
while (hexStr.length < 64) {
hexStr = '0' + hexStr;
}
return Buffer.from(hexStr, 'hex');
};
exports.bigInt2Buffer = bigInt2Buffer;
// Hash up to 2 elements
const poseidonT3 = (inputs) => {
assert(inputs.length === 2);
return (0, circomlib_0_5_1_1.poseidon)(inputs);
};
// Hash up to 5 elements
const poseidonT6 = (inputs) => {
assert(inputs.length === 5);
return (0, circomlib_0_5_1_1.poseidon)(inputs);
};
const hash5 = (elements) => {
const elementLength = elements.length;
if (elements.length > 5) {
throw new Error(`elements length should not greater than 5, got ${elements.length}`);
}
const elementsPadded = elements.slice();
if (elementLength < 5) {
for (let i = elementLength; i < 5; i++) {
elementsPadded.push(BigInt(0));
}
}
return poseidonT6(elementsPadded);
};
exports.hash5 = hash5;
/*
* A convenience function for to use Poseidon to hash a Plaintext with
* no more than 11 elements
*/
const hash11 = (elements) => {
const elementLength = elements.length;
if (elementLength > 11) {
throw new TypeError(`elements length should not greater than 11, got ${elementLength}`);
}
const elementsPadded = elements.slice();
if (elementLength < 11) {
for (let i = elementLength; i < 11; i++) {
elementsPadded.push(BigInt(0));
}
}
return poseidonT3([
poseidonT3([
poseidonT6(elementsPadded.slice(0, 5)),
poseidonT6(elementsPadded.slice(5, 10))
]),
elementsPadded[10]
]);
};
exports.hash11 = hash11;
/*
* Hash a single BigInt with the Poseidon hash function
*/
const hashOne = (preImage) => {
return poseidonT3([preImage, BigInt(0)]);
};
exports.hashOne = hashOne;
/*
* Hash two BigInts with the Poseidon hash function
*/
const hashLeftRight = (left, right) => {
return poseidonT3([left, right]);
};
exports.hashLeftRight = hashLeftRight;
/*
* Returns a BabyJub-compatible random value. We create it by first generating
* a random value (initially 256 bits large) modulo the snark field size as
* described in EIP197. This results in a key size of roughly 253 bits and no
* more than 254 bits. To prevent modulo bias, we then use this efficient
* algorithm:
* http://cvsweb.openbsd.org/cgi-bin/cvsweb/~checkout~/src/lib/libc/crypt/arc4random_uniform.c
* @return A BabyJub-compatible random value.
*/
const genRandomBabyJubValue = () => {
// Prevent modulo bias
//const lim = BigInt('0x10000000000000000000000000000000000000000000000000000000000000000')
//const min = (lim - SNARK_FIELD_SIZE) % SNARK_FIELD_SIZE
const min = BigInt('6350874878119819312338956282401532410528162663560392320966563075034087161851');
let rand;
while (true) {
rand = BigInt('0x' + crypto.randomBytes(32).toString('hex'));
if (rand >= min) {
break;
}
}
const privKey = rand % SNARK_FIELD_SIZE;
assert(privKey < SNARK_FIELD_SIZE);
return privKey;
};
/*
* @return A BabyJub-compatible private key.
*/
const genPrivKey = () => {
return genRandomBabyJubValue();
};
exports.genPrivKey = genPrivKey;
/*
* @return A BabyJub-compatible salt.
*/
const genRandomSalt = () => {
return genRandomBabyJubValue();
};
exports.genRandomSalt = genRandomSalt;
/*
* An internal function which formats a random private key to be compatible
* with the BabyJub curve. This is the format which should be passed into the
* PublicKey and other circuits.
*/
const formatPrivKeyForBabyJub = (privKey) => {
const sBuff = circomlib_0_5_1_1.eddsa.pruneBuffer(createBlakeHash("blake512").update(bigInt2Buffer(privKey)).digest().slice(0, 32));
const s = ff.utils.leBuff2int(sBuff);
return ff.Scalar.shr(s, 3);
};
exports.formatPrivKeyForBabyJub = formatPrivKeyForBabyJub;
/*
* Losslessly reduces the size of the representation of a public key
* @param pubKey The public key to pack
* @return A packed public key
*/
const packPubKey = (pubKey) => {
return circomlib_0_5_1_1.babyJub.packPoint(pubKey);
};
exports.packPubKey = packPubKey;
/*
* Restores the original PubKey from its packed representation
* @param packed The value to unpack
* @return The unpacked public key
*/
const unpackPubKey = (packed) => {
return circomlib_0_5_1_1.babyJub.unpackPoint(packed);
};
exports.unpackPubKey = unpackPubKey;
/*
* @param privKey A private key generated using genPrivKey()
* @return A public key associated with the private key
*/
const genPubKey = (privKey) => {
privKey = BigInt(privKey.toString());
assert(privKey < SNARK_FIELD_SIZE);
return circomlib_0_5_1_1.eddsa.prv2pub(bigInt2Buffer(privKey));
};
exports.genPubKey = genPubKey;
const genKeypair = () => {
const privKey = genPrivKey();
const pubKey = genPubKey(privKey);
const Keypair = { privKey, pubKey };
return Keypair;
};
exports.genKeypair = genKeypair;
/*
* Generates an Elliptic-curve DiffieHellman shared key given a private key
* and a public key.
* @return The ECDH shared key.
*/
const genEcdhSharedKey = (privKey, pubKey) => {
return circomlib_0_5_1_1.babyJub.mulPointEscalar(pubKey, formatPrivKeyForBabyJub(privKey))[0];
};
exports.genEcdhSharedKey = genEcdhSharedKey;
/*
* Encrypts a plaintext using a given key.
* @return The ciphertext.
*/
const encrypt = (plaintext, sharedKey) => {
// Generate the IV
const iv = circomlib_0_5_1_1.mimc7.multiHash(plaintext, BigInt(0));
const ciphertext = {
iv,
data: plaintext.map((e, i) => {
return e + circomlib_0_5_1_1.mimc7.hash(sharedKey, iv + BigInt(i));
}),
};
// TODO: add asserts here
return ciphertext;
};
exports.encrypt = encrypt;
/*
* Decrypts a ciphertext using a given key.
* @return The plaintext.
*/
const decrypt = (ciphertext, sharedKey) => {
const plaintext = ciphertext.data.map((e, i) => {
return BigInt(e) - BigInt(circomlib_0_5_1_1.mimc7.hash(sharedKey, BigInt(ciphertext.iv) + BigInt(i)));
});
return plaintext;
};
exports.decrypt = decrypt;
/*
* Generates a signature given a private key and plaintext.
* @return The signature.
*/
const sign = (privKey, msg) => {
return circomlib_0_5_1_1.eddsa.signPoseidon(bigInt2Buffer(privKey), msg);
};
exports.sign = sign;
/*
* Checks whether the signature of the given plaintext was created using the
* private key associated with the given public key.
* @return True if the signature is valid, and false otherwise.
*/
const verifySignature = (msg, signature, pubKey) => {
return circomlib_0_5_1_1.eddsa.verifyPoseidon(msg, signature, pubKey);
};
exports.verifySignature = verifySignature;

343
test/modules/maci-crypto.ts Normal file
View File

@@ -0,0 +1,343 @@
const assert = require('assert')
const crypto = require("crypto")
const ethers = require("ethers")
const ff = require('ffjavascript')
const createBlakeHash = require('blake-hash')
import { babyJub, mimc7, poseidon, eddsa } from './circomlib-0.5.1'
const stringifyBigInts: (obj: object) => any = ff.utils.stringifyBigInts
const unstringifyBigInts: (obj: object) => any = ff.utils.unstringifyBigInts
type SnarkBigInt = BigInt
type PrivKey = BigInt
type PubKey = BigInt[]
type EcdhSharedKey = BigInt
type Plaintext = BigInt[]
interface Keypair {
privKey: PrivKey;
pubKey: PubKey;
}
interface Ciphertext {
// The initialisation vector
iv: BigInt;
// The encrypted data
data: BigInt[];
}
// An EdDSA signature.
// TODO: document what R8 and S mean
interface Signature {
R8: BigInt[];
S: BigInt;
}
const SNARK_FIELD_SIZE = BigInt(
'21888242871839275222246405745257275088548364400416034343698204186575808495617'
)
// A nothing-up-my-sleeve zero value
// Should be equal to 8370432830353022751713833565135785980866757267633941821328460903436894336785
const NOTHING_UP_MY_SLEEVE =
BigInt(ethers.utils.solidityKeccak256(['bytes'], [ethers.utils.toUtf8Bytes('Maci')])) % SNARK_FIELD_SIZE
// The pubkey is the first Pedersen base point from iden3's circomlib
// See https://github.com/iden3/circomlib/blob/d5ed1c3ce4ca137a6b3ca48bec4ac12c1b38957a/src/pedersen_printbases.js
const NOTHING_UP_MY_SLEEVE_PUBKEY: PubKey = [
BigInt('10457101036533406547632367118273992217979173478358440826365724437999023779287'),
BigInt('19824078218392094440610104313265183977899662750282163392862422243483260492317')
]
/*
* Convert a BigInt to a Buffer
*/
const bigInt2Buffer = (i: BigInt): Buffer => {
let hexStr = i.toString(16)
while (hexStr.length < 64) {
hexStr = '0' + hexStr
}
return Buffer.from(hexStr, 'hex')
}
// Hash up to 2 elements
const poseidonT3 = (inputs: BigInt[]) => {
assert(inputs.length === 2)
return poseidon(inputs)
}
// Hash up to 5 elements
const poseidonT6 = (inputs: BigInt[]) => {
assert(inputs.length === 5)
return poseidon(inputs)
}
const hash5 = (elements: Plaintext): BigInt => {
const elementLength = elements.length
if (elements.length > 5) {
throw new Error(`elements length should not greater than 5, got ${elements.length}`)
}
const elementsPadded = elements.slice()
if (elementLength < 5) {
for (let i = elementLength; i < 5; i++) {
elementsPadded.push(BigInt(0))
}
}
return poseidonT6(elementsPadded)
}
/*
* A convenience function for to use Poseidon to hash a Plaintext with
* no more than 11 elements
*/
const hash11 = (elements: Plaintext): BigInt => {
const elementLength = elements.length
if (elementLength > 11) {
throw new TypeError(`elements length should not greater than 11, got ${elementLength}`)
}
const elementsPadded = elements.slice()
if (elementLength < 11) {
for (let i = elementLength; i < 11; i++) {
elementsPadded.push(BigInt(0))
}
}
return poseidonT3([
poseidonT3([
poseidonT6(elementsPadded.slice(0, 5)),
poseidonT6(elementsPadded.slice(5, 10))
])
, elementsPadded[10]
])
}
/*
* Hash a single BigInt with the Poseidon hash function
*/
const hashOne = (preImage: BigInt): BigInt => {
return poseidonT3([preImage, BigInt(0)])
}
/*
* Hash two BigInts with the Poseidon hash function
*/
const hashLeftRight = (left: BigInt, right: BigInt): BigInt => {
return poseidonT3([left, right])
}
/*
* Returns a BabyJub-compatible random value. We create it by first generating
* a random value (initially 256 bits large) modulo the snark field size as
* described in EIP197. This results in a key size of roughly 253 bits and no
* more than 254 bits. To prevent modulo bias, we then use this efficient
* algorithm:
* http://cvsweb.openbsd.org/cgi-bin/cvsweb/~checkout~/src/lib/libc/crypt/arc4random_uniform.c
* @return A BabyJub-compatible random value.
*/
const genRandomBabyJubValue = (): BigInt => {
// Prevent modulo bias
//const lim = BigInt('0x10000000000000000000000000000000000000000000000000000000000000000')
//const min = (lim - SNARK_FIELD_SIZE) % SNARK_FIELD_SIZE
const min = BigInt('6350874878119819312338956282401532410528162663560392320966563075034087161851')
let rand
while (true) {
rand = BigInt('0x' + crypto.randomBytes(32).toString('hex'))
if (rand >= min) {
break
}
}
const privKey: PrivKey = rand % SNARK_FIELD_SIZE
assert(privKey < SNARK_FIELD_SIZE)
return privKey
}
/*
* @return A BabyJub-compatible private key.
*/
const genPrivKey = (): PrivKey => {
return genRandomBabyJubValue()
}
/*
* @return A BabyJub-compatible salt.
*/
const genRandomSalt = (): PrivKey=> {
return genRandomBabyJubValue()
}
/*
* An internal function which formats a random private key to be compatible
* with the BabyJub curve. This is the format which should be passed into the
* PublicKey and other circuits.
*/
const formatPrivKeyForBabyJub = (privKey: PrivKey) => {
const sBuff = eddsa.pruneBuffer(
createBlakeHash("blake512").update(
bigInt2Buffer(privKey),
).digest().slice(0,32)
)
const s = ff.utils.leBuff2int(sBuff)
return ff.Scalar.shr(s, 3)
}
/*
* Losslessly reduces the size of the representation of a public key
* @param pubKey The public key to pack
* @return A packed public key
*/
const packPubKey = (pubKey: PubKey): Buffer => {
return babyJub.packPoint(pubKey)
}
/*
* Restores the original PubKey from its packed representation
* @param packed The value to unpack
* @return The unpacked public key
*/
const unpackPubKey = (packed: Buffer): PubKey => {
return babyJub.unpackPoint(packed)
}
/*
* @param privKey A private key generated using genPrivKey()
* @return A public key associated with the private key
*/
const genPubKey = (privKey: PrivKey): PubKey => {
privKey = BigInt(privKey.toString())
assert(privKey < SNARK_FIELD_SIZE)
return eddsa.prv2pub(bigInt2Buffer(privKey))
}
const genKeypair = (): Keypair => {
const privKey = genPrivKey()
const pubKey = genPubKey(privKey)
const Keypair: Keypair = { privKey, pubKey }
return Keypair
}
/*
* Generates an Elliptic-curve DiffieHellman shared key given a private key
* and a public key.
* @return The ECDH shared key.
*/
const genEcdhSharedKey = (
privKey: PrivKey,
pubKey: PubKey,
): EcdhSharedKey => {
return babyJub.mulPointEscalar(pubKey, formatPrivKeyForBabyJub(privKey))[0]
}
/*
* Encrypts a plaintext using a given key.
* @return The ciphertext.
*/
const encrypt = (
plaintext: Plaintext,
sharedKey: EcdhSharedKey,
): Ciphertext => {
// Generate the IV
const iv = mimc7.multiHash(plaintext, BigInt(0))
const ciphertext: Ciphertext = {
iv,
data: plaintext.map((e: BigInt, i: number): BigInt => {
return e + mimc7.hash(
sharedKey,
iv + BigInt(i),
)
}),
}
// TODO: add asserts here
return ciphertext
}
/*
* Decrypts a ciphertext using a given key.
* @return The plaintext.
*/
const decrypt = (
ciphertext: Ciphertext,
sharedKey: EcdhSharedKey,
): Plaintext => {
const plaintext: Plaintext = ciphertext.data.map(
(e: BigInt, i: number): BigInt => {
return BigInt(e) - BigInt(mimc7.hash(sharedKey, BigInt(ciphertext.iv) + BigInt(i)))
}
)
return plaintext
}
/*
* Generates a signature given a private key and plaintext.
* @return The signature.
*/
const sign = (
privKey: PrivKey,
msg: BigInt,
): Signature => {
return eddsa.signPoseidon(
bigInt2Buffer(privKey),
msg,
)
}
/*
* Checks whether the signature of the given plaintext was created using the
* private key associated with the given public key.
* @return True if the signature is valid, and false otherwise.
*/
const verifySignature = (
msg: BigInt,
signature: Signature,
pubKey: PubKey,
): boolean => {
return eddsa.verifyPoseidon(msg, signature, pubKey)
}
export {
genRandomSalt,
genPrivKey,
genPubKey,
genKeypair,
genEcdhSharedKey,
encrypt,
decrypt,
sign,
hashOne,
hash5,
hash11,
hashLeftRight,
verifySignature,
Signature,
PrivKey,
PubKey,
Keypair,
EcdhSharedKey,
Ciphertext,
Plaintext,
SnarkBigInt,
stringifyBigInts,
unstringifyBigInts,
formatPrivKeyForBabyJub,
NOTHING_UP_MY_SLEEVE,
NOTHING_UP_MY_SLEEVE_PUBKEY,
SNARK_FIELD_SIZE,
bigInt2Buffer,
packPubKey,
unpackPubKey,
}

View File

@@ -0,0 +1,296 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PrivKey = exports.PubKey = exports.Keypair = exports.Message = exports.Command = exports.StateLeaf = void 0;
const assert = require('assert');
const maci_crypto_1 = require("./maci-crypto");
const SERIALIZED_PRIV_KEY_PREFIX = 'macisk.';
class PrivKey {
constructor(rawPrivKey) {
this.copy = () => {
return new PrivKey(BigInt(this.rawPrivKey.toString()));
};
this.asCircuitInputs = () => {
return (0, maci_crypto_1.formatPrivKeyForBabyJub)(this.rawPrivKey).toString();
};
this.serialize = () => {
return SERIALIZED_PRIV_KEY_PREFIX + this.rawPrivKey.toString(16);
};
this.rawPrivKey = rawPrivKey;
}
}
exports.PrivKey = PrivKey;
PrivKey.unserialize = (s) => {
const x = s.slice(SERIALIZED_PRIV_KEY_PREFIX.length);
return new PrivKey(BigInt('0x' + x));
};
PrivKey.isValidSerializedPrivKey = (s) => {
const correctPrefix = s.startsWith(SERIALIZED_PRIV_KEY_PREFIX);
const x = s.slice(SERIALIZED_PRIV_KEY_PREFIX.length);
let validValue = false;
try {
const value = BigInt('0x' + x);
validValue = value < maci_crypto_1.SNARK_FIELD_SIZE;
}
catch {
// comment to make linter happy
}
return correctPrefix && validValue;
};
const SERIALIZED_PUB_KEY_PREFIX = 'macipk.';
class PubKey {
constructor(rawPubKey) {
this.copy = () => {
return new PubKey([
BigInt(this.rawPubKey[0].toString()),
BigInt(this.rawPubKey[1].toString()),
]);
};
this.asContractParam = () => {
return {
x: this.rawPubKey[0].toString(),
y: this.rawPubKey[1].toString(),
};
};
this.asCircuitInputs = () => {
return this.rawPubKey.map((x) => x.toString());
};
this.asArray = () => {
return [
this.rawPubKey[0],
this.rawPubKey[1],
];
};
this.serialize = () => {
// Blank leaves have pubkey [0, 0], which packPubKey does not support
if (BigInt(this.rawPubKey[0]) === BigInt(0) &&
BigInt(this.rawPubKey[1]) === BigInt(0)) {
return SERIALIZED_PUB_KEY_PREFIX + 'z';
}
const packed = (0, maci_crypto_1.packPubKey)(this.rawPubKey).toString('hex');
return SERIALIZED_PUB_KEY_PREFIX + packed.toString();
};
assert(rawPubKey.length === 2);
assert(rawPubKey[0] < maci_crypto_1.SNARK_FIELD_SIZE);
assert(rawPubKey[1] < maci_crypto_1.SNARK_FIELD_SIZE);
this.rawPubKey = rawPubKey;
}
}
exports.PubKey = PubKey;
PubKey.unserialize = (s) => {
// Blank leaves have pubkey [0, 0], which packPubKey does not support
if (s === SERIALIZED_PUB_KEY_PREFIX + 'z') {
return new PubKey([BigInt(0), BigInt(0)]);
}
const len = SERIALIZED_PUB_KEY_PREFIX.length;
const packed = Buffer.from(s.slice(len), 'hex');
return new PubKey((0, maci_crypto_1.unpackPubKey)(packed));
};
PubKey.isValidSerializedPubKey = (s) => {
const correctPrefix = s.startsWith(SERIALIZED_PUB_KEY_PREFIX);
let validValue = false;
try {
PubKey.unserialize(s);
validValue = true;
}
catch {
// comment to make linter happy
}
return correctPrefix && validValue;
};
class Keypair {
constructor(privKey) {
this.copy = () => {
return new Keypair(this.privKey.copy());
};
if (privKey) {
this.privKey = privKey;
this.pubKey = new PubKey((0, maci_crypto_1.genPubKey)(privKey.rawPrivKey));
}
else {
const rawKeyPair = (0, maci_crypto_1.genKeypair)();
this.privKey = new PrivKey(rawKeyPair.privKey);
this.pubKey = new PubKey(rawKeyPair.pubKey);
}
}
static genEcdhSharedKey(privKey, pubKey) {
return (0, maci_crypto_1.genEcdhSharedKey)(privKey.rawPrivKey, pubKey.rawPubKey);
}
equals(keypair) {
const equalPrivKey = this.privKey.rawPrivKey === keypair.privKey.rawPrivKey;
const equalPubKey = this.pubKey.rawPubKey[0] === keypair.pubKey.rawPubKey[0] &&
this.pubKey.rawPubKey[1] === keypair.pubKey.rawPubKey[1];
// If this assertion fails, something is very wrong and this function
// should not return anything
// XOR is equivalent to: (x && !y) || (!x && y )
const x = (equalPrivKey && equalPubKey);
const y = (!equalPrivKey && !equalPubKey);
assert((x && !y) || (!x && y));
return equalPrivKey;
}
}
exports.Keypair = Keypair;
/*
* An encrypted command and signature.
*/
class Message {
constructor(iv, data) {
this.asArray = () => {
return [
this.iv,
...this.data,
];
};
this.asContractParam = () => {
return {
iv: this.iv.toString(),
data: this.data.map((x) => x.toString()),
};
};
this.asCircuitInputs = () => {
return this.asArray();
};
this.hash = () => {
return (0, maci_crypto_1.hash11)(this.asArray());
};
this.copy = () => {
return new Message(BigInt(this.iv.toString()), this.data.map((x) => BigInt(x.toString())));
};
assert(data.length === 10);
this.iv = iv;
this.data = data;
}
}
exports.Message = Message;
/*
* A leaf in the state tree, which maps public keys to votes
*/
class StateLeaf {
constructor(pubKey, voteOptionTreeRoot, voiceCreditBalance, nonce) {
this.asArray = () => {
return [
...this.pubKey.asArray(),
this.voteOptionTreeRoot,
this.voiceCreditBalance,
this.nonce,
];
};
this.asCircuitInputs = () => {
return this.asArray();
};
this.hash = () => {
return (0, maci_crypto_1.hash5)(this.asArray());
};
this.serialize = () => {
const j = {
pubKey: this.pubKey.serialize(),
voteOptionTreeRoot: this.voteOptionTreeRoot.toString(16),
voiceCreditBalance: this.voiceCreditBalance.toString(16),
nonce: this.nonce.toString(16),
};
return Buffer.from(JSON.stringify(j, null, 0), 'utf8').toString('base64');
};
this.pubKey = pubKey;
this.voteOptionTreeRoot = voteOptionTreeRoot;
this.voiceCreditBalance = voiceCreditBalance;
// The this is the current nonce. i.e. a user who has published 0 valid
// command should have this value at 0, and the first command should
// have a nonce of 1
this.nonce = nonce;
}
copy() {
return new StateLeaf(this.pubKey.copy(), BigInt(this.voteOptionTreeRoot.toString()), BigInt(this.voiceCreditBalance.toString()), BigInt(this.nonce.toString()));
}
static genBlankLeaf(emptyVoteOptionTreeRoot) {
return new StateLeaf(new PubKey(maci_crypto_1.NOTHING_UP_MY_SLEEVE_PUBKEY), emptyVoteOptionTreeRoot, BigInt(0), BigInt(0));
}
static genRandomLeaf() {
return new StateLeaf(new PubKey(maci_crypto_1.NOTHING_UP_MY_SLEEVE_PUBKEY), (0, maci_crypto_1.genRandomSalt)(), (0, maci_crypto_1.genRandomSalt)(), (0, maci_crypto_1.genRandomSalt)());
}
}
exports.StateLeaf = StateLeaf;
StateLeaf.unserialize = (serialized) => {
const j = JSON.parse(Buffer.from(serialized, 'base64').toString('utf8'));
return new StateLeaf(PubKey.unserialize(j.pubKey), BigInt('0x' + j.voteOptionTreeRoot), BigInt('0x' + j.voiceCreditBalance), BigInt('0x' + j.nonce));
};
/*
* Unencrypted data whose fields include the user's public key, vote etc.
*/
class Command {
constructor(stateIndex, newPubKey, voteOptionIndex, newVoteWeight, nonce, salt = (0, maci_crypto_1.genRandomSalt)()) {
this.copy = () => {
return new Command(BigInt(this.stateIndex.toString()), this.newPubKey.copy(), BigInt(this.voteOptionIndex.toString()), BigInt(this.newVoteWeight.toString()), BigInt(this.nonce.toString()), BigInt(this.salt.toString()));
};
this.asArray = () => {
return [
this.stateIndex,
...this.newPubKey.asArray(),
this.voteOptionIndex,
this.newVoteWeight,
this.nonce,
this.salt,
];
};
/*
* Check whether this command has deep equivalence to another command
*/
this.equals = (command) => {
return this.stateIndex == command.stateIndex &&
this.newPubKey[0] == command.newPubKey[0] &&
this.newPubKey[1] == command.newPubKey[1] &&
this.voteOptionIndex == command.voteOptionIndex &&
this.newVoteWeight == command.newVoteWeight &&
this.nonce == command.nonce &&
this.salt == command.salt;
};
this.hash = () => {
return (0, maci_crypto_1.hash11)(this.asArray());
};
/*
* Signs this command and returns a Signature.
*/
this.sign = (privKey) => {
return (0, maci_crypto_1.sign)(privKey.rawPrivKey, this.hash());
};
/*
* Returns true if the given signature is a correct signature of this
* command and signed by the private key associated with the given public
* key.
*/
this.verifySignature = (signature, pubKey) => {
return (0, maci_crypto_1.verifySignature)(this.hash(), signature, pubKey.rawPubKey);
};
/*
* Encrypts this command along with a signature to produce a Message.
*/
this.encrypt = (signature, sharedKey) => {
const plaintext = [
...this.asArray(),
signature.R8[0],
signature.R8[1],
signature.S,
];
const ciphertext = (0, maci_crypto_1.encrypt)(plaintext, sharedKey);
const message = new Message(ciphertext.iv, ciphertext.data);
return message;
};
this.stateIndex = stateIndex;
this.newPubKey = newPubKey;
this.voteOptionIndex = voteOptionIndex;
this.newVoteWeight = newVoteWeight;
this.nonce = nonce;
this.salt = salt;
}
}
exports.Command = Command;
/*
* Decrypts a Message to produce a Command.
*/
Command.decrypt = (message, sharedKey) => {
const decrypted = (0, maci_crypto_1.decrypt)(message, sharedKey);
const command = new Command(decrypted[0], new PubKey([decrypted[1], decrypted[2]]), decrypted[3], decrypted[4], decrypted[5], decrypted[6]);
const signature = {
R8: [decrypted[7], decrypted[8]],
S: decrypted[9],
};
return { command, signature };
};

View File

@@ -0,0 +1,514 @@
const assert = require('assert');
import {
Ciphertext,
Plaintext,
EcdhSharedKey,
Signature,
PubKey as RawPubKey,
PrivKey as RawPrivKey,
encrypt,
decrypt,
sign,
hash5,
hash11,
verifySignature,
genRandomSalt,
genKeypair,
genPubKey,
formatPrivKeyForBabyJub,
genEcdhSharedKey,
packPubKey,
unpackPubKey,
SNARK_FIELD_SIZE,
NOTHING_UP_MY_SLEEVE_PUBKEY,
} from './maci-crypto'
const SERIALIZED_PRIV_KEY_PREFIX = 'macisk.'
class PrivKey {
public rawPrivKey: RawPrivKey
constructor (rawPrivKey: RawPrivKey) {
this.rawPrivKey = rawPrivKey
}
public copy = (): PrivKey => {
return new PrivKey(BigInt(this.rawPrivKey.toString()))
}
public asCircuitInputs = () => {
return formatPrivKeyForBabyJub(this.rawPrivKey).toString()
}
public serialize = (): string => {
return SERIALIZED_PRIV_KEY_PREFIX + this.rawPrivKey.toString(16)
}
public static unserialize = (s: string): PrivKey => {
const x = s.slice(SERIALIZED_PRIV_KEY_PREFIX.length)
return new PrivKey(BigInt('0x' + x))
}
public static isValidSerializedPrivKey = (s: string): boolean => {
const correctPrefix = s.startsWith(SERIALIZED_PRIV_KEY_PREFIX)
const x = s.slice(SERIALIZED_PRIV_KEY_PREFIX.length)
let validValue = false
try {
const value = BigInt('0x' + x)
validValue = value < SNARK_FIELD_SIZE
} catch {
// comment to make linter happy
}
return correctPrefix && validValue
}
}
const SERIALIZED_PUB_KEY_PREFIX = 'macipk.'
class PubKey {
public rawPubKey: RawPubKey
constructor (rawPubKey: RawPubKey) {
assert(rawPubKey.length === 2)
assert(rawPubKey[0] < SNARK_FIELD_SIZE)
assert(rawPubKey[1] < SNARK_FIELD_SIZE)
this.rawPubKey = rawPubKey
}
public copy = (): PubKey => {
return new PubKey([
BigInt(this.rawPubKey[0].toString()),
BigInt(this.rawPubKey[1].toString()),
])
}
public asContractParam = () => {
return {
x: this.rawPubKey[0].toString(),
y: this.rawPubKey[1].toString(),
}
}
public asCircuitInputs = () => {
return this.rawPubKey.map((x) => x.toString())
}
public asArray = (): BigInt[] => {
return [
this.rawPubKey[0],
this.rawPubKey[1],
]
}
public serialize = (): string => {
// Blank leaves have pubkey [0, 0], which packPubKey does not support
if (
BigInt(this.rawPubKey[0]) === BigInt(0) &&
BigInt(this.rawPubKey[1]) === BigInt(0)
) {
return SERIALIZED_PUB_KEY_PREFIX + 'z'
}
const packed = packPubKey(this.rawPubKey).toString('hex')
return SERIALIZED_PUB_KEY_PREFIX + packed.toString()
}
public static unserialize = (s: string): PubKey => {
// Blank leaves have pubkey [0, 0], which packPubKey does not support
if (s === SERIALIZED_PUB_KEY_PREFIX + 'z') {
return new PubKey([BigInt(0), BigInt(0)])
}
const len = SERIALIZED_PUB_KEY_PREFIX.length
const packed = Buffer.from(s.slice(len), 'hex')
return new PubKey(unpackPubKey(packed))
}
public static isValidSerializedPubKey = (s: string): boolean => {
const correctPrefix = s.startsWith(SERIALIZED_PUB_KEY_PREFIX)
let validValue = false
try {
PubKey.unserialize(s)
validValue = true
} catch {
// comment to make linter happy
}
return correctPrefix && validValue
}
}
class Keypair {
public privKey: PrivKey
public pubKey: PubKey
constructor (
privKey?: PrivKey,
) {
if (privKey) {
this.privKey = privKey
this.pubKey = new PubKey(genPubKey(privKey.rawPrivKey))
} else {
const rawKeyPair = genKeypair()
this.privKey = new PrivKey(rawKeyPair.privKey)
this.pubKey = new PubKey(rawKeyPair.pubKey)
}
}
public copy = (): Keypair => {
return new Keypair(this.privKey.copy())
}
public static genEcdhSharedKey(
privKey: PrivKey,
pubKey: PubKey,
) {
return genEcdhSharedKey(privKey.rawPrivKey, pubKey.rawPubKey)
}
public equals(
keypair: Keypair,
): boolean {
const equalPrivKey = this.privKey.rawPrivKey === keypair.privKey.rawPrivKey
const equalPubKey =
this.pubKey.rawPubKey[0] === keypair.pubKey.rawPubKey[0] &&
this.pubKey.rawPubKey[1] === keypair.pubKey.rawPubKey[1]
// If this assertion fails, something is very wrong and this function
// should not return anything
// XOR is equivalent to: (x && !y) || (!x && y )
const x = (equalPrivKey && equalPubKey)
const y = (!equalPrivKey && !equalPubKey)
assert((x && !y) || (!x && y))
return equalPrivKey
}
}
interface IStateLeaf {
pubKey: PubKey;
voteOptionTreeRoot: BigInt;
voiceCreditBalance: BigInt;
nonce: BigInt;
}
interface VoteOptionTreeLeaf {
votes: BigInt;
}
/*
* An encrypted command and signature.
*/
class Message {
public iv: BigInt
public data: BigInt[]
constructor (
iv: BigInt,
data: BigInt[],
) {
assert(data.length === 10)
this.iv = iv
this.data = data
}
private asArray = (): BigInt[] => {
return [
this.iv,
...this.data,
]
}
public asContractParam = () => {
return {
iv: this.iv.toString(),
data: this.data.map((x: BigInt) => x.toString()),
}
}
public asCircuitInputs = (): BigInt[] => {
return this.asArray()
}
public hash = (): BigInt => {
return hash11(this.asArray())
}
public copy = (): Message => {
return new Message(
BigInt(this.iv.toString()),
this.data.map((x: BigInt) => BigInt(x.toString())),
)
}
}
/*
* A leaf in the state tree, which maps public keys to votes
*/
class StateLeaf implements IStateLeaf {
public pubKey: PubKey
public voteOptionTreeRoot: BigInt
public voiceCreditBalance: BigInt
public nonce: BigInt
constructor (
pubKey: PubKey,
voteOptionTreeRoot: BigInt,
voiceCreditBalance: BigInt,
nonce: BigInt,
) {
this.pubKey = pubKey
this.voteOptionTreeRoot = voteOptionTreeRoot
this.voiceCreditBalance = voiceCreditBalance
// The this is the current nonce. i.e. a user who has published 0 valid
// command should have this value at 0, and the first command should
// have a nonce of 1
this.nonce = nonce
}
public copy(): StateLeaf {
return new StateLeaf(
this.pubKey.copy(),
BigInt(this.voteOptionTreeRoot.toString()),
BigInt(this.voiceCreditBalance.toString()),
BigInt(this.nonce.toString()),
)
}
public static genBlankLeaf(
emptyVoteOptionTreeRoot: BigInt,
): StateLeaf {
return new StateLeaf(
new PubKey(NOTHING_UP_MY_SLEEVE_PUBKEY),
emptyVoteOptionTreeRoot,
BigInt(0),
BigInt(0),
)
}
public static genRandomLeaf() {
return new StateLeaf(
new PubKey(NOTHING_UP_MY_SLEEVE_PUBKEY),
genRandomSalt(),
genRandomSalt(),
genRandomSalt(),
)
}
private asArray = (): BigInt[] => {
return [
...this.pubKey.asArray(),
this.voteOptionTreeRoot,
this.voiceCreditBalance,
this.nonce,
]
}
public asCircuitInputs = (): BigInt[] => {
return this.asArray()
}
public hash = (): BigInt => {
return hash5(this.asArray())
}
public serialize = (): string => {
const j = {
pubKey: this.pubKey.serialize(),
voteOptionTreeRoot: this.voteOptionTreeRoot.toString(16),
voiceCreditBalance: this.voiceCreditBalance.toString(16),
nonce: this.nonce.toString(16),
}
return Buffer.from(JSON.stringify(j, null, 0), 'utf8').toString('base64')
}
static unserialize = (serialized: string): StateLeaf => {
const j = JSON.parse(Buffer.from(serialized, 'base64').toString('utf8'))
return new StateLeaf(
PubKey.unserialize(j.pubKey),
BigInt('0x' + j.voteOptionTreeRoot),
BigInt('0x' + j.voiceCreditBalance),
BigInt('0x' + j.nonce),
)
}
}
interface ICommand {
stateIndex: BigInt;
newPubKey: PubKey;
voteOptionIndex: BigInt;
newVoteWeight: BigInt;
nonce: BigInt;
sign: (PrivKey) => Signature;
encrypt: (EcdhSharedKey, Signature) => Message;
}
/*
* Unencrypted data whose fields include the user's public key, vote etc.
*/
class Command implements ICommand {
public stateIndex: BigInt
public newPubKey: PubKey
public voteOptionIndex: BigInt
public newVoteWeight: BigInt
public nonce: BigInt
public salt: BigInt
constructor (
stateIndex: BigInt,
newPubKey: PubKey,
voteOptionIndex: BigInt,
newVoteWeight: BigInt,
nonce: BigInt,
salt: BigInt = genRandomSalt(),
) {
this.stateIndex = stateIndex
this.newPubKey = newPubKey
this.voteOptionIndex = voteOptionIndex
this.newVoteWeight = newVoteWeight
this.nonce = nonce
this.salt = salt
}
public copy = (): Command => {
return new Command(
BigInt(this.stateIndex.toString()),
this.newPubKey.copy(),
BigInt(this.voteOptionIndex.toString()),
BigInt(this.newVoteWeight.toString()),
BigInt(this.nonce.toString()),
BigInt(this.salt.toString()),
)
}
public asArray = (): BigInt[] => {
return [
this.stateIndex,
...this.newPubKey.asArray(),
this.voteOptionIndex,
this.newVoteWeight,
this.nonce,
this.salt,
]
}
/*
* Check whether this command has deep equivalence to another command
*/
public equals = (command: Command): boolean => {
return this.stateIndex == command.stateIndex &&
this.newPubKey[0] == command.newPubKey[0] &&
this.newPubKey[1] == command.newPubKey[1] &&
this.voteOptionIndex == command.voteOptionIndex &&
this.newVoteWeight == command.newVoteWeight &&
this.nonce == command.nonce &&
this.salt == command.salt
}
public hash = (): BigInt => {
return hash11(this.asArray())
}
/*
* Signs this command and returns a Signature.
*/
public sign = (
privKey: PrivKey,
): Signature => {
return sign(privKey.rawPrivKey, this.hash())
}
/*
* Returns true if the given signature is a correct signature of this
* command and signed by the private key associated with the given public
* key.
*/
public verifySignature = (
signature: Signature,
pubKey: PubKey,
): boolean => {
return verifySignature(
this.hash(),
signature,
pubKey.rawPubKey,
)
}
/*
* Encrypts this command along with a signature to produce a Message.
*/
public encrypt = (
signature: Signature,
sharedKey: EcdhSharedKey,
): Message => {
const plaintext: Plaintext = [
...this.asArray(),
signature.R8[0],
signature.R8[1],
signature.S,
]
const ciphertext: Ciphertext = encrypt(plaintext, sharedKey)
const message = new Message(ciphertext.iv, ciphertext.data)
return message
}
/*
* Decrypts a Message to produce a Command.
*/
public static decrypt = (
message: Message,
sharedKey: EcdhSharedKey,
) => {
const decrypted = decrypt(message, sharedKey)
const command = new Command(
decrypted[0],
new PubKey([decrypted[1], decrypted[2]]),
decrypted[3],
decrypted[4],
decrypted[5],
decrypted[6],
)
const signature = {
R8: [decrypted[7], decrypted[8]],
S: decrypted[9],
}
return { command, signature }
}
}
export {
StateLeaf,
VoteOptionTreeLeaf,
Command,
Message,
Keypair,
PubKey,
PrivKey,
}

128
test/modules/src/babyjub.js Normal file
View File

@@ -0,0 +1,128 @@
const F1Field = require("ffjavascript").F1Field;
const Scalar = require("ffjavascript").Scalar;
const utils = require("ffjavascript").utils;
exports.addPoint = addPoint;
exports.mulPointEscalar = mulPointEscalar;
exports.inCurve = inCurve;
exports.inSubgroup = inSubgroup;
exports.packPoint = packPoint;
exports.unpackPoint = unpackPoint;
exports.p = Scalar.fromString("21888242871839275222246405745257275088548364400416034343698204186575808495617");
const F = new F1Field(exports.p);
exports.F = F;
exports.Generator = [
F.e("995203441582195749578291179787384436505546430278305826713579947235728471134"),
F.e("5472060717959818805561601436314318772137091100104008585924551046643952123905")
];
exports.Base8 = [
F.e("5299619240641551281634865583518297030282874472190772894086521144482721001553"),
F.e("16950150798460657717958625567821834550301663161624707787222815936182638968203")
];
exports.order = Scalar.fromString("21888242871839275222246405745257275088614511777268538073601725287587578984328");
exports.subOrder = Scalar.shiftRight(exports.order, 3);
exports.A = F.e("168700");
exports.D = F.e("168696");
function addPoint(a,b) {
const res = [];
/* does the equivalent of:
res[0] = bigInt((a[0]*b[1] + b[0]*a[1]) * bigInt(bigInt("1") + d*a[0]*b[0]*a[1]*b[1]).inverse(q)).affine(q);
res[1] = bigInt((a[1]*b[1] - cta*a[0]*b[0]) * bigInt(bigInt("1") - d*a[0]*b[0]*a[1]*b[1]).inverse(q)).affine(q);
*/
const beta = F.mul(a[0],b[1]);
const gamma = F.mul(a[1],b[0]);
const delta = F.mul(
F.sub(a[1], F.mul(exports.A, a[0])),
F.add(b[0], b[1])
);
const tau = F.mul(beta, gamma);
const dtau = F.mul(exports.D, tau);
res[0] = F.div(
F.add(beta, gamma),
F.add(F.one, dtau)
);
res[1] = F.div(
F.add(delta, F.sub(F.mul(exports.A,beta), gamma)),
F.sub(F.one, dtau)
);
return res;
}
function mulPointEscalar(base, e) {
let res = [F.e("0"),F.e("1")];
let rem = e;
let exp = base;
while (! Scalar.isZero(rem)) {
if (Scalar.isOdd(rem)) {
res = addPoint(res, exp);
}
exp = addPoint(exp, exp);
rem = Scalar.shiftRight(rem, 1);
}
return res;
}
function inSubgroup(P) {
if (!inCurve(P)) return false;
const res= mulPointEscalar(P, exports.subOrder);
return (F.isZero(res[0]) && F.eq(res[1], F.one));
}
function inCurve(P) {
const x2 = F.square(P[0]);
const y2 = F.square(P[1]);
if (!F.eq(
F.add(F.mul(exports.A, x2), y2),
F.add(F.one, F.mul(F.mul(x2, y2), exports.D)))) return false;
return true;
}
function packPoint(P) {
const buff = utils.leInt2Buff(P[1], 32);
if (F.lt(P[0], F.zero)) {
buff[31] = buff[31] | 0x80;
}
return buff;
}
function unpackPoint(_buff) {
const buff = Buffer.from(_buff);
let sign = false;
const P = new Array(2);
if (buff[31] & 0x80) {
sign = true;
buff[31] = buff[31] & 0x7F;
}
P[1] = utils.leBuff2int(buff);
if (Scalar.gt(P[1], exports.p)) return null;
const y2 = F.square(P[1]);
let x = F.sqrt(F.div(
F.sub(F.one, y2),
F.sub(exports.A, F.mul(exports.D, y2))));
if (x == null) return null;
if (sign) x = F.neg(x);
P[0] = x;
return P;
}

228
test/modules/src/eddsa.js Normal file
View File

@@ -0,0 +1,228 @@
const createBlakeHash = require("blake-hash");
const Scalar = require("ffjavascript").Scalar;
const F1Field = require("ffjavascript").F1Field;
const babyJub = require("./babyjub");
const utils = require("ffjavascript").utils;
const pedersenHash = require("./pedersenHash").hash;
const mimc7 = require("./mimc7");
const poseidon = require("./poseidon.js");
const mimcsponge = require("./mimcsponge");
exports.prv2pub= prv2pub;
exports.sign = sign;
exports.signMiMC = signMiMC;
exports.signPoseidon = signPoseidon;
exports.signMiMCSponge = signMiMCSponge;
exports.verify = verify;
exports.verifyMiMC = verifyMiMC;
exports.verifyPoseidon = verifyPoseidon;
exports.verifyMiMCSponge = verifyMiMCSponge;
exports.packSignature = packSignature;
exports.unpackSignature = unpackSignature;
exports.pruneBuffer = pruneBuffer;
function pruneBuffer(_buff) {
const buff = Buffer.from(_buff);
buff[0] = buff[0] & 0xF8;
buff[31] = buff[31] & 0x7F;
buff[31] = buff[31] | 0x40;
return buff;
}
function prv2pub(prv) {
const sBuff = pruneBuffer(createBlakeHash("blake512").update(prv).digest().slice(0,32));
let s = utils.leBuff2int(sBuff);
const A = babyJub.mulPointEscalar(babyJub.Base8, Scalar.shr(s,3));
return A;
}
function sign(prv, msg) {
const h1 = createBlakeHash("blake512").update(prv).digest();
const sBuff = pruneBuffer(h1.slice(0,32));
const s = utils.leBuff2int(sBuff);
const A = babyJub.mulPointEscalar(babyJub.Base8, Scalar.shr(s, 3));
const rBuff = createBlakeHash("blake512").update(Buffer.concat([h1.slice(32,64), msg])).digest();
let r = utils.leBuff2int(rBuff);
const Fr = new F1Field(babyJub.subOrder);
r = Fr.e(r);
const R8 = babyJub.mulPointEscalar(babyJub.Base8, r);
const R8p = babyJub.packPoint(R8);
const Ap = babyJub.packPoint(A);
const hmBuff = pedersenHash(Buffer.concat([R8p, Ap, msg]));
const hm = utils.leBuff2int(hmBuff);
const S = Fr.add(r , Fr.mul(hm, s));
return {
R8: R8,
S: S
};
}
function signMiMC(prv, msg) {
const h1 = createBlakeHash("blake512").update(prv).digest();
const sBuff = pruneBuffer(h1.slice(0,32));
const s = utils.leBuff2int(sBuff);
const A = babyJub.mulPointEscalar(babyJub.Base8, Scalar.shr(s, 3));
const msgBuff = utils.leInt2Buff(msg, 32);
const rBuff = createBlakeHash("blake512").update(Buffer.concat([h1.slice(32,64), msgBuff])).digest();
let r = utils.leBuff2int(rBuff);
const Fr = new F1Field(babyJub.subOrder);
r = Fr.e(r);
const R8 = babyJub.mulPointEscalar(babyJub.Base8, r);
const hm = mimc7.multiHash([R8[0], R8[1], A[0], A[1], msg]);
const S = Fr.add(r , Fr.mul(hm, s));
return {
R8: R8,
S: S
};
}
function signMiMCSponge(prv, msg) {
const h1 = createBlakeHash("blake512").update(prv).digest();
const sBuff = pruneBuffer(h1.slice(0,32));
const s = utils.leBuff2int(sBuff);
const A = babyJub.mulPointEscalar(babyJub.Base8, Scalar.shr(s, 3));
const msgBuff = utils.leInt2Buff(msg, 32);
const rBuff = createBlakeHash("blake512").update(Buffer.concat([h1.slice(32,64), msgBuff])).digest();
let r = utils.leBuff2int(rBuff);
const Fr = new F1Field(babyJub.subOrder);
r = Fr.e(r);
const R8 = babyJub.mulPointEscalar(babyJub.Base8, r);
const hm = mimcsponge.multiHash([R8[0], R8[1], A[0], A[1], msg]);
const S = Fr.add(r , Fr.mul(hm, s));
return {
R8: R8,
S: S
};
}
function signPoseidon(prv, msg) {
const h1 = createBlakeHash("blake512").update(prv).digest();
const sBuff = pruneBuffer(h1.slice(0,32));
const s = utils.leBuff2int(sBuff);
const A = babyJub.mulPointEscalar(babyJub.Base8, Scalar.shr(s, 3));
const msgBuff = utils.leInt2Buff(msg, 32);
const rBuff = createBlakeHash("blake512").update(Buffer.concat([h1.slice(32,64), msgBuff])).digest();
let r = utils.leBuff2int(rBuff);
const Fr = new F1Field(babyJub.subOrder);
r = Fr.e(r);
const R8 = babyJub.mulPointEscalar(babyJub.Base8, r);
const hm = poseidon([R8[0], R8[1], A[0], A[1], msg]);
const S = Fr.add(r , Fr.mul(hm, s));
return {
R8: R8,
S: S
};
}
function verify(msg, sig, A) {
// Check parameters
if (typeof sig != "object") return false;
if (!Array.isArray(sig.R8)) return false;
if (sig.R8.length!= 2) return false;
if (!babyJub.inCurve(sig.R8)) return false;
if (!Array.isArray(A)) return false;
if (A.length!= 2) return false;
if (!babyJub.inCurve(A)) return false;
if (sig.S>= babyJub.subOrder) return false;
const R8p = babyJub.packPoint(sig.R8);
const Ap = babyJub.packPoint(A);
const hmBuff = pedersenHash(Buffer.concat([R8p, Ap, msg]));
const hm = utils.leBuff2int(hmBuff);
const Pleft = babyJub.mulPointEscalar(babyJub.Base8, sig.S);
let Pright = babyJub.mulPointEscalar(A, Scalar.mul(hm,8));
Pright = babyJub.addPoint(sig.R8, Pright);
if (!babyJub.F.eq(Pleft[0],Pright[0])) return false;
if (!babyJub.F.eq(Pleft[1],Pright[1])) return false;
return true;
}
function verifyMiMC(msg, sig, A) {
// Check parameters
if (typeof sig != "object") return false;
if (!Array.isArray(sig.R8)) return false;
if (sig.R8.length!= 2) return false;
if (!babyJub.inCurve(sig.R8)) return false;
if (!Array.isArray(A)) return false;
if (A.length!= 2) return false;
if (!babyJub.inCurve(A)) return false;
if (sig.S>= babyJub.subOrder) return false;
const hm = mimc7.multiHash([sig.R8[0], sig.R8[1], A[0], A[1], msg]);
const Pleft = babyJub.mulPointEscalar(babyJub.Base8, sig.S);
let Pright = babyJub.mulPointEscalar(A, Scalar.mul(hm, 8));
Pright = babyJub.addPoint(sig.R8, Pright);
if (!babyJub.F.eq(Pleft[0],Pright[0])) return false;
if (!babyJub.F.eq(Pleft[1],Pright[1])) return false;
return true;
}
function verifyPoseidon(msg, sig, A) {
// Check parameters
if (typeof sig != "object") return false;
if (!Array.isArray(sig.R8)) return false;
if (sig.R8.length!= 2) return false;
if (!babyJub.inCurve(sig.R8)) return false;
if (!Array.isArray(A)) return false;
if (A.length!= 2) return false;
if (!babyJub.inCurve(A)) return false;
if (sig.S>= babyJub.subOrder) return false;
const hm = poseidon([sig.R8[0], sig.R8[1], A[0], A[1], msg]);
const Pleft = babyJub.mulPointEscalar(babyJub.Base8, sig.S);
let Pright = babyJub.mulPointEscalar(A, Scalar.mul(hm, 8));
Pright = babyJub.addPoint(sig.R8, Pright);
if (!babyJub.F.eq(Pleft[0],Pright[0])) return false;
if (!babyJub.F.eq(Pleft[1],Pright[1])) return false;
return true;
}
function verifyMiMCSponge(msg, sig, A) {
// Check parameters
if (typeof sig != "object") return false;
if (!Array.isArray(sig.R8)) return false;
if (sig.R8.length!= 2) return false;
if (!babyJub.inCurve(sig.R8)) return false;
if (!Array.isArray(A)) return false;
if (A.length!= 2) return false;
if (!babyJub.inCurve(A)) return false;
if (sig.S>= babyJub.subOrder) return false;
const hm = mimcsponge.multiHash([sig.R8[0], sig.R8[1], A[0], A[1], msg]);
const Pleft = babyJub.mulPointEscalar(babyJub.Base8, sig.S);
let Pright = babyJub.mulPointEscalar(A, hm.times(bigInt("8")));
Pright = babyJub.addPoint(sig.R8, Pright);
if (!babyJub.F.eq(Pleft[0],Pright[0])) return false;
if (!babyJub.F.eq(Pleft[1],Pright[1])) return false;
return true;
}
function packSignature(sig) {
const R8p = babyJub.packPoint(sig.R8);
const Sp = utils.leInt2Buff(sig.S, 32);
return Buffer.concat([R8p, Sp]);
}
function unpackSignature(sigBuff) {
return {
R8: babyJub.unpackPoint(sigBuff.slice(0,32)),
S: utils.leBuff2int(sigBuff.slice(32,64))
};
}

208
test/modules/src/evmasm.js Normal file
View File

@@ -0,0 +1,208 @@
// Copyright (c) 2018 Jordi Baylina
// License: LGPL-3.0+
//
const Web3Utils = require("web3-utils");
class Contract {
constructor() {
this.code = [];
this.labels = {};
this.pendingLabels = {};
}
createTxData() {
let C;
// Check all labels are defined
const pendingLabels = Object.keys(this.pendingLabels);
if (pendingLabels.length>0) {
throw new Error("Lables not defined: "+ pendingLabels.join(", "));
}
let setLoaderLength = 0;
let genLoadedLength = -1;
while (genLoadedLength!=setLoaderLength) {
setLoaderLength = genLoadedLength;
C = new module.exports();
C.codesize();
C.push(setLoaderLength);
C.push(0);
C.codecopy();
C.push(this.code.length);
C.push(0);
C.return();
genLoadedLength = C.code.length;
}
return Web3Utils.bytesToHex(C.code.concat(this.code));
}
stop() { this.code.push(0x00); }
add() { this.code.push(0x01); }
mul() { this.code.push(0x02); }
sub() { this.code.push(0x03); }
div() { this.code.push(0x04); }
sdiv() { this.code.push(0x05); }
mod() { this.code.push(0x06); }
smod() { this.code.push(0x07); }
addmod() { this.code.push(0x08); }
mulmod() { this.code.push(0x09); }
exp() { this.code.push(0x0a); }
signextend() { this.code.push(0x0b); }
lt() { this.code.push(0x10); }
gt() { this.code.push(0x11); }
slt() { this.code.push(0x12); }
sgt() { this.code.push(0x13); }
eq() { this.code.push(0x14); }
iszero() { this.code.push(0x15); }
and() { this.code.push(0x16); }
or() { this.code.push(0x17); }
shor() { this.code.push(0x18); }
not() { this.code.push(0x19); }
byte() { this.code.push(0x1a); }
keccak() { this.code.push(0x20); }
sha3() { this.code.push(0x20); } // alias
address() { this.code.push(0x30); }
balance() { this.code.push(0x31); }
origin() { this.code.push(0x32); }
caller() { this.code.push(0x33); }
callvalue() { this.code.push(0x34); }
calldataload() { this.code.push(0x35); }
calldatasize() { this.code.push(0x36); }
calldatacopy() { this.code.push(0x37); }
codesize() { this.code.push(0x38); }
codecopy() { this.code.push(0x39); }
gasprice() { this.code.push(0x3a); }
extcodesize() { this.code.push(0x3b); }
extcodecopy() { this.code.push(0x3c); }
returndatasize() { this.code.push(0x3d); }
returndatacopy() { this.code.push(0x3e); }
blockhash() { this.code.push(0x40); }
coinbase() { this.code.push(0x41); }
timestamp() { this.code.push(0x42); }
number() { this.code.push(0x43); }
difficulty() { this.code.push(0x44); }
gaslimit() { this.code.push(0x45); }
pop() { this.code.push(0x50); }
mload() { this.code.push(0x51); }
mstore() { this.code.push(0x52); }
mstore8() { this.code.push(0x53); }
sload() { this.code.push(0x54); }
sstore() { this.code.push(0x55); }
_pushLabel(label) {
if (typeof this.labels[label] != "undefined") {
this.push(this.labels[label]);
} else {
this.pendingLabels[label] = this.pendingLabels[label] || [];
this.pendingLabels[label].push(this.code.length);
this.push("0x000000");
}
}
_fillLabel(label) {
if (!this.pendingLabels[label]) return;
let dst = this.labels[label];
const dst3 = [dst >> 16, (dst >> 8) & 0xFF, dst & 0xFF];
this.pendingLabels[label].forEach((p) => {
for (let i=0; i<3; i++) {
this.code[p+i+1] = dst3[i];
}
});
delete this.pendingLabels[label];
}
jmp(label) {
if (typeof label !== "undefined") {
this._pushLabel(label);
}
this.code.push(0x56);
}
jmpi(label) {
if (typeof label !== "undefined") {
this._pushLabel(label);
}
this.code.push(0x57);
}
pc() { this.code.push(0x58); }
msize() { this.code.push(0x59); }
gas() { this.code.push(0x5a); }
label(name) {
if (typeof this.labels[name] != "undefined") {
throw new Error("Label already defined");
}
this.labels[name] = this.code.length;
this.code.push(0x5b);
this._fillLabel(name);
}
push(data) {
if (typeof data === "number") {
let isNeg;
if (data<0) {
isNeg = true;
data = -data;
}
data = data.toString(16);
if (data.length % 2 == 1) data = "0" + data;
data = "0x" + data;
if (isNeg) data = "-"+data;
}
const d = Web3Utils.hexToBytes(Web3Utils.toHex(data));
if (d.length == 0 || d.length > 32) {
throw new Error("Assertion failed");
}
this.code = this.code.concat([0x5F + d.length], d);
}
dup(n) {
if (n < 0 || n >= 16) {
throw new Error("Assertion failed");
}
this.code.push(0x80 + n);
}
swap(n) {
if (n < 1 || n > 16) {
throw new Error("Assertion failed");
}
this.code.push(0x8f + n);
}
log0() { this.code.push(0xa0); }
log1() { this.code.push(0xa1); }
log2() { this.code.push(0xa2); }
log3() { this.code.push(0xa3); }
log4() { this.code.push(0xa4); }
create() { this.code.push(0xf0); }
call() { this.code.push(0xf1); }
callcode() { this.code.push(0xf2); }
return() { this.code.push(0xf3); }
delegatecall() { this.code.push(0xf4); }
staticcall() { this.code.push(0xfa); }
revert() { this.code.push(0xfd); }
invalid() { this.code.push(0xfe); }
selfdestruct() { this.code.push(0xff); }
}
module.exports = Contract;

View File

@@ -0,0 +1,582 @@
// Copyright (c) 2018 Jordi Baylina
// License: LGPL-3.0+
//
const Contract = require("./evmasm");
const G2 = require("snarkjs").bn128.G2;
function toHex256(a) {
let S = a.toString(16);
while (S.length < 64) S="0"+S;
return "0x" + S;
}
function createCode(P, w) {
const C = new Contract();
const NPOINTS = 1 << (w-1);
const VAR_POS = C.allocMem(32);
const VAR_POINTS = C.allocMem( (NPOINTS)*4*32);
const savedP = C.allocMem(32);
const savedZ3 = C.allocMem(32);
// Check selector
C.push("0x0100000000000000000000000000000000000000000000000000000000");
C.push(0);
C.calldataload();
C.div();
C.push("b65c7c74"); // mulexp(uint256)
C.eq();
C.jmpi("start");
C.invalid();
C.label("start");
storeVals();
C.push( Math.floor(255/w)*w ); // pos := 255
C.push(VAR_POS);
C.mstore();
C.push("21888242871839275222246405745257275088696311157297823662689037894645226208583");
C.push(0);
C.push(0);
C.push(0);
C.push(0);
C.push(0);
C.push(0);
C.label("begin_loop"); // ACC_X ACC_Y ACC_Z q
C.internalCall("double");
// g = (e>>pos)&MASK
C.push(4);
C.calldataload(); // e ACC_X ACC_Y ACC_Z q
C.push(VAR_POS);
C.mload(); // pos e ACC_X ACC_Y ACC_Z q
C.shr();
C.push(NPOINTS-1);
C.and(); // g ACC_X ACC_Y ACC_Z q
C.internalCall("add"); // acc_x acc_y acc_z
C.push(VAR_POS);
C.mload(); // pos acc_x acc_y acc_z
C.dup(0); // pos pos acc_x acc_y acc_z
C.push(0); // 0 pos pos acc_x acc_y acc_z
C.eq(); // eq pos acc_x acc_y acc_z
C.jmpi("after_loop"); // pos acc_x acc_y acc_z
C.push(w); // 5 pos acc_x acc_y acc_z
C.sub(); // pos acc_x acc_y acc_z
C.push(VAR_POS);
C.mstore(); // acc_x acc_y acc_z
C.jmp("begin_loop");
C.label("after_loop"); // pos acc_x acc_y acc_z
C.pop(); // acc_x acc_y acc_z
C.internalCall("affine"); // acc_x acc_y
C.push(0);
C.mstore();
C.push(20);
C.mstore();
C.push(40);
C.mstore();
C.push(60);
C.mstore();
C.push("0x80");
C.push("0x00");
C.return();
double();
addPoint();
affine();
return C.createTxData();
function add(a,b,q) {
C.dup(q);
C.dup(a+1 + 1);
C.dup(b+1 + 2);
C.addmod();
C.dup(q + 1);
C.dup(a + 2);
C.dup(b + 3);
C.addmod();
}
function sub(a,b,q) {
C.dup(q); // q
C.dup(a+1 + 1); // ai q
C.dub(q + 2); // q ai q
C.dup(b+1 + 3); // bi q ai q
C.sub(); // -bi ai q
C.addmod(); // ci
C.dup(q + 1); // q ci
C.dup(a + 2); // ar q ci
C.dup(q + 3); // q ar q ci
C.dup(b + 4); // br q ar q ci
C.sub(); // -br ar q ci
C.addmod(); // cr ci
}
function mul(a, b, q) {
C.dup(q); // q
C.dup(q + 1); // q q
C.dup(a + 2); // ar q q
C.dup(b+1 + 3); // bi ar q q
C.mulmod(); // ci1 q
C.dup(q + 2); // q ci1 q
C.dup(a+1 + 3); // ai q ci1 q
C.dup(b + 4); // ar ai q ci1 q
C.mulmod(); // ci2 ci1 q
C.addmod(); // ci
C.dup(q + 1); // q ci
C.dup(q + 2); // q q ci
C.dup(q + 3); // q q q ci
C.dup(a+1 + 4); // ai q q ci
C.dup(b+1 + 5); // bi ai q q ci
C.mulmod(); // cr2 q q ci
C.sub(); // -cr2 q ci
C.dup(q + 3); // q -cr2 q ci
C.dup(a + 4); // ar q -cr2 q ci
C.dup(b + 5); // br ar q -cr2 q ci
C.mulmod(); // cr1 -cr2 q ci
C.addmod(); // cr ci
}
function square(a, q) {
C.dup(q); // q
C.dup(q + 1); // q q
C.dup(a + 2); // ar q q
C.dup(a+1 + 3); // ai ar q q
C.mulmod(); // arai q
C.dup(0); // arai arai q
C.addmod(); // ci
C.dup(q + 1); // q ci
C.dup(q + 2); // q q ci
C.dup(q + 3); // q q q ci
C.dup(a+1 + 4); // ai q q ci
C.dup(a+1 + 5); // ai ai q q ci
C.mulmod(); // cr2 q q ci
C.sub(); // -cr2 q ci
C.dup(q + 3); // q -cr2 q ci
C.dup(a + 4); // ar q -cr2 q ci
C.dup(a + 5); // br ar q -cr2 q ci
C.mulmod(); // cr1 -cr2 q ci
C.addmod(); // cr ci
}
function add1(a, q) {
C.dup(a+1); // im
C.dup(1 + q); // q
C.dup(2 + a); // re q im
C.push(1); // 1 re q im
C.addmod();
}
function cmp(a, b) {
C.dup(a);
C.dup(b);
C.eq();
C.dup(a+1);
C.dup(a+1);
C.and();
}
function rm(a) {
if (a>0) C.swap(a);
C.pop();
if (a>0) C.swap(a);
C.pop();
}
function double() {
C.label("double"); // xR, xI, yR, yI, zR zI, q
C.dup(4);
C.iszero();
C.dup(6);
C.iszero();
C.and();
C.jumpi("enddouble"); // X Y Z q
// Z3 = 2*Y*Z // Remove Z
mul(2, 4, 6); // yz X Y Z q
rm(6); // X Y yz q
add(4, 4, 6); // 2yz X Y yz q
rm(6); // X Y Z3 q
// A = X^2
square(0,6); // A X Y Z3 q
// B = Y^2 // Remove Y
square(4,8); // B A X Y Z3 q
rm(6); // A X B Z3 q
// C = B^2
square(4,8); // C A X B Z3 q
// D = (X+B)^2-A-C // Remove X, Remove B
add(4,6, 10); // X+B C A X B Z3 q
rm(6); // C A X+B B Z3 q
rm(6); // A X+B C Z3 q
square(2,8); // (X+B)^2 A X+B C Z3 q
rm(4); // A (X+B)^2 C Z3 q
sub(2, 0, 8); // (X+B)^2-A A (X+B)^2 C Z3 q
rm(4); // A (X+B)^2-A C Z3 q
sub(2, 4, 8); // (X+B)^2-A-C A (X+B)^2-A C Z3 q
rm(4); // A D C Z3 q
// D = D+D
add(2,2, 8); // D+D A D C Z3 q
rm(4); // A D C Z3 q
// E=A+A+A
add(0, 0, 8); // 2A A D C Z3 q
add(0, 2, 10); // 3A 2A A D C Z3 q
rm(4); // 2A 3A D C Z3 q
rm(0); // E D C Z3 q
// F=E^2
square(0, 8); // F E D C Z3 q
// X3= F - 2*D // Remove F
add(4, 4, 10); // 2D F E D C Z3 q
sub(2, 0, 12); // F-2D 2D F E D C Z3 q
rm(4); // 2D X3 E D C Z3 q
rm(0); // X3 E D C Z3 q
// Y3 = E * (D - X3) - 8 * C // Remove D C E
sub(4, 0, 10); // D-X3 X3 E D C Z3 q
rm(6); // X3 E D-X3 C Z3 q
mul(2, 4, 10); // E*(D-X3) X3 E D-X3 C Z3 q
rm(6); // X3 E E*(D-X3) C Z3 q
rm(2); // X3 E*(D-X3) C Z3 q
add(4, 4, 8); // 2C X3 E*(D-X3) C Z3 q
rm(6); // X3 E*(D-X3) 2C Z3 q
add(4, 4, 8); // 4C X3 E*(D-X3) 2C Z3 q
rm(6); // X3 E*(D-X3) 4C Z3 q
add(4, 4, 8); // 8C X3 E*(D-X3) 4C Z3 q
rm(6); // X3 E*(D-X3) 8C Z3 q
sub(2, 4, 8); // E*(D-X3)-8C X3 E*(D-X3) 8C Z3 q
rm(6); // X3 E*(D-X3) Y3 Z3 q
rm(2); // X3 Y3 Z3 q
C.label("enddouble");
C.returnCall();
}
function addPoint() { // p, xR, xI, yR, yI, zR zI, q
C.dup(0); // p p X2 Y2 Z2 q
C.push(savedP);
C.mstore();
C.iszero(); // X2 Y2 Z2 q
C.jumpi("endpadd");
C.dup(4);
C.iszero();
C.dup(6);
C.iszero();
C.and();
C.jumpi("returnP"); // X2 Y2 Z2 q
// lastZ3 = (Z2+1)^2 - Z2^2
add1(4, 6); // Z2+1 X2 Y2 Z2 q
square(0, 8); // (Z2+1)^2 Z2+1 X2 Y2 Z2 q
rm(2); // (Z2+1)^2 X2 Y2 Z2 q
square(6, 8); // Z2^2 (Z2+1)^2 X2 Y2 Z2 q
sub(2, 0, 10); // (Z2+1)^2-Z2^2 Z2^2 (Z2+1)^2 X2 Y2 Z2 q
saveZ3(); // Z2^2 (Z2+1)^2 X2 Y2 Z2 q
rm(2); // Z2^2 X2 Y2 Z2 q
// U2 = X2
// S2 = Y2 // Z2^2 U2 S2 Z2 q
// U1 = X1 * Z2^2
loadX(); // X1 Z2^2 U2 S2 Z2 q
mul(0, 2, 10); // X1*Z2^2 X1 Z2^2 U2 S2 Z2 q
rm(2); // X1*Z2^2 Z2^2 U2 S2 Z2 q
mul(2, 8, 10); // Z2^3 U1 Z2^2 U2 S2 Z2 q
rm(4); // U1 Z2^3 U2 S2 Z2 q
rm(8); // Z2^3 U2 S2 U1 q
// S1 = Y1 * Z1^3
loadY(); // Y1 Z2^3 U2 S2 U1 q
mul(0, 2, 10); // S1 Y1 Z2^3 U2 S2 U1 q
rm(4); // Y1 S1 U2 S2 U1 q
rm(0); // S1 U2 S2 U1 q
cmp(0, 4); // c1 S1 U2 S2 U1 q
cmp(3, 7); // c2 c1 S1 U2 S2 U1 q
C.and(); // c2&c1 S1 U2 S2 U1 q
C.jumpi("double1"); // S1 U2 S2 U1 q
// Returns the double
// H = U2-U1 // Remove U2
C.sub(4, 8, 10); // H S1 U2 S2 U1 q
rm(4); // S1 H S2 U1 q
// // r = 2 * (S2-S1) // Remove S2
C.sub(4, 4, 8); // S1-S2 S1 H S2 U1 q
rm(6); // S1 H S1-S2 U1 q
C.add(4, 4, 8); // 2*(S1-S2) S1 H S1-S2 U1 q
rm(6); // S1 H r U1 q
// I = (2 * H)^2
C.add(2, 2, 8); // 2*H S1 H r U1 q
C.square(0, 10); // (2*H)^2 2*H S1 H r U1 q
rm(2); // I S1 H r U1 q
// V = U1 * I
mul(8, 0, 10); // V I S1 H r U1 q
rm(10); // I S1 H r V q
// J = H * I // Remove I
mul(4, 0, 10); // J I S1 H r V q
rm(2); // J S1 H r V q
// X3 = r^2 - J - 2 * V
// S1J2 = (S1*J)*2 // Remove S1
mul(2, 0, 10); // S1*J J S1 H r V q
rm(4); // J S1*J H r V q
add(2,2, 10); // (S1*J)*2 J S1*J H r V q
rm(4); // J S1J2 H r V q
// X3 = r^2 - J - 2 * V
square(6, 10); // r^2 J S1J2 H r V q
sub(0, 2, 12); // r^2-J r^2 J S1J2 H r V q
rm(2); // r^2-J J S1J2 H r V q
rm(2); // r^2-J S1J2 H r V q
add(8, 8, 10); // 2*V r^2-J S1J2 H r V q
sub(2, 0, 12); // r^2-J-2*V 2*V r^2-J S1J2 H r V q
rm(4); // 2*V X3 S1J2 H r V q
rm(0); // X3 S1J2 H r V q
// Y3 = r * (V-X3)-S1J2
sub(8, 0, 10); // V-X3 X3 S1J2 H r V q
rm(10); // X3 S1J2 H r V-X3 q
mul(6, 8, 10); // r*(V-X3) X3 S1J2 H r V-X3 q
rm(8); // X3 S1J2 H r*(V-X3) V-X3 q
rm(8); // S1J2 H r*(V-X3) X3 q
sub(4, 0, 8); // Y3 S1J2 H r*(V-X3) X3 q
rm(6); // S1J2 H Y3 X3 q
rm(0); // H Y3 X3 q
// Z3 = lastZ * H
loadZ3(); // lastZ3 H Y3 X3 q
mul(0, 2, 8); // Z3 lastZ3 H Y3 X3 q
rm(4); // lastZ3 Z3 Y3 X3 q
rm(0); // Z3 Y3 X3 q
C.swap(1);
C.swap(5);
C.swap(1);
C.swap(4); // X3 Y3 Z3 q
// returns the point in memory
C.label("returnP"); // X Y Z q
rm(0);
rm(0);
rm(0);
C.push(0);
C.push(1);
loadX();
loadY();
C.jump("endpadd");
C.label("double1"); // S1 U2 S2 U1 q
rm(0);
rm(0);
rm(0);
rm(0);
C.push(0);
C.push(1);
loadX();
loadY();
C.jump("double");
C.label("endpadd");
C.returnCall();
function loadX() {
C.push(savedP);
C.mload(); // p
C.push(32);
C.mul(); // P*32
C.push(VAR_POINTS+32);
C.add(); // P*32+32
C.dup(); // P*32+32 P*32+32
C.mload(); // im P*32+32
C.swap(1); // P*32+32 im
C.push(0x20); // 32 P*32+32 im
C.sub(); // P*32 im
C.mload(); // re im
}
function loadY() {
C.push(savedP);
C.mload(); // p
C.push(32);
C.mul(); // P*32
C.push(VAR_POINTS+32*3);
C.add(); // P*32+32
C.dup(); // P*32+32 P*32+32
C.mload(); // im P*32+32
C.swap(1); // P*32+32 im
C.push(0x20); // 32 P*32+32 im
C.sub(); // P*32 im
C.mload(); // re im
}
function loadZ3() {
C.push(savedZ3+32);
C.mload(); // p
C.push(savedZ3);
C.mload();
}
function saveZ3() {
C.push(savedZ3);
C.mstore();
C.push(savedZ3+32);
C.mstore();
}
}
function affine() { // X Y Z q
// If Z2=0 return 0
C.label("affine");
C.dup(4);
C.dup(5 + 1);
C.or();
C.jumpi("notZero"); // X Y Z q
rm(0);
rm(0);
C.push(0);
C.push(0);
C.jmp("endAffine");
C.label("notZero");
inverse2(4,6); // Z_inv X Y Z q
square(2, 8); // Z2_inv Z_inv X Y Z q
mul(0, 2, 10); // Z3_inv Z2_inv Z_inv X Y Z q
rm(4); // Z2_inv Z3_inv X Y Z q
C.push(1);
C.push(0); // 1 Z2_inv Z3_inv X Y Z q
rm(10); // Z2_inv Z3_inv X Y 1 q
mul(2, 6, 10); // YI Z2_inv Z3_inv X Y 1 q
rm(8); // Z2_inv Z3_inv X YI 1 q
mul(0, 4, 10); // XI Z2_inv Z3_inv X YI 1 q
rm(6); // Z2_inv Z3_inv XI YI 1 q
rm(0); // Z3_inv XI YI 1 q
rm(0); // XI YI 1 q
C.label("endAffine");
C.returnCall();
}
function inverse2(a, q) {
C.dup(q); // q
C.dup(q + 1); // q q
C.push(2); // 2 q q
C.sub(); // q-2 q
C.dup(q + 2); // q q-2 q
C.dup(q + 3); // q q q-2 q
C.dup(a + 4); // ar q q q-2 q
C.dup(a + 5); // ar ar q q q-2 q
C.mulmod(); // t0 q q-2 q
C.dup(q + 4); // q t0 q q-2 q
C.dup(a+1 + 5); // ai q t0 q q-2 q
C.dup(a+1 + 6); // ai ai q t0 q q-2 q
C.mulmod(); // t1 t0 q q-2 q
C.addmod(); // t2 q-2 q
C.expmod(); // t3
C.dup(q + 1); // q t3
C.dup(q + 2); // q q t3
C.dup(q + 3); // q q q t3
C.dup(1); // t3 q q q t3
C.sub(); // -t3 q q t3
C.dup(a+1 + 3); // ai -t3 q q t3
C.mulmod(); // ii q t3
C.swap(2); // t3 q ii
C.dup(a + 3); // ar t3 q ii
C.mulmod(); // ir ii
}
function storeVals() {
C.push(VAR_POINTS); // p
for (let i=0; i<NPOINTS; i++) {
const MP = G2.affine(G2.mulScalar(P, i));
for (let j=0; j<2; j++) {
for (let k=0; k<2; k++) {
C.push(toHex256(MP[j][k])); // MP[0][0] p
C.dup(1); // p MP[0][0] p
C.mstore(); // p
C.push(32); // 32 p
C.add(); // p+32
}
}
}
}
}
module.exports.abi = [
{
"constant": true,
"inputs": [
{
"name": "escalar",
"type": "uint256"
}
],
"name": "mulexp",
"outputs": [
{
"name": "",
"type": "uint256"
},
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "pure",
"type": "function"
}
];
module.exports.createCode = createCode;

66
test/modules/src/mimc7.js Normal file
View File

@@ -0,0 +1,66 @@
const Scalar = require("ffjavascript").Scalar;
const ZqField = require("ffjavascript").ZqField;
const Web3Utils = require("web3-utils");
const F = new ZqField(Scalar.fromString("21888242871839275222246405745257275088548364400416034343698204186575808495617"));
exports.F = F;
const SEED = "mimc";
const NROUNDS = 91;
exports.getIV = (seed) => {
if (typeof seed === "undefined") seed = SEED;
const c = Web3Utils.keccak256(seed+"_iv");
const cn = Scalar.FromString(Web3Utils.toBN(c).toString());
const iv = cn.mod(F.p);
return iv;
};
exports.getConstants = (seed, nRounds) => {
if (typeof seed === "undefined") seed = SEED;
if (typeof nRounds === "undefined") nRounds = NROUNDS;
const cts = new Array(nRounds);
let c = Web3Utils.keccak256(SEED);
for (let i=1; i<nRounds; i++) {
c = Web3Utils.keccak256(c);
const n1 = Web3Utils.toBN(c).mod(Web3Utils.toBN(F.p.toString()));
const c2 = Web3Utils.padLeft(Web3Utils.toHex(n1), 64);
cts[i] = Scalar.fromString(Web3Utils.toBN(c2).toString());
}
cts[0] = F.e(0);
return cts;
};
const cts = exports.getConstants(SEED, 91);
exports.hash = (_x_in, _k) =>{
const x_in = F.e(_x_in);
const k = F.e(_k);
let r;
for (let i=0; i<NROUNDS; i++) {
const c = cts[i];
const t = (i==0) ? F.add(x_in, k) : F.add(F.add(r, k), c);
r = F.pow(t, 7);
}
return F.add(r, k);
};
exports.multiHash = (arr, key) => {
let r;
if (typeof(key) === "undefined") {
r = F.zero;
} else {
r = key;
}
for (let i=0; i<arr.length; i++) {
r = F.add(
F.add(
r,
arr[i]
),
exports.hash(F.e(arr[i]), r)
);
}
return r;
};

View File

@@ -0,0 +1,114 @@
// Copyright (c) 2018 Jordi Baylina
// License: LGPL-3.0+
//
const Web3Utils = require("web3-utils");
const Contract = require("./evmasm");
function createCode(seed, n) {
let ci = Web3Utils.keccak256(seed);
const C = new Contract();
C.push(0x44);
C.push("0x00");
C.push("0x00");
C.calldatacopy();
C.push("0x0100000000000000000000000000000000000000000000000000000000");
C.push("0x00");
C.mload();
C.div();
C.push("0xd15ca109"); // MiMCpe7(uint256,uint256)
// C.push("0x8c42199e"); // MiMCpe7(uint256,uint256,uint256)
C.eq();
C.jmpi("start");
C.invalid();
C.label("start");
C.push("0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001"); // q
C.push("0x24");
C.mload(); // k q
C.dup(1); // q k q
C.dup(0); // q q k q
C.push("0x04");
C.mload(); // x q q k q
C.dup(3); // k x q q k q
C.addmod(); // t=x+k q k q
C.dup(1); // q t q k q
C.dup(0); // q q t q k q
C.dup(2); // t q q t q k q
C.dup(0); // t t q q t q k q
C.mulmod(); // a=t^2 q t q k q
C.dup(1); // q a q t q k q
C.dup(1); // a q a q t q k q
C.dup(0); // a a q a q t q k q
C.mulmod(); // b=t^4 a q t q k q
C.mulmod(); // c=t^6 t q k q
C.mulmod(); // r=t^7 k q
for (let i=0; i<n-1; i++) {
ci = Web3Utils.keccak256(ci);
C.dup(2); // q r k q
C.dup(0); // q q r k q
C.dup(0); // q q q r k q
C.swap(3); // r q q q k q
C.push(ci); // c r q q k q
C.addmod(); // s=c+r q q k q
C.dup(3); // k s q q k q
C.addmod(); // t=s+k q k q
C.dup(1); // q t q k q
C.dup(0); // q q t q k q
C.dup(2); // t q q t q k q
C.dup(0); // t t q q t q k q
C.mulmod(); // a=t^2 q t q k q
C.dup(1); // q a q t q k q
C.dup(1); // a q a q t q k q
C.dup(0); // a a q a q t q k q
C.mulmod(); // b=t^4 a q t q k q
C.mulmod(); // c=t^6 t q k q
C.mulmod(); // r=t^7 k q
}
C.addmod(); // res=t^7+k
C.push("0x00");
C.mstore(); // Save it to pos 0;
C.push("0x20");
C.push("0x00");
C.return();
return C.createTxData();
}
module.exports.abi = [
{
"constant": true,
"inputs": [
{
"name": "in_x",
"type": "uint256"
},
{
"name": "in_k",
"type": "uint256"
}
],
"name": "MiMCpe7",
"outputs": [
{
"name": "out_x",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "pure",
"type": "function"
}
];
module.exports.createCode = createCode;

View File

@@ -0,0 +1,3 @@
const mimc7 = require("./mimc7.js");
console.log("IV: "+mimc7.getIV().toString());

View File

@@ -0,0 +1,13 @@
const mimc7 = require("./mimc7.js");
const nRounds = 91;
let S = "[\n";
const cts = mimc7.getConstants();
for (let i=0; i<nRounds; i++) {
S = S + cts[i].toString();
if (i<nRounds-1) S = S + ",";
S=S+"\n";
}
S = S + "]\n";
console.log(S);

View File

@@ -0,0 +1,13 @@
const mimcGenContract = require("./mimc_gencontract");
const SEED = "mimc";
let nRounds;
if (typeof process.argv[2] != "undefined") {
nRounds = parseInt(process.argv[2]);
} else {
nRounds = 91;
}
console.log(mimcGenContract.createCode(SEED, nRounds));

View File

@@ -0,0 +1,86 @@
const Scalar = require("ffjavascript").Scalar
const Web3Utils = require("web3-utils");
const ZqField = require("ffjavascript").ZqField;
const F = new ZqField(Scalar.fromString("21888242871839275222246405745257275088548364400416034343698204186575808495617"));
const SEED = "mimcsponge";
const NROUNDS = 220;
exports.getIV = (seed) => {
if (typeof seed === "undefined") seed = SEED;
const c = Web3Utils.keccak256(seed+"_iv");
const cn = Scalar.fromString(Web3Utils.toBN(c).toString());
const iv = cn.mod(F.p);
return iv;
};
exports.getConstants = (seed, nRounds) => {
if (typeof seed === "undefined") seed = SEED;
if (typeof nRounds === "undefined") nRounds = NROUNDS;
const cts = new Array(nRounds);
let c = Web3Utils.keccak256(SEED);
for (let i=1; i<nRounds; i++) {
c = Web3Utils.keccak256(c);
const n1 = Web3Utils.toBN(c).mod(Web3Utils.toBN(F.p.toString()));
const c2 = Web3Utils.padLeft(Web3Utils.toHex(n1), 64);
cts[i] = F.e(Web3Utils.toBN(c2).toString());
}
cts[0] = F.e(0);
cts[cts.length - 1] = F.e(0);
return cts;
};
const cts = exports.getConstants(SEED, NROUNDS);
exports.hash = (_xL_in, _xR_in, _k) =>{
let xL = F.e(_xL_in);
let xR = F.e(_xR_in);
const k = F.e(_k);
for (let i=0; i<NROUNDS; i++) {
const c = cts[i];
const t = (i==0) ? F.add(xL, k) : F.add(F.add(xL, k), c);
const xR_tmp = F.e(xR);
if (i < (NROUNDS - 1)) {
xR = xL;
xL = F.add(xR_tmp, F.pow(t, 5));
} else {
xR = F.add(xR_tmp, F.pow(t, 5));
}
}
return {
xL: F.normalize(xL),
xR: F.normalize(xR),
};
};
exports.multiHash = (arr, key, numOutputs) => {
if (typeof(numOutputs) === "undefined") {
numOutputs = 1;
}
if (typeof(key) === "undefined") {
key = F.zero;
}
let R = F.zero;
let C = F.zero;
for (let i=0; i<arr.length; i++) {
R = F.add(R, F.e(arr[i]));
const S = exports.hash(R, C, key);
R = S.xL;
C = S.xR;
}
let outputs = [R];
for (let i=1; i < numOutputs; i++) {
const S = exports.hash(R, C, key);
R = S.xL;
C = S.xR;
outputs.push(R);
}
if (numOutputs == 1) {
return F.normalize(outputs[0]);
} else {
return outputs.map(x => F.normalize(x));
}
};

View File

@@ -0,0 +1,128 @@
// Copyright (c) 2018 Jordi Baylina
// License: LGPL-3.0+
//
const Web3Utils = require("web3-utils");
const Contract = require("./evmasm");
function createCode(seed, n) {
let ci = Web3Utils.keccak256(seed);
const C = new Contract();
C.push(0x64);
C.push("0x00");
C.push("0x00");
C.calldatacopy();
C.push("0x0100000000000000000000000000000000000000000000000000000000");
C.push("0x00");
C.mload();
C.div();
C.push("0x3f1a1187"); // MiMCSponge(uint256,uint256,uint256)
C.eq();
C.jmpi("start");
C.invalid();
C.label("start");
C.push("0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001"); // q
C.push("0x44");
C.mload(); // k q
C.push("0x04");
C.mload(); // xL k q
C.dup(2); // q xL k q
C.push("0x24");
C.mload(); // xR q xL k q
C.dup(1); // q xR q xL k q
C.dup(0); // q q xR q xL k q
C.dup(4); // xL q q xR q xL k q
C.dup(6); // k xL q q xR q xL k q
C.addmod(); // t=k+xL q xR q xL k q
C.dup(1); // q t q xR q xL k q
C.dup(0); // q q t q xR q xL k q
C.dup(2); // t q q t q xR q xL k q
C.dup(0); // t t q q t q xR q xL k q
C.mulmod(); // b=t^2 q t q xR q xL k q
C.dup(0); // b b q t q xR q xL k q
C.mulmod(); // c=t^4 t q xR q xL k q
C.mulmod(); // d=t^5 xR q xL k q
C.addmod(); // e=t^5+xR xL k q (for next round: xL xR k q)
for (let i=0; i<n-1; i++) {
if (i < n-2) {
ci = Web3Utils.keccak256(ci);
} else {
ci = "0x00";
}
C.swap(1); // xR xL k q
C.dup(3); // q xR xL k q
C.dup(3); // k q xR xL k q
C.dup(1); // q k q xR xL k q
C.dup(4); // xL q k q xR xL k q
C.push(ci); // ci xL q k q xR xL k q
C.addmod(); // a=ci+xL k q xR xL k q
C.addmod(); // t=a+k xR xL k q
C.dup(4); // q t xR xL k q
C.swap(1); // t q xR xL k q
C.dup(1); // q t q xR xL k q
C.dup(0); // q q t q xR xL k q
C.dup(2); // t q q t q xR xL k q
C.dup(0); // t t q q t q xR xL k q
C.mulmod(); // b=t^2 q t q xR xL k q
C.dup(0); // b b q t q xR xL k q
C.mulmod(); // c=t^4 t q xR xL k q
C.mulmod(); // d=t^5 xR xL k q
C.dup(4); // q d xR xL k q
C.swap(2); // xR d q xL k q
C.addmod(); // e=t^5+xR xL k q (for next round: xL xR k q)
}
C.push("0x20");
C.mstore(); // Save it to pos 0;
C.push("0x00");
C.mstore(); // Save it to pos 1;
C.push("0x40");
C.push("0x00");
C.return();
return C.createTxData();
}
module.exports.abi = [
{
"constant": true,
"inputs": [
{
"name": "xL_in",
"type": "uint256"
},
{
"name": "xR_in",
"type": "uint256"
},
{
"name": "k",
"type": "uint256"
}
],
"name": "MiMCSponge",
"outputs": [
{
"name": "xL",
"type": "uint256"
},
{
"name": "xR",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "pure",
"type": "function"
}
];
module.exports.createCode = createCode;

View File

@@ -0,0 +1,13 @@
const mimcsponge = require("./mimcsponge.js");
const nRounds = 220;
let S = "[\n";
const cts = mimcsponge.getConstants();
for (let i=0; i<nRounds; i++) {
S = S + cts[i].toString();
if (i<nRounds-1) S = S + ",";
S=S+"\n";
}
S = S + "]\n";
console.log(S);

View File

@@ -0,0 +1,13 @@
const mimcGenContract = require("./mimcsponge_gencontract");
const SEED = "mimcsponge";
let nRounds;
if (typeof process.argv[2] != "undefined") {
nRounds = parseInt(process.argv[2]);
} else {
nRounds = 220;
}
console.log(mimcGenContract.createCode(SEED, nRounds));

View File

@@ -0,0 +1,121 @@
const babyJub = require("./babyjub");
const createBlakeHash = require("blake-hash");
const blake2b = require("blake2b");
const Scalar = require("ffjavascript").Scalar;
const GENPOINT_PREFIX = "PedersenGenerator";
const windowSize = 4;
const nWindowsPerSegment = 50;
exports.hash = pedersenHash;
exports.getBasePoint = getBasePoint;
function baseHash(type, S) {
if (type == "blake") {
return createBlakeHash("blake256").update(S).digest();
} else if (type == "blake2b") {
return Buffer.from(blake2b(32).update(Buffer.from(S)).digest());
}
}
function pedersenHash(msg, options) {
options = options || {};
options.baseHash = options.baseHash || "blake";
const bitsPerSegment = windowSize*nWindowsPerSegment;
const bits = buffer2bits(msg);
const nSegments = Math.floor((bits.length - 1)/(windowSize*nWindowsPerSegment)) +1;
let accP = [babyJub.F.zero,babyJub.F.one];
for (let s=0; s<nSegments; s++) {
let nWindows;
if (s == nSegments-1) {
nWindows = Math.floor(((bits.length - (nSegments - 1)*bitsPerSegment) - 1) / windowSize) +1;
} else {
nWindows = nWindowsPerSegment;
}
let escalar = Scalar.e(0);
let exp = Scalar.e(1);
for (let w=0; w<nWindows; w++) {
let o = s*bitsPerSegment + w*windowSize;
let acc = Scalar.e(1);
for (let b=0; ((b<windowSize-1)&&(o<bits.length)) ; b++) {
if (bits[o]) {
acc = Scalar.add(acc, Scalar.shl(Scalar.e(1), b) );
}
o++;
}
if (o<bits.length) {
if (bits[o]) {
acc = Scalar.neg(acc);
}
o++;
}
escalar = Scalar.add(escalar, Scalar.mul(acc, exp));
exp = Scalar.shl(exp, windowSize+1);
}
if (Scalar.lt(escalar, 0)) {
escalar = Scalar.add( escalar, babyJub.subOrder);
}
accP = babyJub.addPoint(accP, babyJub.mulPointEscalar(getBasePoint(options.baseHash, s), escalar));
}
return babyJub.packPoint(accP);
}
let bases = [];
function getBasePoint(baseHashType, pointIdx) {
if (pointIdx<bases.length) return bases[pointIdx];
let p= null;
let tryIdx = 0;
while (p==null) {
const S = GENPOINT_PREFIX + "_" + padLeftZeros(pointIdx, 32) + "_" + padLeftZeros(tryIdx, 32);
const h = baseHash(baseHashType, S);
h[31] = h[31] & 0xBF; // Set 255th bit to 0 (256th is the signal and 254th is the last possible bit to 1)
p = babyJub.unpackPoint(h);
tryIdx++;
}
const p8 = babyJub.mulPointEscalar(p, 8);
if (!babyJub.inSubgroup(p8)) {
throw new Error("Point not in curve");
}
bases[pointIdx] = p8;
return p8;
}
function padLeftZeros(idx, n) {
let sidx = "" + idx;
while (sidx.length<n) sidx = "0"+sidx;
return sidx;
}
/*
Input a buffer
Returns an array of booleans. 0 is LSB of first byte and so on.
*/
function buffer2bits(buff) {
const res = new Array(buff.length*8);
for (let i=0; i<buff.length; i++) {
const b = buff[i];
res[i*8] = b & 0x01;
res[i*8+1] = b & 0x02;
res[i*8+2] = b & 0x04;
res[i*8+3] = b & 0x08;
res[i*8+4] = b & 0x10;
res[i*8+5] = b & 0x20;
res[i*8+6] = b & 0x40;
res[i*8+7] = b & 0x80;
}
return res;
}

View File

@@ -0,0 +1,21 @@
const pedersenHash = require("./pedersenHash.js");
let nBases;
if (typeof process.argv[2] != "undefined") {
nBases = parseInt(process.argv[2]);
} else {
nBases = 5;
}
let baseHash;
if (typeof process.argv[3] != "undefined") {
baseHash = process.argv[3];
} else {
baseHash = "blake";
}
for (let i=0; i < nBases; i++) {
const p = pedersenHash.getBasePoint(baseHash, i);
console.log(`[${p[0]},${p[1]}]`);
}

View File

@@ -0,0 +1,46 @@
const assert = require("assert");
const Scalar = require("ffjavascript").Scalar;
const ZqField = require("ffjavascript").ZqField;
const { unstringifyBigInts } = require("ffjavascript").utils;
// Prime 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001
const F = new ZqField(Scalar.fromString("21888242871839275222246405745257275088548364400416034343698204186575808495617"));
// Parameters are generated by a reference script https://extgit.iaik.tugraz.at/krypto/hadeshash/-/blob/master/code/generate_parameters_grain.sage
// Used like so: sage generate_parameters_grain.sage 1 0 254 2 8 56 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001
const { C, M } = unstringifyBigInts(require("./poseidon_constants.json"));
// Using recommended parameters from whitepaper https://eprint.iacr.org/2019/458.pdf (table 2, table 8)
// Generated by https://extgit.iaik.tugraz.at/krypto/hadeshash/-/blob/master/code/calc_round_numbers.py
// And rounded up to nearest integer that divides by t
const N_ROUNDS_F = 8;
const N_ROUNDS_P = [56, 57, 56, 60, 60, 63, 64, 63];
const pow5 = a => F.mul(a, F.square(F.square(a, a)));
function poseidon(inputs) {
assert(inputs.length > 0);
assert(inputs.length < N_ROUNDS_P.length - 1);
const t = inputs.length + 1;
const nRoundsF = N_ROUNDS_F;
const nRoundsP = N_ROUNDS_P[t - 2];
let state = [F.zero, ...inputs.map(a => F.e(a))];
for (let r = 0; r < nRoundsF + nRoundsP; r++) {
state = state.map((a, i) => F.add(a, C[t - 2][r * t + i]));
if (r < nRoundsF / 2 || r >= nRoundsF / 2 + nRoundsP) {
state = state.map(a => pow5(a));
} else {
state[0] = pow5(state[0]);
}
state = state.map((_, i) =>
state.reduce((acc, a, j) => F.add(acc, F.mul(M[t - 2][i][j], a)), F.zero)
);
}
return F.normalize(state[0]);
}
module.exports = poseidon;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,208 @@
// Copyright (c) 2018 Jordi Baylina
// License: LGPL-3.0+
//
const Contract = require("./evmasm");
const { unstringifyBigInts } = require("ffjavascript").utils;
const Web3Utils = require("web3-utils");
const { C:K, M } = unstringifyBigInts(require("./poseidon_constants.json"));
const N_ROUNDS_F = 8;
const N_ROUNDS_P = [56, 57, 56, 60, 60, 63, 64, 63];
function toHex256(a) {
let S = a.toString(16);
while (S.length < 64) S="0"+S;
return "0x" + S;
}
function createCode(nInputs) {
if (( nInputs<1) || (nInputs>8)) throw new Error("Invalid number of inputs. Must be 1<=nInputs<=8");
const t = nInputs + 1;
const nRoundsF = N_ROUNDS_F;
const nRoundsP = N_ROUNDS_P[t - 2];
const C = new Contract();
function saveM() {
for (let i=0; i<t; i++) {
for (let j=0; j<t; j++) {
C.push(toHex256(M[t-2][i][j]));
C.push((1+i*t+j)*32);
C.mstore();
}
}
}
function ark(r) { // st, q
for (let i=0; i<t; i++) {
C.dup(t); // q, st, q
C.push(toHex256(K[t-2][r*t+i])); // K, q, st, q
C.dup(2+i); // st[i], K, q, st, q
C.addmod(); // newSt[i], st, q
C.swap(1 + i); // xx, st, q
C.pop();
}
}
function sigma(p) {
// sq, q
C.dup(t); // q, st, q
C.dup(1+p); // st[p] , q , st, q
C.dup(1); // q, st[p] , q , st, q
C.dup(0); // q, q, st[p] , q , st, q
C.dup(2); // st[p] , q, q, st[p] , q , st, q
C.dup(0); // st[p] , st[p] , q, q, st[p] , q , st, q
C.mulmod(); // st2[p], q, st[p] , q , st, q
C.dup(0); // st2[p], st2[p], q, st[p] , q , st, q
C.mulmod(); // st4[p], st[p] , q , st, q
C.mulmod(); // st5[p], st, q
C.swap(1+p);
C.pop(); // newst, q
}
function mix() {
C.label("mix");
for (let i=0; i<t; i++) {
for (let j=0; j<t; j++) {
if (j==0) {
C.dup(i+t); // q, newSt, oldSt, q
C.push((1+i*t+j)*32);
C.mload(); // M, q, newSt, oldSt, q
C.dup(2+i+j); // oldSt[j], M, q, newSt, oldSt, q
C.mulmod(); // acc, newSt, oldSt, q
} else {
C.dup(1+i+t); // q, acc, newSt, oldSt, q
C.push((1+i*t+j)*32);
C.mload(); // M, q, acc, newSt, oldSt, q
C.dup(3+i+j); // oldSt[j], M, q, acc, newSt, oldSt, q
C.mulmod(); // aux, acc, newSt, oldSt, q
C.dup(2+i+t); // q, aux, acc, newSt, oldSt, q
C.swap(2); // acc, aux, q, newSt, oldSt, q
C.addmod(); // acc, newSt, oldSt, q
}
}
}
for (let i=0; i<t; i++) {
C.swap((t -i) + (t -i-1));
C.pop();
}
C.push(0);
C.mload();
C.jmp();
}
// Check selector
C.push("0x0100000000000000000000000000000000000000000000000000000000");
C.push(0);
C.calldataload();
C.div();
C.dup(0);
C.push(Web3Utils.keccak256(`poseidon(uint256[${nInputs}])`).slice(0, 10)); // poseidon(uint256[n])
C.eq();
C.swap(1);
C.push(Web3Utils.keccak256(`poseidon(bytes32[${nInputs}])`).slice(0, 10)); // poseidon(bytes32[n])
C.eq();
C.or();
C.jmpi("start");
C.invalid();
C.label("start");
saveM();
C.push("0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001"); // q
// Load t values from the call data.
// The function has a single array param param
// [Selector (4)] [item1 (32)] [item2 (32)] ....
// Stack positions 0-nInputs.
for (let i=0; i<nInputs; i++) {
C.push(0x04+(0x20*(nInputs-i-1)));
C.calldataload();
}
C.push(0);
for (let i=0; i<nRoundsF+nRoundsP; i++) {
ark(i);
if ((i<nRoundsF/2) || (i>=nRoundsP+nRoundsF/2)) {
for (let j=0; j<t; j++) {
sigma(j);
}
} else {
sigma(0);
}
const strLabel = "aferMix"+i;
C._pushLabel(strLabel);
C.push(0);
C.mstore();
C.jmp("mix");
C.label(strLabel);
}
C.push("0x00");
C.mstore(); // Save it to pos 0;
C.push("0x20");
C.push("0x00");
C.return();
mix();
return C.createTxData();
}
function generateABI(nInputs) {
return [
{
"constant": true,
"inputs": [
{
"internalType": `bytes32[${nInputs}]`,
"name": "input",
"type": `bytes32[${nInputs}]`
}
],
"name": "poseidon",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"payable": false,
"stateMutability": "pure",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"internalType": `uint256[${nInputs}]`,
"name": "input",
"type": `uint256[${nInputs}]`
}
],
"name": "poseidon",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "pure",
"type": "function"
}
];
}
module.exports.generateABI = generateABI;
module.exports.createCode = createCode;

View File

@@ -0,0 +1,13 @@
const poseidonGenContract = require("./poseidon_gencontract");
if (process.argv.length != 3) {
console.log("Usage: node poseidon_gencontract.js [numberOfInputs]");
process.exit(1);
}
const nInputs = Number(process.argv[2]);
console.log(nInputs);
console.log(poseidonGenContract.createCode(nInputs));

View File

@@ -0,0 +1,22 @@
const Poseidon = require("./poseidon.js");
const M = Poseidon.getMatrix();
let S = "[\n ";
for (let i=0; i<M.length; i++) {
const LC = M[i];
S = S + "[\n";
for (let j=0; j<LC.length; j++) {
S = S + " " + M[i][j].toString();
if (j<LC.length-1) S = S + ",";
S = S + "\n";
}
S = S + " ]";
if (i<M.length-1) S = S + ",";
}
S=S+ "\n]\n";
console.log(S);

301
test/modules/src/smt.js Normal file
View File

@@ -0,0 +1,301 @@
const Scalar = require("ffjavascript").Scalar;
const SMTMemDB = require("./smt_memdb");
const {hash0, hash1, F} = require("./smt_hashes_poseidon");
class SMT {
constructor(db, root) {
this.db = db;
this.root = root;
}
_splitBits(_key) {
const res = Scalar.bits(_key);
while (res.length<256) res.push(false);
return res;
}
async update(_key, _newValue) {
const key = Scalar.e(_key);
const newValue = F.e(_newValue);
const resFind = await this.find(key);
const res = {};
res.oldRoot = this.root;
res.oldKey = key;
res.oldValue = resFind.foundValue;
res.newKey = key;
res.newValue = newValue;
res.siblings = resFind.siblings;
const ins = [];
const dels = [];
let rtOld = hash1(key, resFind.foundValue);
let rtNew = hash1(key, newValue);
ins.push([rtNew, [1, key, newValue ]]);
dels.push(rtOld);
const keyBits = this._splitBits(key);
for (let level = resFind.siblings.length-1; level >=0; level--) {
let oldNode, newNode;
const sibling = resFind.siblings[level];
if (keyBits[level]) {
oldNode = [sibling, rtOld];
newNode = [sibling, rtNew];
} else {
oldNode = [rtOld, sibling];
newNode = [rtNew, sibling];
}
rtOld = hash0(oldNode[0], oldNode[1]);
rtNew = hash0(newNode[0], newNode[1]);
dels.push(rtOld);
ins.push([rtNew, newNode]);
}
res.newRoot = rtNew;
await this.db.multiDel(dels);
await this.db.multiIns(ins);
await this.db.setRoot(rtNew);
this.root = rtNew;
return res;
}
async delete(_key) {
const key = Scalar.e(_key);
const resFind = await this.find(key);
if (!resFind.found) throw new Error("Key does not exists");
const res = {
siblings: [],
delKey: key,
delValue: resFind.foundValue
};
const dels = [];
const ins = [];
let rtOld = hash1(key, resFind.foundValue);
let rtNew;
dels.push(rtOld);
let mixed;
if (resFind.siblings.length > 0) {
const record = await this.db.get(resFind.siblings[resFind.siblings.length - 1]);
if ((record.length == 3)&&(F.eq(record[0], F.one))) {
mixed = false;
res.oldKey = record[1];
res.oldValue = record[2];
res.isOld0 = false;
rtNew = resFind.siblings[resFind.siblings.length - 1];
} else if (record.length == 2) {
mixed = true;
res.oldKey = key;
res.oldValue = F.zero;
res.isOld0 = true;
rtNew = F.zero;
} else {
throw new Error("Invalid node. Database corrupted");
}
} else {
rtNew = F.zero;
res.oldKey = key;
res.oldValue = F.zero;
res.isOld0 = true;
}
const keyBits = this._splitBits(key);
for (let level = resFind.siblings.length-1; level >=0; level--) {
let newSibling = resFind.siblings[level];
if ((level == resFind.siblings.length-1)&&(!res.isOld0)) {
newSibling = F.zero;
}
const oldSibling = resFind.siblings[level];
if (keyBits[level]) {
rtOld = hash0(oldSibling, rtOld);
} else {
rtOld = hash0(rtOld, oldSibling);
}
dels.push(rtOld);
if (!F.isZero(newSibling)) {
mixed = true;
}
if (mixed) {
res.siblings.unshift(resFind.siblings[level]);
let newNode;
if (keyBits[level]) {
newNode = [newSibling, rtNew];
} else {
newNode = [rtNew, newSibling];
}
rtNew = hash0(newNode[0], newNode[1]);
ins.push([rtNew, newNode]);
}
}
await this.db.multiIns(ins);
await this.db.setRoot(rtNew);
this.root = rtNew;
await this.db.multiDel(dels);
res.newRoot = rtNew;
res.oldRoot = rtOld;
return res;
}
async insert(_key, _value) {
const key = Scalar.e(_key);
const value = F.e(_value);
let addedOne = false;
const res = {};
res.oldRoot = this.root;
const newKeyBits = this._splitBits(key);
let rtOld;
const resFind = await this.find(key);
if (resFind.found) throw new Error("Key already exists");
res.siblings = resFind.siblings;
let mixed;
if (!resFind.isOld0) {
const oldKeyits = this._splitBits(resFind.notFoundKey);
for (let i= res.siblings.length; oldKeyits[i] == newKeyBits[i]; i++) {
res.siblings.push(F.zero);
}
rtOld = hash1(resFind.notFoundKey, resFind.notFoundValue);
res.siblings.push(rtOld);
addedOne = true;
mixed = false;
} else if (res.siblings.length >0) {
mixed = true;
rtOld = F.zero;
}
const inserts = [];
const dels = [];
let rt = hash1(key, value);
inserts.push([rt,[1, key, value]] );
for (let i=res.siblings.length-1; i>=0; i--) {
if ((i<res.siblings.length-1)&&(!F.isZero(res.siblings[i]))) {
mixed = true;
}
if (mixed) {
const oldSibling = resFind.siblings[i];
if (newKeyBits[i]) {
rtOld = hash0(oldSibling, rtOld);
} else {
rtOld = hash0(rtOld, oldSibling);
}
dels.push(rtOld);
}
let newRt;
if (newKeyBits[i]) {
newRt = hash0(res.siblings[i], rt);
inserts.push([newRt,[res.siblings[i], rt]] );
} else {
newRt = hash0(rt, res.siblings[i]);
inserts.push([newRt,[rt, res.siblings[i]]] );
}
rt = newRt;
}
if (addedOne) res.siblings.pop();
while ((res.siblings.length>0) && (F.isZero(res.siblings[res.siblings.length-1]))) {
res.siblings.pop();
}
res.oldKey = resFind.notFoundKey;
res.oldValue = resFind.notFoundValue;
res.newRoot = rt;
res.isOld0 = resFind.isOld0;
await this.db.multiIns(inserts);
await this.db.setRoot(rt);
this.root = rt;
await this.db.multiDel(dels);
return res;
}
async find(key) {
const keyBits = this._splitBits(key);
return await this._find(key, keyBits, this.root, 0);
}
async _find(key, keyBits, root, level) {
if (typeof root === "undefined") root = this.root;
let res;
if (F.isZero(root)) {
res = {
found: false,
siblings: [],
notFoundKey: key,
notFoundValue: F.zero,
isOld0: true
};
return res;
}
const record = await this.db.get(root);
if ((record.length==3)&&(F.eq(record[0],F.one))) {
if (F.eq(record[1],key)) {
res = {
found: true,
siblings: [],
foundValue: record[2],
isOld0: false
};
} else {
res = {
found: false,
siblings: [],
notFoundKey: record[1],
notFoundValue: record[2],
isOld0: false
};
}
} else {
if (keyBits[level] == 0) {
res = await this._find(key, keyBits, record[0], level+1);
res.siblings.unshift(record[1]);
} else {
res = await this._find(key, keyBits, record[1], level+1);
res.siblings.unshift(record[0]);
}
}
return res;
}
}
async function loadFromFile(fileName) {
}
async function newMemEmptyTrie() {
const db = new SMTMemDB();
const rt = await db.getRoot();
const smt = new SMT(db, rt);
return smt;
}
module.exports.loadFromFile = loadFromFile;
module.exports.newMemEmptyTrie = newMemEmptyTrie;
module.exports.SMT = SMT;
module.exports.SMTMemDB = SMTMemDB;

View File

@@ -0,0 +1,12 @@
const mimc7 = require("./mimc7");
const bigInt = require("big-integer");
exports.hash0 = function (left, right) {
return mimc7.multiHash(left, right);
};
exports.hash1 = function(key, value) {
return mimc7.multiHash([key, value], bigInt.one);
};
exports.F = mimc7.F;

View File

@@ -0,0 +1,18 @@
const ZqField = require("ffjavascript").ZqField;
const Scalar = require("ffjavascript").Scalar;
const poseidon = require("./poseidon");
const F = new ZqField(Scalar.fromString("21888242871839275222246405745257275088548364400416034343698204186575808495617"));
exports.hash0 = function (left, right) {
return poseidon([left, right]);
};
exports.hash1 = function(key, value) {
return poseidon([key, value, F.one]);
};
exports.F = F;

View File

@@ -0,0 +1,63 @@
const Scalar = require("ffjavascript").Scalar;
const ZqField = require("ffjavascript").ZqField;
// Prime 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001
const F = new ZqField(Scalar.fromString("21888242871839275222246405745257275088548364400416034343698204186575808495617"));
class SMTMemDb {
constructor() {
this.nodes = {};
this.root = F.zero;
}
async getRoot() {
return this.root;
}
_key2str(k) {
// const keyS = bigInt(key).leInt2Buff(32).toString("hex");
const keyS = k.toString();
return keyS;
}
_normalize(n) {
for (let i=0; i<n.length; i++) {
n[i] = F.e(n[i]);
}
}
async get(key) {
const keyS = this._key2str(key);
return this.nodes[keyS];
}
async multiGet(keys) {
const promises = [];
for (let i=0; i<keys.length; i++) {
promises.push(this.get(keys[i]));
}
return await Promise.all(promises);
}
async setRoot(rt) {
this.root = rt;
}
async multiIns(inserts) {
for (let i=0; i<inserts.length; i++) {
const keyS = this._key2str(inserts[i][0]);
this._normalize(inserts[i][1]);
this.nodes[keyS] = inserts[i][1];
}
}
async multiDel(dels) {
for (let i=0; i<dels.length; i++) {
const keyS = this._key2str(dels[i]);
delete this.nodes[keyS];
}
}
}
module.exports = SMTMemDb;

5
test/modules/tsc.sh Normal file
View File

@@ -0,0 +1,5 @@
tsc maci-domainobjs.ts \
--esModuleInterop true \
--moduleResolution node \
--module commonjs \
--target es2020