MIT IAP 2023 Modern Zero Knowledge Cryptography课程笔记
Lecture 2: Circom 1 (Brain Gu)
- autogen “intermediate” values
pragma circom 2.1.6;
template Example () {
signal input x1;
signal input x2;
signal input x3;
signal input x4;
signal y1;
signal y2;
signal output out;
// how to autogen "intermediate" values
// can use any operators
y1 <-- x1 + x2;
y2 <-- y1 * x3;
out <-- y2 - x4;
// constrains
// only can use + and *
y1 === x1 + x2;
y2 === y1 * x3;
out + x4 === y2;
}
component main { public [ x1,x2,x3,x4] } = Example();
/* INPUT = {
"x1": "2",
"x2": "4",
"x3": "8",
"x4": "5"
} */
- Some improvement with “syntactic sugar”
pragma circom 2.1.6;
template Example () {
signal input x1;
signal input x2;
signal input x3;
signal input x4;
signal y1;
signal y2;
signal output out;
//y1 <-- x1 + x2;
//y1 === x1 + x2;
y1 <== x1 + x2;
//y2 <-- y1 * x3;
//y2 === y1 * x3;
y2 <== y1 * x3;
//out <-- y2 - x4;
//out + x4 === y2;
out <== y2 - x4;
}
component main { public [ x1,x2,x3,x4] } = Example();
/* INPUT = {
"x1": "2",
"x2": "4",
"x3": "8",
"x4": "5"
} */
- Example: import other code
pragma circom 2.1.6;
include "circomlib/poseidon.circom";
// include "https://github.com/0xPARC/circom-secp256k1/blob/master/circuits/bigint.circom";
template Example () {
signal input a;
signal input b;
signal output c;
var unused = 4;
c <== a * b;
assert(a > 2);
component hash = Poseidon(2);
hash.inputs[0] <== a;
hash.inputs[1] <== b;
log("hash", hash.out);
}
component main { public [ a ] } = Example();
/* INPUT = {
"a": "5",
"b": "77"
} */
- Exercises
//IsZero
//Parameters: none
//Input signal(s): in
//Output signal(s): out
template IsZero() {
signal input in;
signal output out;
signal inv;
inv <-- in!=0 ? 1/in : 0;
out <== -in*inv +1;
in*out === 0;
}
//IsEqual
//Parameters: none
//Input signal(s): in[2]
//Output signal(s): out
template IsEqual() {
signal input in[2];
signal output out;
component isz = IsZero();
in[1] - in[0] ==> isz.in;
isz.out ==> out;
}
//Selector
//Parameters: nChoices
//Input signal(s): in[nChoices], index
//Output: out
template CalculateTotal(n) {
signal input in[n];
signal output out;
signal sums[n];
sums[0] <== in[0];
for (var i = 1; i < n; i++) {
sums[i] <== sums[i-1] + in[i]
}
out <== sums[n-1];
}
template QuinSelector(choices) {
signal input in[choices];
signal input index;
signal output out;
// Ensure that index < choices
component lessThan = LessThan(4);
lessThan.in[0] <== index;
lessThan.in[1] <== choices;
lessThan.out === 1;
component calcTotal = CalculateTotal(choices);
component eqs[choices];
// For each item, check whether its index equals the input index.
for (var i = 0; i < choices; i ++) {
eqs[i] = IsEqual();
eqs[i].in[0] <== i;
eqs[i].in[1] <== index;
// eqs[i].out is 1 if the index matches. As such, at most one input to
// calcTotal is not 0.
calcTotal.in[i] <== eqs[i].out * in[i];
}
// Returns 0 + 0 + 0 + item
out <== calcTotal.out;
}
//IsNegative
template Num2Bits(n) {
signal input in;
signal output out[n];
var lc1=0;
var e2=1;
for (var i = 0; i<n; i++) {
out[i] <-- (in >> i) & 1;
out[i] * (out[i] -1 ) === 0;
lc1 += out[i] * e2;
e2 = e2+e2;
}
lc1 === in;
}
template CompConstant(ct) {
signal input in[254];
signal output out;
signal parts[127];
signal sout;
var clsb;
var cmsb;
var slsb;
var smsb;
var sum=0;
var b = (1 << 128) -1;
var a = 1;
var e = 1;
var i;
for (i=0;i<127; i++) {
clsb = (ct >> (i*2)) & 1;
cmsb = (ct >> (i*2+1)) & 1;
slsb = in[i*2];
smsb = in[i*2+1];
if ((cmsb==0)&&(clsb==0)) {
parts[i] <== -b*smsb*slsb + b*smsb + b*slsb;
} else if ((cmsb==0)&&(clsb==1)) {
parts[i] <== a*smsb*slsb - a*slsb + b*smsb - a*smsb + a;
} else if ((cmsb==1)&&(clsb==0)) {
parts[i] <== b*smsb*slsb - a*smsb + a;
} else {
parts[i] <== -a*smsb*slsb + a;
}
sum = sum + parts[i];
b = b -e;
a = a +e;
e = e*2;
}
sout <== sum;
component num2bits = Num2Bits(135);
num2bits.in <== sout;
out <== num2bits.out[127];
}
template Sign() {
signal input in[254];
signal output sign;
component comp = CompConstant(10944121435919637611123202872628637544274182200208017171849102093287904247808);
var i;
for (i=0; i<254; i++) {
comp.in[i] <== in[i];
}
sign <== comp.out;
}
//LessThan
//Parameters: none
//Input signal(s): in[2]. Assume that it is known ahead of time that these are at most 2252−1.
//Output signal(s): out
//Specification: If in[0] is strictly less than in[1], out should be 1. Otherwise, out should be 0.
//Extension 1: If you know that the input signals are at most 2^k - 1 (k ≤ 252), how can you reduce the total number of constraints that this circuit requires? Write a version of this circuit parametrized in k.
//Extension 2: Write LessEqThan (tests if in[0] is ≤ in[1]), GreaterThan, and GreaterEqThan
template LessThan(n) {
assert(n <= 252);
signal input in[2];
signal output out;
component n2b = Num2Bits(n+1);
n2b.in <== in[0]+ (1<<n) - in[1];
out <== 1-n2b.out[n];
}
// N is the number of bits the input have.
// The MSF is the sign bit.
template LessEqThan(n) {
signal input in[2];
signal output out;
component lt = LessThan(n);
lt.in[0] <== in[0];
lt.in[1] <== in[1]+1;
lt.out ==> out;
}
// N is the number of bits the input have.
// The MSF is the sign bit.
template GreaterThan(n) {
signal input in[2];
signal output out;
component lt = LessThan(n);
lt.in[0] <== in[1];
lt.in[1] <== in[0];
lt.out ==> out;
}
// N is the number of bits the input have.
// The MSF is the sign bit.
template GreaterEqThan(n) {
signal input in[2];
signal output out;
component lt = LessThan(n);
lt.in[0] <== in[1];
lt.in[1] <== in[0]+1;
lt.out ==> out;
}
//IntegerDivide
//NOTE: This circuit is pretty hard!
//Parameters: nbits. Use assert to assert that this is at most 126!
//Input signal(s): dividend, divisor
//Output signal(s): remainder, quotient
// input: dividend and divisor field elements in [0, sqrt(p))
// output: remainder and quotient field elements in [0, p-1] and [0, sqrt(p)
// Haven't thought about negative divisor yet. Not needed.
// -8 % 5 = 2. [-8 -> 8. 8 % 5 -> 3. 5 - 3 -> 2.]
// (-8 - 2) // 5 = -2
// -8 + 2 * 5 = 2
// check: 2 - 2 * 5 = -8
template Modulo(divisor_bits, SQRT_P) {
signal input dividend; // -8
signal input divisor; // 5
signal output remainder; // 2
signal output quotient; // -2
component is_neg = IsNegative();
is_neg.in <== dividend;
signal output is_dividend_negative;
is_dividend_negative <== is_neg.out;
signal output dividend_adjustment;
dividend_adjustment <== 1 + is_dividend_negative * -2; // 1 or -1
signal output abs_dividend;
abs_dividend <== dividend * dividend_adjustment; // 8
signal output raw_remainder;
raw_remainder <-- abs_dividend % divisor;
signal output neg_remainder;
neg_remainder <-- divisor - raw_remainder;
if (is_dividend_negative == 1 && raw_remainder != 0) {
remainder <-- neg_remainder;
} else {
remainder <-- raw_remainder;
}
quotient <-- (dividend - remainder) / divisor; // (-8 - 2) / 5 = -2.
dividend === divisor * quotient + remainder; // -8 = 5 * -2 + 2.
component rp = MultiRangeProof(3, 128);
rp.in[0] <== divisor;
rp.in[1] <== quotient;
rp.in[2] <== dividend;
rp.max_abs_value <== SQRT_P;
// check that 0 <= remainder < divisor
component remainderUpper = LessThan(divisor_bits);
remainderUpper.in[0] <== remainder;
remainderUpper.in[1] <== divisor;
remainderUpper.out === 1;
}