From 663d8c0ad8dad37fa5c2f68553cfee5e2f9f4a65 Mon Sep 17 00:00:00 2001 From: Marcin Kostrzewa Date: Thu, 7 Sep 2023 12:47:29 +0200 Subject: [PATCH] add a reducedness check to binary reps --- prover/circuit_utils.go | 37 ++++++++++++++++++++++++++++++++++--- prover/deletion_circuit.go | 6 +++--- prover/insertion_circuit.go | 8 ++++---- 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/prover/circuit_utils.go b/prover/circuit_utils.go index 861957f..a5015a6 100644 --- a/prover/circuit_utils.go +++ b/prover/circuit_utils.go @@ -44,6 +44,35 @@ func VerifyProof(api frontend.API, proofSet, helper []frontend.Variable) fronten return sum } +// ReducedModRCheck Checks a little-endian array of bits asserting that it represents a number that +// is less than the field modulus R. +type ReducedModRCheck struct { + Input []frontend.Variable +} + +func (r *ReducedModRCheck) DefineGadget(api abstractor.API) []frontend.Variable { + field := api.Compiler().Field() + if len(r.Input) < field.BitLen() { + // input is shorter than the field, so it's definitely reduced + return []frontend.Variable{} + } + var failed frontend.Variable = 0 // we already know number is > R + var succeeded frontend.Variable = 0 // we already know number is < R + for i := len(r.Input) - 1; i >= 0; i-- { + api.AssertIsBoolean(r.Input[i]) + if field.Bit(i) == 0 { + // if number is not already < R, a 1 in this position means it's > R + failed = api.Select(succeeded, 0, api.Or(r.Input[i], failed)) + } else { + bitNeg := api.Sub(1, r.Input[i]) + // if number isn't already > R, a 0 in this position means it's < R + succeeded = api.Select(failed, 0, api.Or(bitNeg, succeeded)) + } + } + api.AssertIsEqual(succeeded, 1) + return []frontend.Variable{} +} + // SwapBitArrayEndianness Swaps the endianness of the bit pattern in bits, // returning the result in newBits. // @@ -73,13 +102,15 @@ func SwapBitArrayEndianness(bits []frontend.Variable) (newBits []frontend.Variab return newBits, nil } -// ToBinaryBigEndian converts the provided variable to the corresponding bit -// pattern using big-endian byte ordering. +// ToReducedBinaryBigEndian converts the provided variable to the corresponding bit +// pattern using big-endian byte ordering. It also makes sure to pick the smallest +// binary representation (i.e. one that is reduced modulo scalar field order). // // Raises a bitPatternLengthError if the number of bits in variable is not a // whole number of bytes. -func ToBinaryBigEndian(variable frontend.Variable, size int, api frontend.API) (bitsBigEndian []frontend.Variable, err error) { +func ToReducedBinaryBigEndian(variable frontend.Variable, size int, api frontend.API) (bitsBigEndian []frontend.Variable, err error) { bitsLittleEndian := api.ToBinary(variable, size) + abstractor.CallGadget(api, &ReducedModRCheck{Input: bitsLittleEndian}) return SwapBitArrayEndianness(bitsLittleEndian) } diff --git a/prover/deletion_circuit.go b/prover/deletion_circuit.go index 7b81b86..99741b9 100644 --- a/prover/deletion_circuit.go +++ b/prover/deletion_circuit.go @@ -34,20 +34,20 @@ func (circuit *DeletionMbuCircuit) Define(api frontend.API) error { var err error for i := 0; i < circuit.BatchSize; i++ { - bits, err = ToBinaryBigEndian(circuit.DeletionIndices[i], 32, api) + bits, err = ToReducedBinaryBigEndian(circuit.DeletionIndices[i], 32, api) if err != nil { return err } kh.Write(bits...) } - bits, err = ToBinaryBigEndian(circuit.PreRoot, 256, api) + bits, err = ToReducedBinaryBigEndian(circuit.PreRoot, 256, api) if err != nil { return err } kh.Write(bits...) - bits, err = ToBinaryBigEndian(circuit.PostRoot, 256, api) + bits, err = ToReducedBinaryBigEndian(circuit.PostRoot, 256, api) if err != nil { return err } diff --git a/prover/insertion_circuit.go b/prover/insertion_circuit.go index 5a1f5ff..d0d5129 100644 --- a/prover/insertion_circuit.go +++ b/prover/insertion_circuit.go @@ -37,26 +37,26 @@ func (circuit *InsertionMbuCircuit) Define(api frontend.API) error { // We convert all the inputs to the keccak hash to use big-endian (network) byte // ordering so that it agrees with Solidity. This ensures that we don't have to // perform the conversion inside the contract and hence save on gas. - bits, err = ToBinaryBigEndian(circuit.StartIndex, 32, api) + bits, err = ToReducedBinaryBigEndian(circuit.StartIndex, 32, api) if err != nil { return err } kh.Write(bits...) - bits, err = ToBinaryBigEndian(circuit.PreRoot, 256, api) + bits, err = ToReducedBinaryBigEndian(circuit.PreRoot, 256, api) if err != nil { return err } kh.Write(bits...) - bits, err = ToBinaryBigEndian(circuit.PostRoot, 256, api) + bits, err = ToReducedBinaryBigEndian(circuit.PostRoot, 256, api) if err != nil { return err } kh.Write(bits...) for i := 0; i < circuit.BatchSize; i++ { - bits, err = ToBinaryBigEndian(circuit.IdComms[i], 256, api) + bits, err = ToReducedBinaryBigEndian(circuit.IdComms[i], 256, api) if err != nil { return err }