Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
13 changes: 13 additions & 0 deletions std/algebra/algopts/algopts.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import "fmt"
type algebraCfg struct {
NbScalarBits int
FoldMulti bool
UseSafe bool
}

// AlgebraOption allows modifying algebraic operation behaviour.
Expand Down Expand Up @@ -44,6 +45,18 @@ func WithFoldingScalarMul() AlgebraOption {
}
}

// WithUseSafe forces the use of safe addition formulas for scalar
// multiplication.
func WithUseSafe() AlgebraOption {
return func(ac *algebraCfg) error {
if ac.UseSafe {
return fmt.Errorf("WithUseSafe already set")
}
ac.UseSafe = true
return nil
}
}

// NewConfig applies all given options and returns a configuration to be used.
func NewConfig(opts ...AlgebraOption) (*algebraCfg, error) {
ret := new(algebraCfg)
Expand Down
5 changes: 5 additions & 0 deletions std/algebra/emulated/sw_emulated/hints.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ func decomposeScalarG1(mod *big.Int, inputs []*big.Int, outputs []*big.Int) erro
outputs[2].Sub(outputs[2], inputs[0])
outputs[2].Div(outputs[2], inputs[2])

// return:
// output0 = s0 mod r
// output1 = s1 mod r
// output3 = |s0| mod r
// output4 = |s1| mod r
outputs[3].Set(outputs[0])
if outputs[0].Sign() == -1 {
outputs[3].Neg(outputs[0])
Expand Down
110 changes: 76 additions & 34 deletions std/algebra/emulated/sw_emulated/point.go
Original file line number Diff line number Diff line change
Expand Up @@ -456,34 +456,58 @@ func (c *Curve[B, S]) Lookup2(b0, b1 frontend.Variable, i0, i1, i2, i3 *AffinePo
// ScalarMul computes s * p and returns it. It doesn't modify p nor s.
// This function doesn't check that the p is on the curve. See AssertIsOnCurve.
//
// ScalarMul calls ScalarMulGeneric or scalarMulGLV depending on whether an efficient endomorphism is available.
// ScalarMul calls scalarMulGeneric or scalarMulGLV depending on whether an efficient endomorphism is available.
func (c *Curve[B, S]) ScalarMul(p *AffinePoint[B], s *emulated.Element[S], opts ...algopts.AlgebraOption) *AffinePoint[B] {
if c.eigenvalue != nil && c.thirdRootOne != nil {
return c.scalarMulGLV(p, s, opts...)

} else {
return c.ScalarMulGeneric(p, s, opts...)
return c.scalarMulGeneric(p, s, opts...)

}
}

// scalarMulGLV computes s * Q using an efficient endomorphism and returns it. It doesn't modify Q nor s.
// It implements algorithm 1 of [Halo] (see Section 6.2 and appendix C).
//
// ⚠️ The scalar s must be nonzero and the point Q different from (0,0).
// ⚠️ The scalar s must be nonzero and the point Q different from (0,0) unless [algopts.WithUseSafe] is set.
// (0,0) is not on the curve but we conventionally take it as the
// neutral/infinity point as per the [EVM].
//
// [Halo]: https://eprint.iacr.org/2019/1021.pdf
// [EVM]: https://ethereum.github.io/yellowpaper/paper.pdf
func (c *Curve[B, S]) scalarMulGLV(Q *AffinePoint[B], s *emulated.Element[S], opts ...algopts.AlgebraOption) *AffinePoint[B] {
cfg, err := algopts.NewConfig(opts...)
if err != nil {
panic(err)
}
var st S
frModulus := c.scalarApi.Modulus()
addFn := c.Add
var selector frontend.Variable
if cfg.UseSafe {
addFn = c.AddUnified
// if Q=(0,0) we assign a dummy (1,1) to Q and continue
selector = c.api.And(c.baseApi.IsZero(&Q.X), c.baseApi.IsZero(&Q.Y))
one := c.baseApi.One()
Q = c.Select(selector, &AffinePoint[B]{X: *one, Y: *one}, Q)
}
sd, err := c.scalarApi.NewHint(decomposeScalarG1, 5, s, c.eigenvalue, frModulus)
if err != nil {
panic(fmt.Sprintf("compute GLV decomposition: %v", err))
}
s1, s2 := sd[0], sd[1]
s1, s2, s3, s4 := sd[0], sd[1], sd[3], sd[4]

c.scalarApi.AssertIsEqual(
c.scalarApi.Add(s1, c.scalarApi.Mul(s2, c.eigenvalue)),
c.scalarApi.Add(s, c.scalarApi.Mul(frModulus, sd[2])),
)
selector1 := c.scalarApi.IsZero(c.scalarApi.Sub(sd[3], s1))
selector2 := c.scalarApi.IsZero(c.scalarApi.Sub(sd[4], s2))
// s1, s2 can be negative (bigints) in the hint, but are reduced mod r in
// the circuit. So we return in the hint both s1, s2 and s3=|s1|, s4=|s2|.
// In-circuit we compare s1 and s3, s2 and s4 and negate the point when a
// corresponding scalar is naegative.
selector1 := c.scalarApi.IsZero(c.scalarApi.Sub(s3, s1))
selector2 := c.scalarApi.IsZero(c.scalarApi.Sub(s4, s2))

var Acc, B1 *AffinePoint[B]
// precompute -Q, -Φ(Q), Φ(Q)
Expand All @@ -502,8 +526,8 @@ func (c *Curve[B, S]) scalarMulGLV(Q *AffinePoint[B], s *emulated.Element[S], op
// Acc = Q + Φ(Q)
Acc = c.Add(tableQ[1], tablePhiQ[1])

s1 = c.scalarApi.Select(selector1, s1, sd[3])
s2 = c.scalarApi.Select(selector2, s2, sd[4])
s1 = c.scalarApi.Select(selector1, s1, s3)
s2 = c.scalarApi.Select(selector2, s2, s4)

s1bits := c.scalarApi.ToBits(s1)
s2bits := c.scalarApi.ToBits(s2)
Expand All @@ -524,18 +548,26 @@ func (c *Curve[B, S]) scalarMulGLV(Q *AffinePoint[B], s *emulated.Element[S], op

}

tableQ[0] = c.Add(tableQ[0], Acc)
// i = 0
// When cfg.UseSafe is set, we use AddUnified instead of Add. This means
// when s=0 then Acc=(0,0) because AddUnified(Q, -Q) = (0,0).
tableQ[0] = addFn(tableQ[0], Acc)
Acc = c.Select(s1bits[0], Acc, tableQ[0])
tablePhiQ[0] = c.Add(tablePhiQ[0], Acc)
tablePhiQ[0] = addFn(tablePhiQ[0], Acc)
Acc = c.Select(s2bits[0], Acc, tablePhiQ[0])

if cfg.UseSafe {
zero := c.baseApi.Zero()
Acc = c.Select(selector, &AffinePoint[B]{X: *zero, Y: *zero}, Acc)
}

return Acc
}

// ScalarMulGeneric computes s * p and returns it. It doesn't modify p nor s.
// scalarMulGeneric computes s * p and returns it. It doesn't modify p nor s.
// This function doesn't check that the p is on the curve. See AssertIsOnCurve.
//
// ✅ p can can be (0,0) and s can be 0.
// ⚠️ p must not be (0,0) and s must not be 0, unless [algopts.WithUseSafe] option is set.
// (0,0) is not on the curve but we conventionally take it as the
// neutral/infinity point as per the [EVM].
//
Expand All @@ -551,16 +583,20 @@ func (c *Curve[B, S]) scalarMulGLV(Q *AffinePoint[B], s *emulated.Element[S], op
// [ELM03]: https://arxiv.org/pdf/math/0208038.pdf
// [EVM]: https://ethereum.github.io/yellowpaper/paper.pdf
// [Joye07]: https://www.iacr.org/archive/ches2007/47270135/47270135.pdf
func (c *Curve[B, S]) ScalarMulGeneric(p *AffinePoint[B], s *emulated.Element[S], opts ...algopts.AlgebraOption) *AffinePoint[B] {
func (c *Curve[B, S]) scalarMulGeneric(p *AffinePoint[B], s *emulated.Element[S], opts ...algopts.AlgebraOption) *AffinePoint[B] {
cfg, err := algopts.NewConfig(opts...)
if err != nil {
panic(fmt.Sprintf("parse opts: %v", err))
}

// if p=(0,0) we assign a dummy (0,1) to p and continue
selector := c.api.And(c.baseApi.IsZero(&p.X), c.baseApi.IsZero(&p.Y))
one := c.baseApi.One()
p = c.Select(selector, &AffinePoint[B]{X: *one, Y: *one}, p)
addFn := c.Add
var selector frontend.Variable
if cfg.UseSafe {
// if p=(0,0) we assign a dummy (0,1) to p and continue
selector = c.api.And(c.baseApi.IsZero(&p.X), c.baseApi.IsZero(&p.Y))
one := c.baseApi.One()
p = c.Select(selector, &AffinePoint[B]{X: *one, Y: *one}, p)
addFn = c.AddUnified
}

var st S
sr := c.scalarApi.Reduce(s)
Expand All @@ -586,13 +622,15 @@ func (c *Curve[B, S]) ScalarMulGeneric(p *AffinePoint[B], s *emulated.Element[S]
R0 = c.Select(sBits[n-1], Rb, R0)

// i = 0
// we use AddUnified here instead of add so that when s=0, res=(0,0)
// because AddUnified(p, -p) = (0,0)
R0 = c.Select(sBits[0], R0, c.AddUnified(R0, c.Neg(p)))
// When cfg.UseSafe is set, we use AddUnified instead of Add. This means
// when s=0 then Acc=(0,0) because AddUnified(Q, -Q) = (0,0).
R0 = c.Select(sBits[0], R0, addFn(R0, c.Neg(p)))

// if p=(0,0), return (0,0)
zero := c.baseApi.Zero()
R0 = c.Select(selector, &AffinePoint[B]{X: *zero, Y: *zero}, R0)
if cfg.UseSafe {
// if p=(0,0), return (0,0)
zero := c.baseApi.Zero()
R0 = c.Select(selector, &AffinePoint[B]{X: *zero, Y: *zero}, R0)
}

return R0
}
Expand Down Expand Up @@ -638,14 +676,14 @@ func (c *Curve[B, S]) jointScalarMulGLV(Q, R *AffinePoint[B], s, t *emulated.Ele
// err is non-nil only for invalid number of inputs
panic(err)
}
s1, s2 := sd[0], sd[1]
s1, s2, s3, s4 := sd[0], sd[1], sd[3], sd[4]

td, err := c.scalarApi.NewHint(decomposeScalarG1, 5, t, c.eigenvalue, frModulus)
if err != nil {
// err is non-nil only for invalid number of inputs
panic(err)
}
t1, t2 := td[0], td[1]
t1, t2, t3, t4 := td[0], td[1], td[3], td[4]

c.scalarApi.AssertIsEqual(
c.scalarApi.Add(s1, c.scalarApi.Mul(s2, c.eigenvalue)),
Expand All @@ -655,10 +693,14 @@ func (c *Curve[B, S]) jointScalarMulGLV(Q, R *AffinePoint[B], s, t *emulated.Ele
c.scalarApi.Add(t1, c.scalarApi.Mul(t2, c.eigenvalue)),
c.scalarApi.Add(t, c.scalarApi.Mul(frModulus, td[2])),
)
selector1 := c.scalarApi.IsZero(c.scalarApi.Sub(sd[3], s1))
selector2 := c.scalarApi.IsZero(c.scalarApi.Sub(sd[4], s2))
selector3 := c.scalarApi.IsZero(c.scalarApi.Sub(td[3], t1))
selector4 := c.scalarApi.IsZero(c.scalarApi.Sub(td[4], t2))
// s1, s2 can be negative (bigints) in the hint, but are reduced mod r in
// the circuit. So we return in the hint both s1, s2 and s3=|s1|, s4=|s2|.
// In-circuit we compare s1 and s3, s2 and s4 and negate the point when a
// corresponding scalar is naegative. Respectively for t1, t2, t3, t4.
selector1 := c.scalarApi.IsZero(c.scalarApi.Sub(s3, s1))
selector2 := c.scalarApi.IsZero(c.scalarApi.Sub(s4, s2))
selector3 := c.scalarApi.IsZero(c.scalarApi.Sub(t3, t1))
selector4 := c.scalarApi.IsZero(c.scalarApi.Sub(t4, t2))

// precompute -Q, -Φ(Q), Φ(Q)
var tableQ, tablePhiQ [2]*AffinePoint[B]
Expand Down Expand Up @@ -711,10 +753,10 @@ func (c *Curve[B, S]) jointScalarMulGLV(Q, R *AffinePoint[B], s, t *emulated.Ele
// Acc = Q + R + Φ(Q) + Φ(R)
Acc := c.Add(tableS[1], tablePhiS[1])

s1 = c.scalarApi.Select(selector1, s1, sd[3])
s2 = c.scalarApi.Select(selector2, s2, sd[4])
t1 = c.scalarApi.Select(selector3, t1, td[3])
t2 = c.scalarApi.Select(selector4, t2, td[4])
s1 = c.scalarApi.Select(selector1, s1, s3)
s2 = c.scalarApi.Select(selector2, s2, s4)
t1 = c.scalarApi.Select(selector3, t1, t3)
t2 = c.scalarApi.Select(selector4, t2, t4)

s1bits := c.scalarApi.ToBits(s1)
s2bits := c.scalarApi.ToBits(s2)
Expand Down
4 changes: 2 additions & 2 deletions std/algebra/emulated/sw_emulated/point_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -716,7 +716,7 @@ func (c *ScalarMulEdgeCasesTest[T, S]) Define(api frontend.API) error {
if err != nil {
return err
}
res := cr.ScalarMulGeneric(&c.P, &c.S)
res := cr.ScalarMul(&c.P, &c.S, algopts.WithUseSafe())
cr.AssertIsEqual(res, &c.R)
return nil
}
Expand Down Expand Up @@ -1000,7 +1000,7 @@ func (c *ScalarMulTestBounded[T, S]) Define(api frontend.API) error {
if err != nil {
return err
}
res := cr.ScalarMulGeneric(&c.P, &c.S, algopts.WithNbScalarBits(c.bits))
res := cr.scalarMulGeneric(&c.P, &c.S, algopts.WithNbScalarBits(c.bits))
cr.AssertIsEqual(res, &c.Q)
return nil
}
Expand Down
12 changes: 12 additions & 0 deletions std/algebra/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,27 @@ type Curve[FR emulated.FieldParams, G1El G1ElementT] interface {

// ScalarMul returns the scalar multiplication of the point by a scalar. It
// does not modify the inputs.
//
// Depending on the implementation the scalar multiplication may be
// incomplete for zero scalar or point at infinity. To allow the exceptional
// case use the [algopts.WithUseSafe] option.
ScalarMul(*G1El, *emulated.Element[FR], ...algopts.AlgebraOption) *G1El

// ScalarMulBase returns the scalar multiplication of the curve base point
// by a scalar. It does not modify the scalar.
//
// Depending on the implementation the scalar multiplication may be
// incomplete for zero scalar. To allow the exceptional case use the
// [algopts.WithUseSafe] option.
ScalarMulBase(*emulated.Element[FR], ...algopts.AlgebraOption) *G1El

// MultiScalarMul computes the sum ∑ s_i P_i for the input
// scalars s_i and points P_i. It returns an error if the input lengths
// mismatch.
//
// Depending on the implementation the scalar multiplication may be
// incomplete for zero scalar or point at infinity. To allow the exceptional
// case use the [algopts.WithUseSafe] option.
MultiScalarMul([]*G1El, []*emulated.Element[FR], ...algopts.AlgebraOption) (*G1El, error)

// MarshalG1 returns the binary decomposition G1.X || G1.Y. It matches the
Expand Down
3 changes: 2 additions & 1 deletion std/evmprecompiles/07-bnmul.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package evmprecompiles

import (
"github.com/consensys/gnark/frontend"
"github.com/consensys/gnark/std/algebra/algopts"
"github.com/consensys/gnark/std/algebra/emulated/sw_emulated"
"github.com/consensys/gnark/std/math/emulated"
)
Expand All @@ -15,6 +16,6 @@ func ECMul(api frontend.API, P *sw_emulated.AffinePoint[emulated.BN254Fp], u *em
panic(err)
}
// Check that P is on the curve (done in the zkEVM ⚠️ )
res := curve.ScalarMulGeneric(P, u)
res := curve.ScalarMul(P, u, algopts.WithUseSafe())
return res
}