-
Notifications
You must be signed in to change notification settings - Fork 369
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
Fix(emulated pairing): edge cases in torus-based final exp #613
Conversation
Lookup2 is slighly more inefficient than select, but because we do select for all E12 elements, then should add up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made some modifications to avoid passing API around. This made the interfaces simpler as we do not need to initialize the field emulation everywhere.
Also store api in pairing context so that can use select instead of lookup2 in final exp.
I'm not sure if the small comment is correct. Left a remark just in case. Otherwise I think is good to go!
@ivokub last commit does a switch on the Miller loop size (1 vs. product). In the case of 1, the edge-cases do not happen as the pairing is non-degenerate (≠1) so we save the Select/IsZero logic (~13k in BN254). |
Suggested edit: diff --git a/std/algebra/emulated/sw_bls12381/pairing.go b/std/algebra/emulated/sw_bls12381/pairing.go
index 44b345e9..788f6203 100644
--- a/std/algebra/emulated/sw_bls12381/pairing.go
+++ b/std/algebra/emulated/sw_bls12381/pairing.go
@@ -64,25 +64,65 @@ func NewPairing(api frontend.API) (*Pairing, error) {
}, nil
}
-// FinalExponentiation computes the exponentiation (∏ᵢ zᵢ)ᵈ
-// where d = (p¹²-1)/r = (p¹²-1)/Φ₁₂(p) ⋅ Φ₁₂(p)/r = (p⁶-1)(p²+1)(p⁴ - p² +1)/r
-// we use instead d=s ⋅ (p⁶-1)(p²+1)(p⁴ - p² +1)/r
-// where s is the cofactor 3 (Hayashida et al.)
-func (pr Pairing) FinalExponentiation(e *GTEl, n int) *GTEl {
+// FinalExponentiation computes the exponentiation (∏ᵢ zᵢ)ᵈ where
+//
+// d = (p¹²-1)/r = (p¹²-1)/Φ₁₂(p) ⋅ Φ₁₂(p)/r = (p⁶-1)(p²+1)(p⁴ - p² +1)/r
+//
+// we use instead
+//
+// d=s ⋅ (p⁶-1)(p²+1)(p⁴ - p² +1)/r
+//
+// where s is the cofactor 3 (Hayashida et al.).
+//
+// This is the safe version of the method where e may be {-1,1}. If it is known
+// that e ≠ {-1,1} then using the unsafe version of the method saves
+// considerable amount of constraints. When called with the result of
+// [MillerLoop], then current method is applicable when length of the inputs to
+// Miller loop is 1.
+func (pr Pairing) FinalExponentiation(e *GTEl) *GTEl {
+ return pr.finalExponentiation(e, false)
+}
+
+// FinalExponentiationUnsafe computes the exponentiation (∏ᵢ zᵢ)ᵈ where
+//
+// d = (p¹²-1)/r = (p¹²-1)/Φ₁₂(p) ⋅ Φ₁₂(p)/r = (p⁶-1)(p²+1)(p⁴ - p² +1)/r
+//
+// we use instead
+//
+// d=s ⋅ (p⁶-1)(p²+1)(p⁴ - p² +1)/r
+//
+// where s is the cofactor 3 (Hayashida et al.).
+//
+// This is the unsafe version of the method where e may NOT be {-1,1}. If e ∈
+// {-1, 1}, then there exists no valid solution to the circuit. This method is
+// applicable when called with the result of [MillerLoop] method when the length
+// of the inputs to Miller loop is 1.
+func (pr Pairing) FinalExponentiationUnsafe(e *GTEl) *GTEl {
+ return pr.finalExponentiation(e, true)
+}
+
+// finalExponentiation computes the exponentiation (∏ᵢ zᵢ)ᵈ where
+//
+// d = (p¹²-1)/r = (p¹²-1)/Φ₁₂(p) ⋅ Φ₁₂(p)/r = (p⁶-1)(p²+1)(p⁴ - p² +1)/r
+//
+// we use instead
+//
+// d=s ⋅ (p⁶-1)(p²+1)(p⁴ - p² +1)/r
+//
+// where s is the cofactor 3 (Hayashida et al.).
+func (pr Pairing) finalExponentiation(e *GTEl, unsafe bool) *GTEl {
// 1. Easy part
// (p⁶-1)(p²+1)
var selector1, selector2 frontend.Variable
_dummy := pr.Ext6.One()
- switch n {
- case 1:
+ if unsafe {
// The Miller loop result is ≠ {-1,1}, otherwise this means P and Q are
// linearly dependant and not from G1 and G2 respectively.
// So e ∈ G_{q,2} \ {-1,1} and hence e.C1 ≠ 0.
// Nothing to do.
-
- default:
+ } else {
// However, for a product of Miller loops (n>=2) this might happen. If this is
// the case, the result is 1 in the torus. We assign a dummy value (1) to e.C1
// and proceed further.
@@ -125,13 +165,12 @@ func (pr Pairing) FinalExponentiation(e *GTEl, n int) *GTEl {
t1 = pr.MulTorus(t1, t0)
var result GTEl
- switch n {
// MulTorus(c, t1) requires c ≠ -t1. When c = -t1, it means the
// product is 1 in the torus.
- case 1:
+ if unsafe {
// For a single pairing, this does not happen because the pairing is non-degenerate.
result = *pr.DecompressTorus(pr.MulTorus(c, t1))
- default:
+ } else {
// For a product of pairings this might happen when the result is expected to be 1.
// We assign a dummy value (1) to t1 and proceed furhter.
// Finally we do a select on both edge cases:
@@ -160,11 +199,11 @@ type lineEvaluation struct {
//
// This function doesn't check that the inputs are in the correct subgroups.
func (pr Pairing) Pair(P []*G1Affine, Q []*G2Affine) (*GTEl, error) {
- res, n, err := pr.MillerLoop(P, Q)
+ res, err := pr.MillerLoop(P, Q)
if err != nil {
return nil, fmt.Errorf("miller loop: %w", err)
}
- res = pr.FinalExponentiation(res, n)
+ res = pr.finalExponentiation(res, len(P) == 1)
return res, nil
}
@@ -202,11 +241,11 @@ var loopCounter = [64]int8{
// MillerLoop computes the multi-Miller loop
// ∏ᵢ { fᵢ_{u,Q}(P) }
-func (pr Pairing) MillerLoop(P []*G1Affine, Q []*G2Affine) (*GTEl, int, error) {
+func (pr Pairing) MillerLoop(P []*G1Affine, Q []*G2Affine) (*GTEl, error) {
// check input size match
n := len(P)
if n == 0 || n != len(Q) {
- return nil, n, errors.New("invalid inputs sizes")
+ return nil, errors.New("invalid inputs sizes")
}
res := pr.Ext12.One()
@@ -322,7 +361,7 @@ func (pr Pairing) MillerLoop(P []*G1Affine, Q []*G2Affine) (*GTEl, int, error) {
// negative x₀
res = pr.Ext12.Conjugate(res)
- return res, n, nil
+ return res, nil
}
// doubleAndAddStep doubles p1 and adds p2 to the result in affine coordinates, and evaluates the line in Miller loop
diff --git a/std/algebra/emulated/sw_bls12381/pairing_test.go b/std/algebra/emulated/sw_bls12381/pairing_test.go
index 5523ced8..f9b16663 100644
--- a/std/algebra/emulated/sw_bls12381/pairing_test.go
+++ b/std/algebra/emulated/sw_bls12381/pairing_test.go
@@ -35,7 +35,7 @@ func (c *FinalExponentiationCircuit) Define(api frontend.API) error {
if err != nil {
return fmt.Errorf("new pairing: %w", err)
}
- res := pairing.FinalExponentiation(&c.InGt, 1)
+ res := pairing.FinalExponentiation(&c.InGt)
pairing.AssertIsEqual(res, &c.Res)
return nil
}
diff --git a/std/algebra/emulated/sw_bn254/pairing.go b/std/algebra/emulated/sw_bn254/pairing.go
index 4bfaa389..88245f17 100644
--- a/std/algebra/emulated/sw_bn254/pairing.go
+++ b/std/algebra/emulated/sw_bn254/pairing.go
@@ -74,22 +74,62 @@ func NewPairing(api frontend.API) (*Pairing, error) {
//
// and r does NOT divide d'
//
-// FinalExponentiation returns a decompressed element in E12
-func (pr Pairing) FinalExponentiation(e *GTEl, n int) *GTEl {
+// FinalExponentiation returns a decompressed element in E12.
+//
+// This is the safe version of the method where e may be {-1,1}. If it is known
+// that e ≠ {-1,1} then using the unsafe version of the method saves
+// considerable amount of constraints. When called with the result of
+// [MillerLoop], then current method is applicable when length of the inputs to
+// Miller loop is 1.
+func (pr Pairing) FinalExponentiation(e *GTEl) *GTEl {
+ return pr.finalExponentiation(e, false)
+}
+
+// FinalExponentiationUnsafe computes the exponentiation eᵈ where
+//
+// d = (p¹²-1)/r = (p¹²-1)/Φ₁₂(p) ⋅ Φ₁₂(p)/r = (p⁶-1)(p²+1)(p⁴ - p² +1)/r.
+//
+// We use instead d'= s ⋅ d, where s is the cofactor
+//
+// 2x₀(6x₀²+3x₀+1)
+//
+// and r does NOT divide d'
+//
+// FinalExponentiationUnsafe returns a decompressed element in E12.
+//
+// This is the unsafe version of the method where e may NOT be {-1,1}. If e ∈
+// {-1, 1}, then there exists no valid solution to the circuit. This method is
+// applicable when called with the result of [MillerLoop] method when the length
+// of the inputs to Miller loop is 1.
+func (pr Pairing) FinalExponentiationUnsafe(e *GTEl) *GTEl {
+ return pr.finalExponentiation(e, true)
+}
+
+// finalExponentiation computes the exponentiation eᵈ where
+//
+// d = (p¹²-1)/r = (p¹²-1)/Φ₁₂(p) ⋅ Φ₁₂(p)/r = (p⁶-1)(p²+1)(p⁴ - p² +1)/r.
+//
+// We use instead d'= s ⋅ d, where s is the cofactor
+//
+// 2x₀(6x₀²+3x₀+1)
+//
+// and r does NOT divide d'
+//
+// finalExponentiation returns a decompressed element in E12
+func (pr Pairing) finalExponentiation(e *GTEl, unsafe bool) *GTEl {
// 1. Easy part
// (p⁶-1)(p²+1)
var selector1, selector2 frontend.Variable
_dummy := pr.Ext6.One()
- switch n {
- case 1:
+ if unsafe {
// The Miller loop result is ≠ {-1,1}, otherwise this means P and Q are
// linearly dependant and not from G1 and G2 respectively.
// So e ∈ G_{q,2} \ {-1,1} and hence e.C1 ≠ 0.
// Nothing to do.
- default:
+ } else {
// However, for a product of Miller loops (n>=2) this might happen. If this is
// the case, the result is 1 in the torus. We assign a dummy value (1) to e.C1
// and proceed further.
@@ -138,13 +178,12 @@ func (pr Pairing) FinalExponentiation(e *GTEl, n int) *GTEl {
t2 = pr.FrobeniusCubeTorus(t2)
var result GTEl
- switch n {
// MulTorus(t0, t2) requires t0 ≠ -t2. When t0 = -t2, it means the
// product is 1 in the torus.
- case 1:
+ if unsafe {
// For a single pairing, this does not happen because the pairing is non-degenerate.
result = *pr.DecompressTorus(pr.MulTorus(t2, t0))
- default:
+ } else {
// For a product of pairings this might happen when the result is expected to be 1.
// We assign a dummy value (1) to t0 and proceed furhter.
// Finally we do a select on both edge cases:
@@ -165,11 +204,11 @@ func (pr Pairing) FinalExponentiation(e *GTEl, n int) *GTEl {
//
// This function doesn't check that the inputs are in the correct subgroups.
func (pr Pairing) Pair(P []*G1Affine, Q []*G2Affine) (*GTEl, error) {
- res, n, err := pr.MillerLoop(P, Q)
+ res, err := pr.MillerLoop(P, Q)
if err != nil {
return nil, fmt.Errorf("miller loop: %w", err)
}
- res = pr.FinalExponentiation(res, n)
+ res = pr.finalExponentiation(res, len(P) == 1)
return res, nil
}
@@ -215,11 +254,11 @@ type lineEvaluation struct {
// MillerLoop computes the multi-Miller loop
// ∏ᵢ { fᵢ_{6x₀+2,Q}(P) · ℓᵢ_{[6x₀+2]Q,π(Q)}(P) · ℓᵢ_{[6x₀+2]Q+π(Q),-π²(Q)}(P) }
-func (pr Pairing) MillerLoop(P []*G1Affine, Q []*G2Affine) (*GTEl, int, error) {
+func (pr Pairing) MillerLoop(P []*G1Affine, Q []*G2Affine) (*GTEl, error) {
// check input size match
n := len(P)
if n == 0 || n != len(Q) {
- return nil, n, errors.New("invalid inputs sizes")
+ return nil, errors.New("invalid inputs sizes")
}
res := pr.Ext12.One()
@@ -389,7 +428,7 @@ func (pr Pairing) MillerLoop(P []*G1Affine, Q []*G2Affine) (*GTEl, int, error) {
}
default:
- return nil, n, errors.New("invalid loopCounter")
+ return nil, errors.New("invalid loopCounter")
}
}
@@ -428,7 +467,7 @@ func (pr Pairing) MillerLoop(P []*G1Affine, Q []*G2Affine) (*GTEl, int, error) {
}
- return res, n, nil
+ return res, nil
}
// doubleAndAddStep doubles p1 and adds p2 to the result in affine coordinates, and evaluates the line in Miller loop
diff --git a/std/algebra/emulated/sw_bn254/pairing_test.go b/std/algebra/emulated/sw_bn254/pairing_test.go
index 9a00b1db..3d24335e 100644
--- a/std/algebra/emulated/sw_bn254/pairing_test.go
+++ b/std/algebra/emulated/sw_bn254/pairing_test.go
@@ -35,7 +35,7 @@ func (c *FinalExponentiationCircuit) Define(api frontend.API) error {
if err != nil {
return fmt.Errorf("new pairing: %w", err)
}
- res := pairing.FinalExponentiation(&c.InGt, 1)
+ res := pairing.FinalExponentiation(&c.InGt)
pairing.AssertIsEqual(res, &c.Res)
return nil
}
|
See what do you think about using safe<>unsafe versions of the method instead of passing If seems ok, then I can push as commits. |
Yup that seems ok. You can push the commit for both bn254 and bls12-381. Maybe we can also add the safe vs. unsafe test: diff --git a/std/algebra/emulated/sw_bn254/pairing_test.go b/std/algebra/emulated/sw_bn254/pairing_test.go
index 9a00b1db..3ecfb588 100644
--- a/std/algebra/emulated/sw_bn254/pairing_test.go
+++ b/std/algebra/emulated/sw_bn254/pairing_test.go
@@ -35,8 +35,10 @@ func (c *FinalExponentiationCircuit) Define(api frontend.API) error {
if err != nil {
return fmt.Errorf("new pairing: %w", err)
}
- res := pairing.FinalExponentiation(&c.InGt, 1)
- pairing.AssertIsEqual(res, &c.Res)
+ res1 := pairing.FinalExponentiation(&c.InGt)
+ res2 := pairing.FinalExponentiationUnsafe(&c.InGt)
+ pairing.AssertIsEqual(res1, &c.Res)
+ pairing.AssertIsEqual(res2, &c.Res)
return nil
} |
|
awesome! thanks for that. It's good on my side, I think we can merge this. |
All good from my side. You are good to merge! |
fixes #605