Consensys / gnark

gnark is a fast zk-SNARK library that offers a high-level API to design circuits. The library is open source and developed under the Apache 2.0 license
https://hackmd.io/@gnark
Apache License 2.0
1.44k stars 369 forks source link

bug: `Rangechecker.Check` passes even when `bits int` is 1 less than bit-length of `v variable` in case when `bits` is odd #897

Closed ultrainstinct30 closed 1 year ago

ultrainstinct30 commented 1 year ago

Description

After creating a new instance of Rangechecker by using rangecheck.New(api), calling Check function using this instance passes even when the input bits is 1 less than the actual bit-length of v when bits is odd.

Expected Behavior

Check should fail

Actual Behavior

Check passes, implying v has bit-length bits

Steps to Reproduce

import (
    "testing"

    "github.com/consensys/gnark-crypto/ecc"
    "github.com/consensys/gnark/frontend"
    "github.com/consensys/gnark/frontend/cs/r1cs"
    "github.com/consensys/gnark/std/rangecheck"
    "github.com/consensys/gnark/test"
)

type TestRangeCheckCircuit struct {
    I1 frontend.Variable
    N  int
}

func (circuit *TestRangeCheckCircuit) Define(api frontend.API) error {
    rangeChecker := rangecheck.New(api)
    rangeChecker.Check(circuit.I1, circuit.N)
    return nil
}

func TestRangeCheck_Odd(t *testing.T) {
    assert := test.NewAssert(t)

    type testData struct {
        i1 uint64
        n  int
    }

    tests := []testData{}
    for i := 128; i < 256; i++ { //[128, 256) all have bit-length 8
        tests = append(tests, testData{i1: uint64(i), n: 7})
    }

    for _, t_i := range tests {
        circuit := TestRangeCheckCircuit{
            N: t_i.n,
        }
        r1cs, err := frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &circuit)
        if err != nil {
            t.Fatal("Error in compiling circuit: ", err)
        }
        var witness TestRangeCheckCircuit
        witness.I1 = t_i.i1
        w, err := frontend.NewWitness(&witness, ecc.BN254.ScalarField())
        if err != nil {
            t.Fatal("Error in witness: ", err, "\n test: ", t_i)
        }
        err = r1cs.IsSolved(w)
        if err != nil {
            t.Fatal("Circuit not solved: ", err, "\n test: ", t_i)
        }

        assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BN254))
    }
}

func TestRangeCheck_Even(t *testing.T) {
    assert := test.NewAssert(t)

    type testData struct {
        i1 uint64
        n  int
    }

    tests := []testData{}
    for i := 64; i < 128; i++ { //[64, 128) all have bit-length 7
        tests = append(tests, testData{i1: uint64(i), n: 6})
    }

    for _, t_i := range tests {
        circuit := TestRangeCheckCircuit{
            N: t_i.n,
        }
        r1cs, err := frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &circuit)
        if err != nil {
            t.Fatal("Error in compiling circuit: ", err)
        }
        var witness TestRangeCheckCircuit
        witness.I1 = t_i.i1
        w, err := frontend.NewWitness(&witness, ecc.BN254.ScalarField())
        if err != nil {
            t.Fatal("Error in witness: ", err, "\n test: ", t_i)
        }
        err = r1cs.IsSolved(w)
        if err != nil {
            t.Fatal("Circuit not solved: ", err, "\n test: ", t_i)
        }

        assert.CheckCircuit(&circuit, test.WithValidAssignment(&witness), test.WithCurves(ecc.BN254))
    }
}

TestRangeCheck_Odd output: ok TestRangeCheck_Even output: Circuit not solved: constraint #0 is not satisfied: 1 ⋅ 0 != 64 test: {64 6}

Your Environment

ivokub commented 1 year ago

Thank you for the report. I will have a look at it promptly.