Skip to content
Open
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
59 changes: 34 additions & 25 deletions std/algebra/emulated/sw_bls12381/hints.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,41 +223,50 @@ func millerLoopAndCheckFinalExpHint(nativeMod *big.Int, nativeInputs, nativeOutp
var P bls12381.G1Affine
var Q bls12381.G2Affine
var previous bls12381.E12
n := len(inputs) - 12
p := make([]bls12381.G1Affine, 0, n/6)
q := make([]bls12381.G2Affine, 0, n/6)
// first one-third is G1 points
for k := 0; k < n/3; k += 2 {
P.X.SetBigInt(inputs[k])
P.Y.SetBigInt(inputs[k+1])
p = append(p, P)
}
// subsequent two-thirds are G2 points
for k := n / 3; k < n; k += 4 {
Q.X.A0.SetBigInt(inputs[k])
Q.X.A1.SetBigInt(inputs[k+1])
Q.Y.A0.SetBigInt(inputs[k+2])
Q.Y.A1.SetBigInt(inputs[k+3])
q = append(q, Q)
}

P.X.SetBigInt(inputs[0])
P.Y.SetBigInt(inputs[1])
Q.X.A0.SetBigInt(inputs[2])
Q.X.A1.SetBigInt(inputs[3])
Q.Y.A0.SetBigInt(inputs[4])
Q.Y.A1.SetBigInt(inputs[5])

previous.C0.B0.A0.SetBigInt(inputs[6])
previous.C0.B0.A1.SetBigInt(inputs[7])
previous.C0.B1.A0.SetBigInt(inputs[8])
previous.C0.B1.A1.SetBigInt(inputs[9])
previous.C0.B2.A0.SetBigInt(inputs[10])
previous.C0.B2.A1.SetBigInt(inputs[11])
previous.C1.B0.A0.SetBigInt(inputs[12])
previous.C1.B0.A1.SetBigInt(inputs[13])
previous.C1.B1.A0.SetBigInt(inputs[14])
previous.C1.B1.A1.SetBigInt(inputs[15])
previous.C1.B2.A0.SetBigInt(inputs[16])
previous.C1.B2.A1.SetBigInt(inputs[17])
previous.C0.B0.A0.SetBigInt(inputs[n])
previous.C0.B0.A1.SetBigInt(inputs[n+1])
previous.C0.B1.A0.SetBigInt(inputs[n+2])
previous.C0.B1.A1.SetBigInt(inputs[n+3])
previous.C0.B2.A0.SetBigInt(inputs[n+4])
previous.C0.B2.A1.SetBigInt(inputs[n+5])
previous.C1.B0.A0.SetBigInt(inputs[n+6])
previous.C1.B0.A1.SetBigInt(inputs[n+7])
previous.C1.B1.A0.SetBigInt(inputs[n+8])
previous.C1.B1.A1.SetBigInt(inputs[n+9])
previous.C1.B2.A0.SetBigInt(inputs[n+10])
previous.C1.B2.A1.SetBigInt(inputs[n+11])

if previous.IsZero() {
return errors.New("previous Miller loop result is zero")
}

lines := bls12381.PrecomputeLines(Q)
millerLoop, err := bls12381.MillerLoopFixedQ(
[]bls12381.G1Affine{P},
[][2][len(bls12381.LoopCounter) - 1]bls12381.LineEvaluationAff{lines},
)
lines := make([][2][len(bls12381.LoopCounter) - 1]bls12381.LineEvaluationAff, 0, len(q))
for _, qi := range q {
lines = append(lines, bls12381.PrecomputeLines(qi))
}
millerLoop, err := bls12381.MillerLoopFixedQ(p, lines)
if err != nil {
return err
}
millerLoop.Conjugate(&millerLoop)

millerLoop.Mul(&millerLoop, &previous)

residueWitnessInv, scalingFactor := finalExpWitness(&millerLoop)
Expand Down
52 changes: 38 additions & 14 deletions std/algebra/emulated/sw_bls12381/pairing.go
Original file line number Diff line number Diff line change
Expand Up @@ -813,17 +813,44 @@ func (pr *Pairing) MillerLoopAndMul(P *G1Affine, Q *G2Affine, previous *GTEl) (*
//
// [On Proving Pairings]: https://eprint.iacr.org/2024/640.pdf
func (pr *Pairing) AssertMillerLoopAndFinalExpIsOne(P *G1Affine, Q *G2Affine, previous *GTEl) {
t2 := pr.millerLoopAndFinalExpResult([]*G1Affine{P}, []*G2Affine{Q}, previous)
pr.AssertIsEqual(t2, pr.Ext12.One())
}

// AssertMultiMillerLoopAndFinalExpIsOne computes the Miller loop between
// multiple P and Q pairs and multiplies it in 𝔽p¹² by previous and
// returns a boolean indicating if the result lies in the same equivalence
// class as the reduced pairing purported to be 1. This check replaces the
// final exponentiation step in-circuit and follows Section 4 of
// [On Proving Pairings] paper by A. Novakovic and L. Eagen.
//
// [On Proving Pairings]: https://eprint.iacr.org/2024/640.pdf
func (pr *Pairing) AssertMultiMillerLoopAndFinalExpIsOne(P []*G1Affine, Q []*G2Affine, previous *GTEl) {
t2 := pr.millerLoopAndFinalExpResult(P, Q, previous)
pr.AssertIsEqual(t2, pr.Ext12.One())
}

// millerLoopAndFinalExpResult computes the Miller loop between P and Q,
// multiplies it in 𝔽p¹² by previous and returns the result.
func (pr *Pairing) millerLoopAndFinalExpResult(P *G1Affine, Q *G2Affine, previous *GTEl) *GTEl {
func (pr *Pairing) millerLoopAndFinalExpResult(P []*G1Affine, Q []*G2Affine, previous *GTEl) *GTEl {
nP := len(P)
nQ := len(Q)
if nP == 0 || nP != nQ {
return nil
}
tower := pr.ToTower(previous)

inputs := make([]*baseEl, 0, 2*nP+4*nQ+12)
for _, p := range P {
inputs = append(inputs, &p.X, &p.Y)
}
for _, q := range Q {
inputs = append(inputs, &q.P.X.A0, &q.P.X.A1, &q.P.Y.A0, &q.P.Y.A1)
}
inputs = append(inputs, tower[0], tower[1], tower[2], tower[3], tower[4], tower[5], tower[6], tower[7], tower[8], tower[9], tower[10], tower[11])

// hint the non-residue witness
hint, err := pr.curveF.NewHint(millerLoopAndCheckFinalExpHint, 18, &P.X, &P.Y, &Q.P.X.A0, &Q.P.X.A1, &Q.P.Y.A0, &Q.P.Y.A1, tower[0], tower[1], tower[2], tower[3], tower[4], tower[5], tower[6], tower[7], tower[8], tower[9], tower[10], tower[11])
hint, err := pr.curveF.NewHint(millerLoopAndCheckFinalExpHint, 18, inputs...)
if err != nil {
// err is non-nil only for invalid number of inputs
panic(err)
Expand Down Expand Up @@ -859,18 +886,16 @@ func (pr *Pairing) millerLoopAndFinalExpResult(P *G1Affine, Q *G2Affine, previou
A11: *pr.curveF.Zero(),
}

if Q.Lines == nil {
Qlines := pr.computeLines(&Q.P)
Q.Lines = &Qlines
lines := make([]lineEvaluations, nQ)
for i := range Q {
if Q[i].Lines == nil {
Qlines := pr.computeLines(&Q[i].P)
Q[i].Lines = &Qlines
}
lines[i] = *Q[i].Lines
}
lines := *Q.Lines

res, err := pr.millerLoopLines(
[]*G1Affine{P},
[]lineEvaluations{lines},
residueWitnessInv,
false,
)
res, err := pr.millerLoopLines(P, lines, residueWitnessInv, false)
if err != nil {
return nil
}
Expand All @@ -891,7 +916,6 @@ func (pr *Pairing) millerLoopAndFinalExpResult(P *G1Affine, Q *G2Affine, previou
res = pr.Ext12.Mul(res, t0)

return res

}

// IsMillerLoopAndFinalExpOne computes the Miller loop between P and Q,
Expand All @@ -903,7 +927,7 @@ func (pr *Pairing) millerLoopAndFinalExpResult(P *G1Affine, Q *G2Affine, previou
//
// [On Proving Pairings]: https://eprint.iacr.org/2024/640.pdf
func (pr *Pairing) IsMillerLoopAndFinalExpOne(P *G1Affine, Q *G2Affine, previous *GTEl) frontend.Variable {
t2 := pr.millerLoopAndFinalExpResult(P, Q, previous)
t2 := pr.millerLoopAndFinalExpResult([]*G1Affine{P}, []*G2Affine{Q}, previous)

res := pr.IsEqual(t2, pr.Ext12.One())
return res
Expand Down
47 changes: 47 additions & 0 deletions std/algebra/emulated/sw_bls12381/pairing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,53 @@ func (c *IsOnGroupCircuit) Define(api frontend.API) error {
return nil
}

type MultiMillerLoopAndFinalExpCircuit struct {
Prev GTEl
P1, P2, P3 G1Affine
Q1, Q2, Q3 G2Affine
}

func (c *MultiMillerLoopAndFinalExpCircuit) Define(api frontend.API) error {
pairing, err := NewPairing(api)
if err != nil {
return fmt.Errorf("new pairing: %w", err)
}
pairing.AssertMultiMillerLoopAndFinalExpIsOne(
[]*G1Affine{&c.P1, &c.P2, &c.P3},
[]*G2Affine{&c.Q1, &c.Q2, &c.Q3},
&c.Prev,
)
return nil
}

func TestMultiMillerLoopAndFinalExpIsOneTestSolve(t *testing.T) {
assert := test.NewAssert(t)
p1, q1 := randomG1G2Affines()
p2, q2 := randomG1G2Affines()
var np1, np2 bls12381.G1Affine
np1.Neg(&p1)
np2.Neg(&p2)

lines := bls12381.PrecomputeLines(q1)
previous, err := bls12381.MillerLoopFixedQ(
[]bls12381.G1Affine{p1},
[][2][len(bls12381.LoopCounter) - 1]bls12381.LineEvaluationAff{lines},
)
assert.NoError(err)
previous.Conjugate(&previous)

witness := MultiMillerLoopAndFinalExpCircuit{
Prev: NewGTEl(previous),
P1: NewG1Affine(np1),
P2: NewG1Affine(np2),
P3: NewG1Affine(p2),
Q1: NewG2Affine(q1),
Q2: NewG2Affine(q2),
Q3: NewG2Affine(q2),
}
assert.NoError(test.IsSolved(&MultiMillerLoopAndFinalExpCircuit{}, &witness, ecc.BN254.ScalarField()))
}

func TestIsOnG1(t *testing.T) {
assert := test.NewAssert(t)
assert.Run(func(assert *test.Assert) {
Expand Down
62 changes: 37 additions & 25 deletions std/algebra/emulated/sw_bn254/hints.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,39 +202,51 @@ func millerLoopAndCheckFinalExpHint(nativeMod *big.Int, nativeInputs, nativeOutp
// This follows section 4.3.2 of https://eprint.iacr.org/2024/640.pdf
return emulated.UnwrapHint(nativeInputs, nativeOutputs,
func(mod *big.Int, inputs, outputs []*big.Int) error {
var previous bn254.E12
var P bn254.G1Affine
var Q bn254.G2Affine
var previous bn254.E12
n := len(inputs) - 12 // inputs for points
p := make([]bn254.G1Affine, 0, n/6)
q := make([]bn254.G2Affine, 0, n/6)
// first one-third is G1 points
for k := 0; k < n/3; k += 2 {
P.X.SetBigInt(inputs[k])
P.Y.SetBigInt(inputs[k+1])
p = append(p, P)
}
// subsequent two-thirds are G2 points
for k := n / 3; k < n; k += 4 {
Q.X.A0.SetBigInt(inputs[k])
Q.X.A1.SetBigInt(inputs[k+1])
Q.Y.A0.SetBigInt(inputs[k+2])
Q.Y.A1.SetBigInt(inputs[k+3])
q = append(q, Q)
}

P.X.SetBigInt(inputs[0])
P.Y.SetBigInt(inputs[1])
Q.X.A0.SetBigInt(inputs[2])
Q.X.A1.SetBigInt(inputs[3])
Q.Y.A0.SetBigInt(inputs[4])
Q.Y.A1.SetBigInt(inputs[5])

previous.C0.B0.A0.SetBigInt(inputs[6])
previous.C0.B0.A1.SetBigInt(inputs[7])
previous.C0.B1.A0.SetBigInt(inputs[8])
previous.C0.B1.A1.SetBigInt(inputs[9])
previous.C0.B2.A0.SetBigInt(inputs[10])
previous.C0.B2.A1.SetBigInt(inputs[11])
previous.C1.B0.A0.SetBigInt(inputs[12])
previous.C1.B0.A1.SetBigInt(inputs[13])
previous.C1.B1.A0.SetBigInt(inputs[14])
previous.C1.B1.A1.SetBigInt(inputs[15])
previous.C1.B2.A0.SetBigInt(inputs[16])
previous.C1.B2.A1.SetBigInt(inputs[17])
// previous Miller loop result is passed in the last 12 inputs
previous.C0.B0.A0.SetBigInt(inputs[n])
previous.C0.B0.A1.SetBigInt(inputs[n+1])
previous.C0.B1.A0.SetBigInt(inputs[n+2])
previous.C0.B1.A1.SetBigInt(inputs[n+3])
previous.C0.B2.A0.SetBigInt(inputs[n+4])
previous.C0.B2.A1.SetBigInt(inputs[n+5])
previous.C1.B0.A0.SetBigInt(inputs[n+6])
previous.C1.B0.A1.SetBigInt(inputs[n+7])
previous.C1.B1.A0.SetBigInt(inputs[n+8])
previous.C1.B1.A1.SetBigInt(inputs[n+9])
previous.C1.B2.A0.SetBigInt(inputs[n+10])
previous.C1.B2.A1.SetBigInt(inputs[n+11])

if previous.IsZero() {
return errors.New("previous Miller loop result is zero")
}

lines := bn254.PrecomputeLines(Q)
millerLoop, err := bn254.MillerLoopFixedQ(
[]bn254.G1Affine{P},
[][2][len(bn254.LoopCounter)]bn254.LineEvaluationAff{lines},
)
lines := make([][2][len(bn254.LoopCounter)]bn254.LineEvaluationAff, 0, len(q))
for _, qi := range q {
lines = append(lines, bn254.PrecomputeLines(qi))
}
millerLoop, err := bn254.MillerLoopFixedQ(p, lines)

if err != nil {
return err
}
Expand Down
52 changes: 42 additions & 10 deletions std/algebra/emulated/sw_bn254/pairing.go
Original file line number Diff line number Diff line change
Expand Up @@ -905,11 +905,26 @@ func (pr *Pairing) MillerLoopAndMul(P *G1Affine, Q *G2Affine, previous *GTEl) (*

// millerLoopAndFinalExpResult computes the Miller loop between P and Q,
// multiplies it in 𝔽p¹² by previous and returns the result.
func (pr *Pairing) millerLoopAndFinalExpResult(P *G1Affine, Q *G2Affine, previous *GTEl) *GTEl {
func (pr *Pairing) millerLoopAndFinalExpResult(P []*G1Affine, Q []*G2Affine, previous *GTEl) *GTEl {
tower := pr.ToTower(previous)

nP := len(P)
nQ := len(Q)
if nP != nQ {
return nil
}

inputs := make([]*baseEl, 0, 2*nP+4*nQ+12)
for _, p := range P {
inputs = append(inputs, &p.X, &p.Y)
}
for _, q := range Q {
inputs = append(inputs, &q.P.X.A0, &q.P.X.A1, &q.P.Y.A0, &q.P.Y.A1)
}
inputs = append(inputs, tower[0], tower[1], tower[2], tower[3], tower[4], tower[5], tower[6], tower[7], tower[8], tower[9], tower[10], tower[11])

// hint the non-residue witness
hint, err := pr.curveF.NewHint(millerLoopAndCheckFinalExpHint, 18, &P.X, &P.Y, &Q.P.X.A0, &Q.P.X.A1, &Q.P.Y.A0, &Q.P.Y.A1, tower[0], tower[1], tower[2], tower[3], tower[4], tower[5], tower[6], tower[7], tower[8], tower[9], tower[10], tower[11])
hint, err := pr.curveF.NewHint(millerLoopAndCheckFinalExpHint, 18, inputs...)
if err != nil {
// err is non-nil only for invalid number of inputs
panic(err)
Expand Down Expand Up @@ -950,15 +965,18 @@ func (pr *Pairing) millerLoopAndFinalExpResult(P *G1Affine, Q *G2Affine, previou
// residueWitnessInv = 1 / residueWitness
residueWitnessInv := pr.Ext12.Inverse(residueWitness)

if Q.Lines == nil {
Qlines := pr.computeLines(&Q.P)
Q.Lines = &Qlines
lines := make([]lineEvaluations, nQ)
for i := range Q {
if Q[i].Lines == nil {
Qlines := pr.computeLines(&Q[i].P)
Q[i].Lines = &Qlines
}
lines[i] = *Q[i].Lines
}
lines := *Q.Lines

res, err := pr.millerLoopLines(
[]*G1Affine{P},
[]lineEvaluations{lines},
P,
lines,
residueWitnessInv,
false,
)
Expand Down Expand Up @@ -998,12 +1016,26 @@ func (pr *Pairing) millerLoopAndFinalExpResult(P *G1Affine, Q *G2Affine, previou
//
// [On Proving Pairings]: https://eprint.iacr.org/2024/640.pdf
func (pr *Pairing) IsMillerLoopAndFinalExpOne(P *G1Affine, Q *G2Affine, previous *GTEl) frontend.Variable {
t2 := pr.millerLoopAndFinalExpResult(P, Q, previous)
t2 := pr.millerLoopAndFinalExpResult([]*G1Affine{P}, []*G2Affine{Q}, previous)

res := pr.IsEqual(t2, pr.Ext12.One())
return res
}

// AssertMultiMillerLoopAndFinalExpIsOne computes the Miller loop between
// multiple P and Q pairs and multiplies it in 𝔽p¹² by previous and
// returns a boolean indicating if the result lies in the same equivalence
// class as the reduced pairing purported to be 1. This check replaces the
// final exponentiation step in-circuit and follows Section 4 of
// [On Proving Pairings] paper by A. Novakovic and L. Eagen.
//
// [On Proving Pairings]: https://eprint.iacr.org/2024/640.pdf
func (pr *Pairing) AssertMultiMillerLoopAndFinalExpIsOne(P []*G1Affine, Q []*G2Affine, previous *GTEl) {
// t2 := pr.millerLoopAndFinalExpResult(P, Q, pr.Ext12.Mul(previous, pr.Ext12.Inverse(previous)))
t2 := pr.millerLoopAndFinalExpResult(P, Q, previous)
pr.AssertIsEqual(t2, pr.Ext12.One())
}

// AssertMillerLoopAndFinalExpIsOne computes the Miller loop between P and Q,
// multiplies it in 𝔽p¹² by previous and checks that the result lies in the
// same equivalence class as the reduced pairing purported to be 1. This check
Expand All @@ -1014,6 +1046,6 @@ func (pr *Pairing) IsMillerLoopAndFinalExpOne(P *G1Affine, Q *G2Affine, previous
//
// [On Proving Pairings]: https://eprint.iacr.org/2024/640.pdf
func (pr *Pairing) AssertMillerLoopAndFinalExpIsOne(P *G1Affine, Q *G2Affine, previous *GTEl) {
t2 := pr.millerLoopAndFinalExpResult(P, Q, previous)
t2 := pr.millerLoopAndFinalExpResult([]*G1Affine{P}, []*G2Affine{Q}, previous)
pr.AssertIsEqual(t2, pr.Ext12.One())
}
Loading