Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds std/math/bits #276

Merged
merged 17 commits into from
Mar 8, 2022
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 0 additions & 29 deletions backend/hint/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,10 @@ var (
// corresponds to checking if a == 0 (for which the function returns 1) or a
// != 0 (for which the function returns 0).
IsZero = NewStaticHint(isZero)

// IthBit returns the i-tb bit the input. The function expects exactly two
// integer inputs i and n, takes the little-endian bit representation of n and
// returns its i-th bit.
IthBit = NewStaticHint(ithBit)

// NBits returns the n first bits of the input. Expects one argument: n.
NBits = NewStaticHint(nBits)
)

func init() {
Register(IsZero)
Register(IthBit)
Register(NBits)
}

func isZero(curveID ecc.ID, inputs []*big.Int, results []*big.Int) error {
Expand All @@ -47,22 +37,3 @@ func isZero(curveID ecc.ID, inputs []*big.Int, results []*big.Int) error {

return nil
}

func ithBit(_ ecc.ID, inputs []*big.Int, results []*big.Int) error {
result := results[0]
if !inputs[1].IsUint64() {
result.SetUint64(0)
return nil
}

result.SetUint64(uint64(inputs[0].Bit(int(inputs[1].Uint64()))))
return nil
}

func nBits(_ ecc.ID, inputs []*big.Int, results []*big.Int) error {
n := inputs[0]
for i := 0; i < len(results); i++ {
results[i].SetUint64(uint64(n.Bit(i)))
}
return nil
}
3 changes: 2 additions & 1 deletion examples/exponentiate/exponentiate.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package exponentiate

import (
"github.com/consensys/gnark/frontend"
"github.com/consensys/gnark/std/math/bits"
)

// Circuit y == x**e
Expand All @@ -38,7 +39,7 @@ func (circuit *Circuit) Define(api frontend.API) error {

// specify constraints
output := frontend.Variable(1)
bits := api.ToBinary(circuit.E, bitSize)
bits := bits.ToBinary(api, circuit.E, bits.WithNbDigits(bitSize))

for i := 0; i < len(bits); i++ {
if i != 0 {
Expand Down
77 changes: 3 additions & 74 deletions frontend/cs/r1cs/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/consensys/gnark/frontend"
"github.com/consensys/gnark/frontend/compiled"
"github.com/consensys/gnark/frontend/schema"
"github.com/consensys/gnark/std/math/bits"
)

// ---------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -253,7 +254,6 @@ func (system *r1cs) Inverse(i1 frontend.Variable) frontend.Variable {
//
// The result in in little endian (first bit= lsb)
func (system *r1cs) ToBinary(i1 frontend.Variable, n ...int) []frontend.Variable {

// nbBits
nbBits := system.BitLen()
if len(n) == 1 {
Expand All @@ -263,83 +263,12 @@ func (system *r1cs) ToBinary(i1 frontend.Variable, n ...int) []frontend.Variable
}
}

vars, _ := system.toVariables(i1)
a := vars[0]

// if a is a constant, work with the big int value.
if c, ok := system.ConstantValue(a); ok {
b := make([]frontend.Variable, nbBits)
for i := 0; i < len(b); i++ {
b[i] = system.toVariable(c.Bit(i))
}
return b
}

return system.toBinary(a, nbBits, false)
}

// toBinary is equivalent to ToBinary, exept the returned bits are NOT boolean constrained.
func (system *r1cs) toBinary(a compiled.LinearExpression, nbBits int, unsafe bool) []frontend.Variable {

if _, ok := system.ConstantValue(a); ok {
return system.ToBinary(a, nbBits)
}

// allocate the resulting frontend.Variables and bit-constraint them
sb := make([]frontend.Variable, nbBits)
var c big.Int
c.SetUint64(1)

bits, err := system.NewHint(hint.NBits, nbBits, a)
if err != nil {
panic(err)
}

for i := 0; i < nbBits; i++ {
sb[i] = system.Mul(bits[i], c)
c.Lsh(&c, 1)
if !unsafe {
system.AssertIsBoolean(bits[i])
}
}

//var Σbi compiled.LinearExpression
var Σbi frontend.Variable
if nbBits == 1 {
Σbi = sb[0]
} else if nbBits == 2 {
Σbi = system.Add(sb[0], sb[1])
} else {
Σbi = system.Add(sb[0], sb[1], sb[2:]...)
}
system.AssertIsEqual(Σbi, a)

// record the constraint Σ (2**i * b[i]) == a
return bits

return bits.ToBinary(system, i1, bits.WithNbDigits(nbBits))
}

// FromBinary packs b, seen as a fr.Element in little endian
func (system *r1cs) FromBinary(_b ...frontend.Variable) frontend.Variable {
b, _ := system.toVariables(_b...)

// res = Σ (2**i * b[i])

var res, v frontend.Variable
res = system.toVariable(0) // no constraint is recorded

var c big.Int
c.SetUint64(1)

L := make(compiled.LinearExpression, len(b))
for i := 0; i < len(L); i++ {
v = system.Mul(c, b[i]) // no constraint is recorded
res = system.Add(v, res) // no constraint is recorded
system.AssertIsBoolean(b[i]) // ensures the b[i]'s are boolean
c.Lsh(&c, 1)
}

return res
return bits.FromBinary(system, _b...)
}

// Xor compute the XOR between two frontend.Variables
Expand Down
5 changes: 3 additions & 2 deletions frontend/cs/r1cs/api_assertions.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/consensys/gnark/frontend"
"github.com/consensys/gnark/frontend/compiled"
"github.com/consensys/gnark/internal/utils"
"github.com/consensys/gnark/std/math/bits"
)

// AssertIsEqual adds an assertion in the constraint system (i1 == i2)
Expand Down Expand Up @@ -92,7 +93,7 @@ func (system *r1cs) mustBeLessOrEqVar(a, bound compiled.LinearExpression) {

nbBits := system.BitLen()

aBits := system.toBinary(a, nbBits, true)
aBits := bits.ToBinary(system, a, bits.WithNbDigits(nbBits), bits.WithUnconstrainedOutputs())
boundBits := system.ToBinary(bound, nbBits)

p := make([]frontend.Variable, nbBits+1)
Expand Down Expand Up @@ -145,7 +146,7 @@ func (system *r1cs) mustBeLessOrEqCst(a compiled.LinearExpression, bound big.Int

// note that at this stage, we didn't boolean-constraint these new variables yet
// (as opposed to ToBinary)
aBits := system.toBinary(a, nbBits, true)
aBits := bits.ToBinary(system, a, bits.WithNbDigits(nbBits), bits.WithUnconstrainedOutputs())

// t trailing bits in the bound
t := 0
Expand Down
66 changes: 3 additions & 63 deletions frontend/cs/scs/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/consensys/gnark/frontend"
"github.com/consensys/gnark/frontend/compiled"
"github.com/consensys/gnark/frontend/schema"
"github.com/consensys/gnark/std/math/bits"
)

// Add returns res = i1+i2+...in
Expand Down Expand Up @@ -174,7 +175,6 @@ func (system *scs) Inverse(i1 frontend.Variable) frontend.Variable {
//
// The result in in little endian (first bit= lsb)
func (system *scs) ToBinary(i1 frontend.Variable, n ...int) []frontend.Variable {

// nbBits
nbBits := system.BitLen()
if len(n) == 1 {
Expand All @@ -184,72 +184,12 @@ func (system *scs) ToBinary(i1 frontend.Variable, n ...int) []frontend.Variable
}
}

// if a is a constant, work with the big int value.
if c, ok := system.ConstantValue(i1); ok {
b := make([]frontend.Variable, nbBits)
for i := 0; i < len(b); i++ {
b[i] = c.Bit(i)
}
return b
}

a := i1.(compiled.Term)
return system.toBinary(a, nbBits, false)
}

func (system *scs) toBinary(a compiled.Term, nbBits int, unsafe bool) []frontend.Variable {

// allocate the resulting frontend.Variables and bit-constraint them
sb := make([]frontend.Variable, nbBits)
var c big.Int
c.SetUint64(1)

bits, err := system.NewHint(hint.NBits, nbBits, a)
if err != nil {
panic(err)
}

for i := 0; i < nbBits; i++ {
sb[i] = system.Mul(bits[i], c)
c.Lsh(&c, 1)
if !unsafe {
system.AssertIsBoolean(bits[i])
}
}

//var Σbi compiled.Term
// TODO we can save a constraint here
var Σbi frontend.Variable
if nbBits == 1 {
Σbi = sb[0]
} else if nbBits == 2 {
Σbi = system.Add(sb[0], sb[1])
} else {
Σbi = system.Add(sb[0], sb[1], sb[2:]...)
}
system.AssertIsEqual(Σbi, a)

// record the constraint Σ (2**i * b[i]) == a
return bits

return bits.ToBinary(system, i1, bits.WithNbDigits(nbBits))
}

// FromBinary packs b, seen as a fr.Element in little endian
func (system *scs) FromBinary(b ...frontend.Variable) frontend.Variable {
_b := make([]frontend.Variable, len(b))
var c big.Int
c.SetUint64(1)
for i := 0; i < len(b); i++ {
_b[i] = system.Mul(b[i], c)
c.Lsh(&c, 1)
}
if len(b) == 1 {
return b[0]
}
if len(b) == 1 {
return system.Add(_b[0], _b[1])
}
return system.Add(_b[0], _b[1], _b[2:]...)
return bits.FromBinary(system, b...)
}

// Xor returns a ^ b
Expand Down
5 changes: 3 additions & 2 deletions frontend/cs/scs/api_assertions.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/consensys/gnark/frontend"
"github.com/consensys/gnark/frontend/compiled"
"github.com/consensys/gnark/internal/utils"
"github.com/consensys/gnark/std/math/bits"
)

// AssertIsEqual fails if i1 != i2
Expand Down Expand Up @@ -104,7 +105,7 @@ func (system *scs) mustBeLessOrEqVar(a compiled.Term, bound compiled.Term) {

nbBits := system.BitLen()

aBits := system.toBinary(a, nbBits, true)
aBits := bits.ToBinary(system, a, bits.WithNbDigits(nbBits), bits.WithUnconstrainedOutputs())
boundBits := system.ToBinary(bound, nbBits)

p := make([]frontend.Variable, nbBits+1)
Expand Down Expand Up @@ -162,7 +163,7 @@ func (system *scs) mustBeLessOrEqCst(a compiled.Term, bound big.Int) {

// note that at this stage, we didn't boolean-constraint these new variables yet
// (as opposed to ToBinary)
aBits := system.toBinary(a, nbBits, true)
aBits := bits.ToBinary(system, a, bits.WithNbDigits(nbBits), bits.WithUnconstrainedOutputs())

// t trailing bits in the bound
t := 0
Expand Down
10 changes: 9 additions & 1 deletion std/hints.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,17 @@ import (
"github.com/consensys/gnark/backend/hint"
"github.com/consensys/gnark/std/algebra/sw_bls12377"
"github.com/consensys/gnark/std/algebra/sw_bls24315"
"github.com/consensys/gnark/std/math/bits"
)

// GetHints return std hints that are always injected in gnark solvers
func GetHints() []hint.Function {
return []hint.Function{sw_bls24315.DecomposeScalar, sw_bls12377.DecomposeScalar}
return []hint.Function{
sw_bls24315.DecomposeScalar,
sw_bls12377.DecomposeScalar,
bits.NTrits,
bits.NNAF,
bits.IthBit,
bits.NBits,
}
}
72 changes: 72 additions & 0 deletions std/math/bits/conversion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package bits

import (
"errors"

"github.com/consensys/gnark/frontend"
)

type Base uint8

const (
Binary Base = 2
Ternary Base = 3
Quinary Base = 5
gbotrel marked this conversation as resolved.
Show resolved Hide resolved
)

// ToBase converts b in given base
func ToBase(api frontend.API, base Base, v frontend.Variable, opts ...BaseConversionOption) []frontend.Variable {
switch base {
case Binary:
return toBinary(api, v, opts...)
case Ternary:
return toTernary(api, v, opts...)
default:
panic("not implemented")
}
}

// FromBase compute from a set of digits its canonical representation
// For example for base 2, it returns Σbi = Σ (2**i * b[i])
func FromBase(api frontend.API, base Base, digits ...frontend.Variable) frontend.Variable {
if len(digits) == 0 {
panic("FromBase needs at least 1 digit")
}
switch base {
case Binary:
return fromBinary(api, digits)
case Ternary:
return fromTernary(api, digits)
default:
panic("not implemented")
}
}

type BaseConversionConfig struct {
gbotrel marked this conversation as resolved.
Show resolved Hide resolved
NbDigits int
Unconstrained bool
}

type BaseConversionOption func(opt *BaseConversionConfig) error

// WithNbDigits set the resulting number of digits to be used in the base conversion
// nbDigits must be > 0
func WithNbDigits(nbDigits int) BaseConversionOption {
return func(opt *BaseConversionConfig) error {
if nbDigits <= 0 {
return errors.New("nbDigits <= 0")
}
opt.NbDigits = nbDigits
return nil
}
}

// WithUnconstrainedOutputs sets the bit conversion API to NOT constrain the output
gbotrel marked this conversation as resolved.
Show resolved Hide resolved
// This is UNSAFE but is useful when the outputs are already constrained by other circuit
// constraints
func WithUnconstrainedOutputs() BaseConversionOption {
return func(opt *BaseConversionConfig) error {
opt.Unconstrained = true
return nil
}
}
gbotrel marked this conversation as resolved.
Show resolved Hide resolved
Loading