RareSkills / zero-knowledge-puzzles

Exercises to learn the syntax of Circom and create EVM compatible zero knowledge programs.
https://www.rareskills.io/zk-bootcamp
GNU General Public License v3.0
261 stars 83 forks source link

Equality test passes even if the third input is different #5

Closed atiselsts closed 1 week ago

atiselsts commented 1 year ago

The tests in Equality.js are very limited, just two test cases. I built a circuit that checks just the first 2 inputs a[0]and a[1] for equality, and was surprised to see that it passed these tests.

I suggest adding more test cases such as [2, 2, 0] in the input.

supernovahs commented 1 year ago

Can u share an example , where the test passes ?

atiselsts commented 1 year ago

Let's say the input is [2, 2, 0]. The expected output is 0 . An implementation that compares just the 2 first input signals, and outputs 1 on the example is going to pass your test set.

I wrote a more complete set of tests. Can make a PR if you agree.

    const expectedOutput = 1;
    witness = await circuit.calculateWitness({"a":[2,2,2]},true);
    assert(Fr.eq(Fr.e(witness[0]), Fr.e(1)));
    assert(Fr.eq(Fr.e(witness[1]), Fr.e(expectedOutput)));

    const expectedOutput2 = 0;
    witness = await circuit.calculateWitness({"a":[1,0,1]},true);
    assert(Fr.eq(Fr.e(witness[0]), Fr.e(1)));
    assert(Fr.eq(Fr.e(witness[1]), Fr.e(expectedOutput2)));

    const expectedOutput3 = 0;
    witness = await circuit.calculateWitness({"a":[3,3,0]},true);
    assert(Fr.eq(Fr.e(witness[0]), Fr.e(1)));
    assert(Fr.eq(Fr.e(witness[1]), Fr.e(expectedOutput3)));

    const expectedOutput4 = 1;
    witness = await circuit.calculateWitness({"a":[0,0,0]},true);
    assert(Fr.eq(Fr.e(witness[0]), Fr.e(1)));
    assert(Fr.eq(Fr.e(witness[1]), Fr.e(expectedOutput4)));

    const expectedOutput5 = 0;
    witness = await circuit.calculateWitness({"a":[1,2,2]},true);
    assert(Fr.eq(Fr.e(witness[0]), Fr.e(1)));
    assert(Fr.eq(Fr.e(witness[1]), Fr.e(expectedOutput5)));

    const expectedOutput6 = 0;
    witness = await circuit.calculateWitness({"a":[0,1,2]},true);
    assert(Fr.eq(Fr.e(witness[0]), Fr.e(1)));
    assert(Fr.eq(Fr.e(witness[1]), Fr.e(expectedOutput5)));
devdacian commented 2 months ago

I have created PR25 to resolve this issue which I also encountered.

Which the current Equality test suite, the following Circuit which only checks the first 2 elements passes all the tests:

pragma circom 2.1.4;

template IsZero() {
    signal input in;
    signal output out;

    signal inv;

    inv <-- in!=0 ? 1/in : 0;

    out <== -in*inv +1;
    in*out === 0;
}

template IsEqual() {
    signal input in[2];
    signal output out;

    component isz = IsZero();

    in[1] - in[0] ==> isz.in;

    isz.out ==> out;
}

template Equality() {
    signal input a[3];
    signal output c; // 1 Equal, 0 False

    // a[0] == a[1] ?
    component check1 = IsEqual();
    check1.in[0] <== a[0];
    check1.in[1] <== a[1];

    c <== check1.out;
}

However this test will fail once we add in the additional 2 test cases:

        witness = await circuit.calculateWitness({"a":[1,1,0]},true);
        let expectedOutput3 = 0;
        assert(Fr.eq(Fr.e(witness[0]), Fr.e(1)));
        assert(Fr.eq(Fr.e(witness[1]), Fr.e(expectedOutput3)));

        witness = await circuit.calculateWitness({"a":[2,1,1]},true);
        let expectedOutput4 = 0;
        assert(Fr.eq(Fr.e(witness[0]), Fr.e(1)));
        assert(Fr.eq(Fr.e(witness[1]), Fr.e(expectedOutput4)));

A Circuit which correctly passes the expanded test suite must check all 3 values; one valid implementation:

pragma circom 2.1.4;

template IsZero() {
    signal input in;
    signal output out;

    signal inv;

    inv <-- in!=0 ? 1/in : 0;

    out <== -in*inv +1;
    in*out === 0;
}

template IsEqual() {
    signal input in[2];
    signal output out;

    component isz = IsZero();

    in[1] - in[0] ==> isz.in;

    isz.out ==> out;
}

// Input 3 values using 'a'(array of length 3) and check if they all are equal.
// Return using signal 'c'.
template Equality() {
    signal input a[3];
    signal output c; // 1 Equal, 0 False

    // a[0] == a[1] ?
    component check1 = IsEqual();
    check1.in[0] <== a[0];
    check1.in[1] <== a[1];

    // a[1] == a[2] ?
    component check2 = IsEqual();
    check2.in[0] <== a[1];
    check2.in[1] <== a[2];

    // constraint inspired by
    // https://github.com/iden3/circomlib/blob/master/circuits/gates.circom#L29-L35
    //
    // a[0] == a[1] && a[1] == a[2]
    c <== check1.out * check2.out;
}

component main = Equality();
tanim0la commented 1 week ago

Merged PR #26 already addressed this issue