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

Fix(emulated pairing): edge cases in torus-based final exp #613

Merged
merged 22 commits into from
Apr 4, 2023

Conversation

yelhousni
Copy link
Contributor

fixes #605

@yelhousni yelhousni requested a review from ivokub March 31, 2023 16:38
@yelhousni yelhousni self-assigned this Mar 31, 2023
@yelhousni yelhousni added this to the v0.9.0 milestone Mar 31, 2023
@yelhousni yelhousni mentioned this pull request Mar 31, 2023
Copy link
Collaborator

@ivokub ivokub left a 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!

std/algebra/emulated/sw_bn254/pairing.go Outdated Show resolved Hide resolved
std/algebra/emulated/sw_bls12381/pairing.go Outdated Show resolved Hide resolved
@yelhousni
Copy link
Contributor Author

@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).

@ivokub
Copy link
Collaborator

ivokub commented Apr 3, 2023

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
 }

@ivokub
Copy link
Collaborator

ivokub commented Apr 3, 2023

See what do you think about using safe<>unsafe versions of the method instead of passing n into FinalExponentiation method as an argument.

If seems ok, then I can push as commits.

@yelhousni
Copy link
Contributor Author

See what do you think about using safe<>unsafe versions of the method instead of passing n into FinalExponentiation method as an argument.

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
 }

@ivokub
Copy link
Collaborator

ivokub commented Apr 4, 2023

  • Added safe<>unsafe versions.
  • added both safe and unsafe test for final exp test
  • added separate test case which should fail with unsafe but not with safe.

@yelhousni
Copy link
Contributor Author

awesome! thanks for that. It's good on my side, I think we can merge this.

@ivokub
Copy link
Collaborator

ivokub commented Apr 4, 2023

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!

@yelhousni yelhousni merged commit 6939cf5 into develop Apr 4, 2023
@yelhousni yelhousni deleted the fix-605 branch April 4, 2023 14:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants