diff --git a/std/commitments/kzg/native_doc_test.go b/std/commitments/kzg/native_doc_test.go
index f5ff956aa5..691acb01f8 100644
--- a/std/commitments/kzg/native_doc_test.go
+++ b/std/commitments/kzg/native_doc_test.go
@@ -71,21 +71,29 @@ func Example_native() {
 		panic("commitment witness failed: " + err.Error())
 	}
 
-	// create a witness element of the opening proof and the evaluation point
-	wProof, err := kzg.ValueOfOpeningProof[sw_bls12377.ScalarField, sw_bls12377.G1Affine](point, proof)
+	// create a witness element of the opening proof
+	wProof, err := kzg.ValueOfOpeningProof[sw_bls12377.ScalarField, sw_bls12377.G1Affine](proof)
 	if err != nil {
 		panic("opening proof witness failed: " + err.Error())
 	}
 
 	// create a witness element of the SRS
-	wVk, err := kzg.ValueOfVerifyingKey[sw_bls12377.G2Affine](srs.Vk)
+	wVk, err := kzg.ValueOfVerifyingKey[sw_bls12377.G1Affine, sw_bls12377.G2Affine](srs.Vk)
 	if err != nil {
 		panic("verifying key witness failed: " + err.Error())
 	}
+
+	// create a witness element of the evaluation point
+	wPt, err := kzg.ValueOfScalar[sw_bls12377.ScalarField](point)
+	if err != nil {
+		panic("point witness failed: " + err.Error())
+	}
+
 	assignment := KZGVerificationCircuit[sw_bls12377.ScalarField, sw_bls12377.G1Affine, sw_bls12377.G2Affine, sw_bls12377.GT]{
 		VerifyingKey: wVk,
 		Commitment:   wCmt,
 		OpeningProof: wProof,
+		Point:        wPt,
 	}
 	circuit := KZGVerificationCircuit[sw_bls12377.ScalarField, sw_bls12377.G1Affine, sw_bls12377.G2Affine, sw_bls12377.GT]{}
 
diff --git a/std/commitments/kzg/nonnative_doc_test.go b/std/commitments/kzg/nonnative_doc_test.go
index 0289ee0959..9278d41821 100644
--- a/std/commitments/kzg/nonnative_doc_test.go
+++ b/std/commitments/kzg/nonnative_doc_test.go
@@ -17,22 +17,18 @@ import (
 )
 
 type KZGVerificationCircuit[FR emulated.FieldParams, G1El algebra.G1ElementT, G2El algebra.G2ElementT, GTEl algebra.GtElementT] struct {
-	kzg.VerifyingKey[G2El]
+	kzg.VerifyingKey[G1El, G2El]
 	kzg.Commitment[G1El]
 	kzg.OpeningProof[FR, G1El]
+	Point emulated.Element[FR]
 }
 
 func (c *KZGVerificationCircuit[FR, G1El, G2El, GTEl]) Define(api frontend.API) error {
-	curve, err := algebra.GetCurve[FR, G1El](api)
+	verifier, err := kzg.NewVerifier[FR, G1El, G2El, GTEl](api)
 	if err != nil {
-		return fmt.Errorf("get curve: %w", err)
+		return fmt.Errorf("new verifier: %w", err)
 	}
-	pairing, err := algebra.GetPairing[G1El, G2El, GTEl](api)
-	if err != nil {
-		return fmt.Errorf("get pairing: %w", err)
-	}
-	verifier := kzg.NewVerifier(c.VerifyingKey, curve, pairing)
-	if err := verifier.AssertProof(c.Commitment, c.OpeningProof); err != nil {
+	if err := verifier.CheckOpeningProof(c.Commitment, c.OpeningProof, c.Point, c.VerifyingKey); err != nil {
 		return fmt.Errorf("assert proof: %w", err)
 	}
 	return nil
@@ -94,21 +90,29 @@ func Example_emulated() {
 		panic("commitment witness failed: " + err.Error())
 	}
 
-	// create a witness element of the opening proof and the evaluation point
-	wProof, err := kzg.ValueOfOpeningProof[sw_bn254.ScalarField, sw_bn254.G1Affine](point, proof)
+	// create a witness element of the opening proof
+	wProof, err := kzg.ValueOfOpeningProof[sw_bn254.ScalarField, sw_bn254.G1Affine](proof)
 	if err != nil {
 		panic("opening proof witness failed: " + err.Error())
 	}
 
 	// create a witness element of the SRS
-	wVk, err := kzg.ValueOfVerifyingKey[sw_bn254.G2Affine](srs.Vk)
+	wVk, err := kzg.ValueOfVerifyingKey[sw_bn254.G1Affine, sw_bn254.G2Affine](srs.Vk)
 	if err != nil {
 		panic("verifying key witness failed: " + err.Error())
 	}
+
+	// create a witness element of the evaluation point
+	wPt, err := kzg.ValueOfScalar[sw_bn254.ScalarField](point)
+	if err != nil {
+		panic("point witness failed: " + err.Error())
+	}
+
 	assignment := KZGVerificationCircuit[sw_bn254.ScalarField, sw_bn254.G1Affine, sw_bn254.G2Affine, sw_bn254.GTEl]{
 		VerifyingKey: wVk,
 		Commitment:   wCmt,
 		OpeningProof: wProof,
+		Point:        wPt,
 	}
 	circuit := KZGVerificationCircuit[sw_bn254.ScalarField, sw_bn254.G1Affine, sw_bn254.G2Affine, sw_bn254.GTEl]{}
 
diff --git a/std/commitments/kzg/verifier.go b/std/commitments/kzg/verifier.go
index 60ba6d621d..3b077a6354 100644
--- a/std/commitments/kzg/verifier.go
+++ b/std/commitments/kzg/verifier.go
@@ -29,6 +29,7 @@ import (
 	bw6761 "github.com/consensys/gnark-crypto/ecc/bw6-761"
 	fr_bw6761 "github.com/consensys/gnark-crypto/ecc/bw6-761/fr"
 	kzg_bw6761 "github.com/consensys/gnark-crypto/ecc/bw6-761/kzg"
+	"github.com/consensys/gnark/frontend"
 	"github.com/consensys/gnark/std/algebra"
 	"github.com/consensys/gnark/std/algebra/emulated/sw_bls12381"
 	"github.com/consensys/gnark/std/algebra/emulated/sw_bn254"
@@ -36,8 +37,50 @@ import (
 	"github.com/consensys/gnark/std/algebra/native/sw_bls12377"
 	"github.com/consensys/gnark/std/algebra/native/sw_bls24315"
 	"github.com/consensys/gnark/std/math/emulated"
+	"github.com/consensys/gnark/std/recursion"
 )
 
+// ValueOfScalar initializes a scalar in a witness from a native scalar (Fr) point.
+// The scalars are always emulated.
+func ValueOfScalar[FR emulated.FieldParams](scalar any) (emulated.Element[FR], error) {
+	var ret emulated.Element[FR]
+	switch s := any(&ret).(type) {
+	case *emulated.Element[sw_bn254.ScalarField]:
+		tScalar, ok := scalar.(fr_bn254.Element)
+		if !ok {
+			return ret, fmt.Errorf("mismatching types %T %T", ret, tScalar)
+		}
+		*s = sw_bn254.NewScalar(tScalar)
+	case *emulated.Element[sw_bls12377.ScalarField]:
+		tScalar, ok := scalar.(fr_bls12377.Element)
+		if !ok {
+			return ret, fmt.Errorf("mismatching types %T %T", ret, tScalar)
+		}
+		*s = sw_bls12377.NewScalar(tScalar)
+	case *emulated.Element[sw_bls12381.ScalarField]:
+		tScalar, ok := scalar.(fr_bls12381.Element)
+		if !ok {
+			return ret, fmt.Errorf("mismatching types %T %T", ret, tScalar)
+		}
+		*s = sw_bls12381.NewScalar(tScalar)
+	case *emulated.Element[sw_bw6761.ScalarField]:
+		tScalar, ok := scalar.(fr_bw6761.Element)
+		if !ok {
+			return ret, fmt.Errorf("mismatching types %T %T", ret, tScalar)
+		}
+		*s = sw_bw6761.NewScalar(tScalar)
+	case *emulated.Element[sw_bls24315.ScalarField]:
+		tScalar, ok := scalar.(fr_bls24315.Element)
+		if !ok {
+			return ret, fmt.Errorf("mismatching types %T %T", ret, tScalar)
+		}
+		*s = sw_bls24315.NewScalar(tScalar)
+	default:
+		return ret, fmt.Errorf("unknown type parametrization")
+	}
+	return ret, nil
+}
+
 // Commitment is an KZG commitment to a polynomial. Use [ValueOfCommitment] to
 // initialize a witness from the native commitment.
 type Commitment[G1El algebra.G1ElementT] struct {
@@ -90,15 +133,14 @@ func ValueOfCommitment[G1El algebra.G1ElementT](cmt any) (Commitment[G1El], erro
 // equal to ClaimedValue. Use [ValueOfOpeningProof] to initialize a witness from
 // a native opening proof.
 type OpeningProof[FR emulated.FieldParams, G1El algebra.G1ElementT] struct {
-	QuotientPoly G1El
+	Quotient     G1El
 	ClaimedValue emulated.Element[FR]
-	Point        emulated.Element[FR]
 }
 
 // ValueOfOpeningProof initializes an opening proof from the given proof and
 // point. It returns an error if there is a mismatch between the type parameters
 // and types of the provided point and proof.
-func ValueOfOpeningProof[FR emulated.FieldParams, G1El algebra.G1ElementT](point any, proof any) (OpeningProof[FR, G1El], error) {
+func ValueOfOpeningProof[FR emulated.FieldParams, G1El algebra.G1ElementT](proof any) (OpeningProof[FR, G1El], error) {
 	var ret OpeningProof[FR, G1El]
 	switch s := any(&ret).(type) {
 	case *OpeningProof[sw_bn254.ScalarField, sw_bn254.G1Affine]:
@@ -106,61 +148,100 @@ func ValueOfOpeningProof[FR emulated.FieldParams, G1El algebra.G1ElementT](point
 		if !ok {
 			return ret, fmt.Errorf("mismatching types %T %T", ret, proof)
 		}
-		tPoint, ok := point.(fr_bn254.Element)
-		if !ok {
-			return ret, fmt.Errorf("mismatching types %T %T", s.Point, point)
-		}
-		s.QuotientPoly = sw_bn254.NewG1Affine(tProof.H)
+		s.Quotient = sw_bn254.NewG1Affine(tProof.H)
 		s.ClaimedValue = sw_bn254.NewScalar(tProof.ClaimedValue)
-		s.Point = sw_bn254.NewScalar(tPoint)
 	case *OpeningProof[sw_bls12377.ScalarField, sw_bls12377.G1Affine]:
 		tProof, ok := proof.(kzg_bls12377.OpeningProof)
 		if !ok {
 			return ret, fmt.Errorf("mismatching types %T %T", ret, proof)
 		}
-		tPoint, ok := point.(fr_bls12377.Element)
-		if !ok {
-			return ret, fmt.Errorf("mismatching types %T %T", s.Point, point)
-		}
-		s.QuotientPoly = sw_bls12377.NewG1Affine(tProof.H)
+		s.Quotient = sw_bls12377.NewG1Affine(tProof.H)
 		s.ClaimedValue = sw_bls12377.NewScalar(tProof.ClaimedValue)
-		s.Point = sw_bls12377.NewScalar(tPoint)
 	case *OpeningProof[sw_bls12381.ScalarField, sw_bls12381.G1Affine]:
 		tProof, ok := proof.(kzg_bls12381.OpeningProof)
 		if !ok {
 			return ret, fmt.Errorf("mismatching types %T %T", ret, proof)
 		}
-		tPoint, ok := point.(fr_bls12381.Element)
-		if !ok {
-			return ret, fmt.Errorf("mismatching types %T %T", s.Point, point)
-		}
-		s.QuotientPoly = sw_bls12381.NewG1Affine(tProof.H)
+		s.Quotient = sw_bls12381.NewG1Affine(tProof.H)
 		s.ClaimedValue = sw_bls12381.NewScalar(tProof.ClaimedValue)
-		s.Point = sw_bls12381.NewScalar(tPoint)
 	case *OpeningProof[sw_bw6761.ScalarField, sw_bw6761.G1Affine]:
 		tProof, ok := proof.(kzg_bw6761.OpeningProof)
 		if !ok {
 			return ret, fmt.Errorf("mismatching types %T %T", ret, proof)
 		}
-		tPoint, ok := point.(fr_bw6761.Element)
-		if !ok {
-			return ret, fmt.Errorf("mismatching types %T %T", s.Point, point)
-		}
-		s.QuotientPoly = sw_bw6761.NewG1Affine(tProof.H)
+		s.Quotient = sw_bw6761.NewG1Affine(tProof.H)
 		s.ClaimedValue = sw_bw6761.NewScalar(tProof.ClaimedValue)
-		s.Point = sw_bw6761.NewScalar(tPoint)
 	case *OpeningProof[sw_bls24315.ScalarField, sw_bls24315.G1Affine]:
 		tProof, ok := proof.(kzg_bls24315.OpeningProof)
 		if !ok {
 			return ret, fmt.Errorf("mismatching types %T %T", ret, proof)
 		}
-		tPoint, ok := point.(fr_bls24315.Element)
+		s.Quotient = sw_bls24315.NewG1Affine(tProof.H)
+		s.ClaimedValue = sw_bls24315.NewScalar(tProof.ClaimedValue)
+	default:
+		return ret, fmt.Errorf("unknown type parametrization")
+	}
+	return ret, nil
+}
+
+type BatchOpeningProof[FR emulated.FieldParams, G1El algebra.G1ElementT] struct {
+	Quotient      G1El
+	ClaimedValues []emulated.Element[FR]
+}
+
+func ValueOfBatchOpeningProof[FR emulated.FieldParams, G1El any](proof any) (BatchOpeningProof[FR, G1El], error) {
+	var ret BatchOpeningProof[FR, G1El]
+	switch s := any(&ret).(type) {
+	case *BatchOpeningProof[sw_bn254.ScalarField, sw_bn254.G1Affine]:
+		tProof, ok := proof.(kzg_bn254.BatchOpeningProof)
 		if !ok {
-			return ret, fmt.Errorf("mismatching types %T %T", s.Point, point)
+			return ret, fmt.Errorf("mismatching types %T %T", ret, proof)
+		}
+		s.Quotient = sw_bn254.NewG1Affine(tProof.H)
+		s.ClaimedValues = make([]emulated.Element[sw_bn254.ScalarField], len(tProof.ClaimedValues))
+		for i := 0; i < len(s.ClaimedValues); i++ {
+			s.ClaimedValues[i] = sw_bn254.NewScalar(tProof.ClaimedValues[i])
+		}
+	case *BatchOpeningProof[sw_bls12377.ScalarField, sw_bls12377.G1Affine]:
+		tProof, ok := proof.(kzg_bls12377.BatchOpeningProof)
+		if !ok {
+			return ret, fmt.Errorf("mismatching types %T %T", ret, proof)
+		}
+		s.Quotient = sw_bls12377.NewG1Affine(tProof.H)
+		s.ClaimedValues = make([]emulated.Element[sw_bls12377.ScalarField], len(tProof.ClaimedValues))
+		for i := 0; i < len(s.ClaimedValues); i++ {
+			s.ClaimedValues[i] = sw_bls12377.NewScalar(tProof.ClaimedValues[i])
+		}
+	case *BatchOpeningProof[sw_bls12381.ScalarField, sw_bls12381.G1Affine]:
+		tProof, ok := proof.(kzg_bls12381.BatchOpeningProof)
+		if !ok {
+			return ret, fmt.Errorf("mismatching types %T %T", ret, proof)
+		}
+		s.Quotient = sw_bls12381.NewG1Affine(tProof.H)
+		s.ClaimedValues = make([]emulated.Element[sw_bls12381.ScalarField], len(tProof.ClaimedValues))
+		for i := 0; i < len(s.ClaimedValues); i++ {
+			s.ClaimedValues[i] = sw_bls12381.NewScalar(tProof.ClaimedValues[i])
+		}
+	case *BatchOpeningProof[sw_bw6761.ScalarField, sw_bw6761.G1Affine]:
+		tProof, ok := proof.(kzg_bw6761.BatchOpeningProof)
+		if !ok {
+			return ret, fmt.Errorf("mismatching types %T %T", ret, proof)
+		}
+		s.Quotient = sw_bw6761.NewG1Affine(tProof.H)
+		s.ClaimedValues = make([]emulated.Element[sw_bw6761.ScalarField], len(tProof.ClaimedValues))
+		for i := 0; i < len(s.ClaimedValues); i++ {
+			s.ClaimedValues[i] = sw_bw6761.NewScalar(tProof.ClaimedValues[i])
+		}
+	case *BatchOpeningProof[sw_bls24315.ScalarField, sw_bls24315.G1Affine]:
+		tProof, ok := proof.(kzg_bls24315.BatchOpeningProof)
+		if !ok {
+			return ret, fmt.Errorf("mismatching types %T %T", ret, proof)
+		}
+		s.Quotient = sw_bls24315.NewG1Affine(tProof.H)
+		s.ClaimedValues = make([]emulated.Element[sw_bls24315.ScalarField], len(tProof.ClaimedValues))
+		for i := 0; i < len(s.ClaimedValues); i++ {
+			s.ClaimedValues[i] = sw_bls24315.NewScalar(tProof.ClaimedValues[i])
 		}
-		s.QuotientPoly = sw_bls24315.NewG1Affine(tProof.H)
-		s.ClaimedValue = sw_bls24315.NewScalar(tProof.ClaimedValue)
-		s.Point = sw_bls24315.NewScalar(tPoint)
 	default:
 		return ret, fmt.Errorf("unknown type parametrization")
 	}
@@ -169,51 +250,57 @@ func ValueOfOpeningProof[FR emulated.FieldParams, G1El algebra.G1ElementT](point
 
 // VerifyingKey is the trusted setup for KZG polynomial commitment scheme. Use
 // [ValueOfVerifyingKey] to initialize a witness from the native VerifyingKey.
-type VerifyingKey[G2El algebra.G2ElementT] struct {
-	SRS [2]G2El
+type VerifyingKey[G1El algebra.G1ElementT, G2El algebra.G2ElementT] struct {
+	G2 [2]G2El
+	G1 G1El
 }
 
 // ValueOfVerifyingKey initializes verifying key witness from the native
 // verifying key. It returns an error if there is a mismatch between the type
 // parameters and the provided verifying key type.
-func ValueOfVerifyingKey[G2El algebra.G2ElementT](vk any) (VerifyingKey[G2El], error) {
-	var ret VerifyingKey[G2El]
+func ValueOfVerifyingKey[G1El algebra.G1ElementT, G2El algebra.G2ElementT](vk any) (VerifyingKey[G1El, G2El], error) {
+	var ret VerifyingKey[G1El, G2El]
 	switch s := any(&ret).(type) {
-	case *VerifyingKey[sw_bn254.G2Affine]:
+	case *VerifyingKey[sw_bn254.G1Affine, sw_bn254.G2Affine]:
 		tVk, ok := vk.(kzg_bn254.VerifyingKey)
 		if !ok {
 			return ret, fmt.Errorf("mismatching types %T %T", ret, vk)
 		}
-		s.SRS[0] = sw_bn254.NewG2Affine(tVk.G2[0])
-		s.SRS[1] = sw_bn254.NewG2Affine(tVk.G2[1])
-	case *VerifyingKey[sw_bls12377.G2Affine]:
+		s.G1 = sw_bn254.NewG1Affine(tVk.G1)
+		s.G2[0] = sw_bn254.NewG2Affine(tVk.G2[0])
+		s.G2[1] = sw_bn254.NewG2Affine(tVk.G2[1])
+	case *VerifyingKey[sw_bls12377.G1Affine, sw_bls12377.G2Affine]:
 		tVk, ok := vk.(kzg_bls12377.VerifyingKey)
 		if !ok {
 			return ret, fmt.Errorf("mismatching types %T %T", ret, vk)
 		}
-		s.SRS[0] = sw_bls12377.NewG2Affine(tVk.G2[0])
-		s.SRS[1] = sw_bls12377.NewG2Affine(tVk.G2[1])
-	case *VerifyingKey[sw_bls12381.G2Affine]:
+		s.G1 = sw_bls12377.NewG1Affine(tVk.G1)
+		s.G2[0] = sw_bls12377.NewG2Affine(tVk.G2[0])
+		s.G2[1] = sw_bls12377.NewG2Affine(tVk.G2[1])
+	case *VerifyingKey[sw_bls12381.G1Affine, sw_bls12381.G2Affine]:
 		tVk, ok := vk.(kzg_bls12381.VerifyingKey)
 		if !ok {
 			return ret, fmt.Errorf("mismatching types %T %T", ret, vk)
 		}
-		s.SRS[0] = sw_bls12381.NewG2Affine(tVk.G2[0])
-		s.SRS[1] = sw_bls12381.NewG2Affine(tVk.G2[1])
-	case *VerifyingKey[sw_bw6761.G2Affine]:
+		s.G1 = sw_bls12381.NewG1Affine(tVk.G1)
+		s.G2[0] = sw_bls12381.NewG2Affine(tVk.G2[0])
+		s.G2[1] = sw_bls12381.NewG2Affine(tVk.G2[1])
+	case *VerifyingKey[sw_bw6761.G1Affine, sw_bw6761.G2Affine]:
 		tVk, ok := vk.(kzg_bw6761.VerifyingKey)
 		if !ok {
 			return ret, fmt.Errorf("mismatching types %T %T", ret, vk)
 		}
-		s.SRS[0] = sw_bw6761.NewG2Affine(tVk.G2[0])
-		s.SRS[1] = sw_bw6761.NewG2Affine(tVk.G2[1])
-	case *VerifyingKey[sw_bls24315.G2Affine]:
+		s.G1 = sw_bw6761.NewG1Affine(tVk.G1)
+		s.G2[0] = sw_bw6761.NewG2Affine(tVk.G2[0])
+		s.G2[1] = sw_bw6761.NewG2Affine(tVk.G2[1])
+	case *VerifyingKey[sw_bls24315.G1Affine, sw_bls24315.G2Affine]:
 		tVk, ok := vk.(kzg_bls24315.VerifyingKey)
 		if !ok {
 			return ret, fmt.Errorf("mismatching types %T %T", ret, vk)
 		}
-		s.SRS[0] = sw_bls24315.NewG2Affine(tVk.G2[0])
-		s.SRS[1] = sw_bls24315.NewG2Affine(tVk.G2[1])
+		s.G1 = sw_bls24315.NewG1Affine(tVk.G1)
+		s.G2[0] = sw_bls24315.NewG2Affine(tVk.G2[0])
+		s.G2[1] = sw_bls24315.NewG2Affine(tVk.G2[1])
 	default:
 		return ret, fmt.Errorf("unknown type parametrization")
 	}
@@ -222,44 +309,279 @@ func ValueOfVerifyingKey[G2El algebra.G2ElementT](vk any) (VerifyingKey[G2El], e
 
 // Verifier allows verifying KZG opening proofs.
 type Verifier[FR emulated.FieldParams, G1El algebra.G1ElementT, G2El algebra.G2ElementT, GtEl algebra.G2ElementT] struct {
-	VerifyingKey[G2El]
-
-	curve   algebra.Curve[FR, G1El]
-	pairing algebra.Pairing[G1El, G2El, GtEl]
+	api       frontend.API
+	scalarApi *emulated.Field[FR]
+	curve     algebra.Curve[FR, G1El]
+	pairing   algebra.Pairing[G1El, G2El, GtEl]
 }
 
 // NewVerifier initializes a new Verifier instance.
-func NewVerifier[FR emulated.FieldParams, G1El algebra.G1ElementT, G2El algebra.G2ElementT, GtEl algebra.G2ElementT](vk VerifyingKey[G2El], curve algebra.Curve[FR, G1El], pairing algebra.Pairing[G1El, G2El, GtEl]) *Verifier[FR, G1El, G2El, GtEl] {
-	return &Verifier[FR, G1El, G2El, GtEl]{
-		VerifyingKey: vk,
-		curve:        curve,
-		pairing:      pairing,
+func NewVerifier[FR emulated.FieldParams, G1El algebra.G1ElementT, G2El algebra.G2ElementT, GtEl algebra.G2ElementT](api frontend.API) (*Verifier[FR, G1El, G2El, GtEl], error) {
+	curve, err := algebra.GetCurve[FR, G1El](api)
+	if err != nil {
+		return nil, err
+	}
+	scalarApi, err := emulated.NewField[FR](api)
+	if err != nil {
+		return nil, err
 	}
+	pairing, err := algebra.GetPairing[G1El, G2El, GtEl](api)
+	if err != nil {
+		return nil, err
+	}
+	return &Verifier[FR, G1El, G2El, GtEl]{
+		api:       api,
+		scalarApi: scalarApi,
+		curve:     curve,
+		pairing:   pairing,
+	}, nil
 }
 
-// AssertProof asserts the validity of the opening proof for the given
-// commitment.
-func (vk *Verifier[FR, G1El, G2El, GtEl]) AssertProof(commitment Commitment[G1El], proof OpeningProof[FR, G1El]) error {
-	// [f(a)]G₁
-	claimedValueG1 := vk.curve.ScalarMulBase(&proof.ClaimedValue)
+// CheckOpeningProof asserts the validity of the opening proof for the given
+// commitment at point.
+func (v *Verifier[FR, G1El, G2El, GTEl]) CheckOpeningProof(commitment Commitment[G1El], proof OpeningProof[FR, G1El], point emulated.Element[FR], vk VerifyingKey[G1El, G2El]) error {
+
+	claimedValueG1 := v.curve.ScalarMulBase(&proof.ClaimedValue)
 
 	// [f(α) - f(a)]G₁
-	fminusfaG1 := vk.curve.Neg(claimedValueG1)
-	fminusfaG1 = vk.curve.Add(fminusfaG1, &commitment.G1El)
+	fminusfaG1 := v.curve.Neg(claimedValueG1)
+	fminusfaG1 = v.curve.Add(fminusfaG1, &commitment.G1El)
 
 	// [-H(α)]G₁
-	negQuotientPoly := vk.curve.Neg(&proof.QuotientPoly)
+	negQuotientPoly := v.curve.Neg(&proof.Quotient)
 
 	// [f(α) - f(a) + a*H(α)]G₁
-	totalG1 := vk.curve.ScalarMul(&proof.QuotientPoly, &proof.Point)
-	totalG1 = vk.curve.Add(totalG1, fminusfaG1)
+	totalG1 := v.curve.ScalarMul(&proof.Quotient, &point)
+	totalG1 = v.curve.Add(totalG1, fminusfaG1)
 
 	// e([f(α)-f(a)+aH(α)]G₁], G₂).e([-H(α)]G₁, [α]G₂) == 1
-	if err := vk.pairing.PairingCheck(
+	if err := v.pairing.PairingCheck(
 		[]*G1El{totalG1, negQuotientPoly},
-		[]*G2El{&vk.SRS[0], &vk.SRS[1]},
+		[]*G2El{&vk.G2[0], &vk.G2[1]},
 	); err != nil {
 		return fmt.Errorf("pairing check: %w", err)
 	}
 	return nil
 }
+
+func (v *Verifier[FR, G1El, G2El, GTEl]) BatchVerifySinglePoint(digests []Commitment[G1El], batchOpeningProof BatchOpeningProof[FR, G1El], point emulated.Element[FR], vk VerifyingKey[G1El, G2El], dataTranscript ...frontend.Variable) error {
+	// fold the proof
+	foldedProof, foldedDigest, err := v.FoldProof(digests, batchOpeningProof, point, dataTranscript...)
+	if err != nil {
+		return fmt.Errorf("fold proofs: %w", err)
+	}
+	// verify the foldedProof against the foldedDigest
+	err = v.CheckOpeningProof(foldedDigest, foldedProof, point, vk)
+	if err != nil {
+		return fmt.Errorf("check opening proof: %w", err)
+	}
+	return nil
+}
+
+func (v *Verifier[FR, G1El, G2El, GTEl]) BatchVerifyMultiPoints(digests []Commitment[G1El], proofs []OpeningProof[FR, G1El], points []emulated.Element[FR], vk VerifyingKey[G1El, G2El]) error {
+	var fr FR
+
+	// check consistency nb proogs vs nb digests
+	if len(digests) != len(proofs) {
+		return fmt.Errorf("number of commitments doesn't match number of proofs")
+	}
+	if len(digests) != len(points) {
+		return fmt.Errorf("number of commitments doesn't match number of points ")
+	}
+
+	// len(digests) should be nonzero because of randomNumbers
+	if len(digests) == 0 {
+		return fmt.Errorf("number of digests should be nonzero")
+	}
+
+	// if only one digest, call Verify
+	if len(digests) == 1 {
+		return v.CheckOpeningProof(digests[0], proofs[0], points[0], vk)
+	}
+
+	// sample random numbers λᵢ for sampling
+	randomNumbers := make([]*emulated.Element[FR], len(digests))
+	randomNumbers[0] = v.scalarApi.One()
+	whSnark, err := recursion.NewHash(v.api, fr.Modulus(), true)
+	if err != nil {
+		return err
+	}
+	for i := 0; i < len(digests); i++ {
+		marshalledG1 := v.curve.MarshalG1(digests[i].G1El)
+		whSnark.Write(marshalledG1...)
+		marshalledG1 = v.curve.MarshalG1(proofs[i].Quotient)
+		whSnark.Write(marshalledG1...)
+		marshalledScalar := v.curve.MarshalScalar(proofs[i].ClaimedValue)
+		whSnark.Write(marshalledScalar...)
+		marshalledScalar = v.curve.MarshalScalar(points[i])
+		whSnark.Write(marshalledScalar...)
+	}
+
+	seed := whSnark.Sum()
+	binSeed := v.api.ToBinary(seed)
+	randomNumbers[1] = v.scalarApi.FromBits(binSeed...)
+
+	for i := 2; i < len(randomNumbers); i++ {
+		// TODO use real random numbers, follow the solidity smart contract to know which variables are used as seed
+		randomNumbers[i] = v.scalarApi.Mul(randomNumbers[1], randomNumbers[i-1])
+	}
+
+	// fold the committed quotients compute ∑ᵢλᵢ[Hᵢ(α)]G₁
+	var foldedQuotients *G1El
+	quotients := make([]G1El, len(proofs))
+	for i := 0; i < len(randomNumbers); i++ {
+		quotients[i] = proofs[i].Quotient
+	}
+	foldedQuotients = v.curve.ScalarMul(&quotients[0], randomNumbers[0])
+	for i := 1; i < len(digests); i++ {
+		tmp := v.curve.ScalarMul(&quotients[i], randomNumbers[i])
+		foldedQuotients = v.curve.Add(tmp, foldedQuotients)
+	}
+
+	// fold digests and evals
+	evals := make([]emulated.Element[FR], len(digests))
+
+	// fold the digests: ∑ᵢλᵢ[f_i(α)]G₁
+	// fold the evals  : ∑ᵢλᵢfᵢ(aᵢ)
+	for i := 0; i < len(digests); i++ {
+		evals[i] = proofs[i].ClaimedValue
+	}
+
+	foldedDigests, foldedEvals := v.fold(digests, evals, randomNumbers)
+
+	// compute commitment to folded Eval  [∑ᵢλᵢfᵢ(aᵢ)]G₁
+	foldedEvalsCommit := v.curve.ScalarMul(&vk.G1, foldedEvals)
+
+	// compute foldedDigests = ∑ᵢλᵢ[fᵢ(α)]G₁ - [∑ᵢλᵢfᵢ(aᵢ)]G₁
+	tmp := v.curve.Neg(foldedEvalsCommit)
+	var foldedDigest *G1El
+	foldedDigest = v.curve.Add(&foldedDigests.G1El, tmp)
+
+	// combien the points and the quotients using γᵢ
+	// ∑ᵢλᵢ[p_i]([Hᵢ(α)]G₁)
+	var foldedPointsQuotients *G1El
+	for i := 0; i < len(randomNumbers); i++ {
+		randomNumbers[i] = v.scalarApi.Mul(randomNumbers[i], &points[i])
+	}
+	foldedPointsQuotients = v.curve.ScalarMul(&quotients[0], randomNumbers[0])
+	for i := 1; i < len(digests); i++ {
+		tmp = v.curve.ScalarMul(&quotients[i], randomNumbers[i])
+		foldedPointsQuotients = v.curve.Add(foldedPointsQuotients, tmp)
+	}
+
+	// ∑ᵢλᵢ[f_i(α)]G₁ - [∑ᵢλᵢfᵢ(aᵢ)]G₁ + ∑ᵢλᵢ[p_i]([Hᵢ(α)]G₁)
+	// = [∑ᵢλᵢf_i(α) - ∑ᵢλᵢfᵢ(aᵢ) + ∑ᵢλᵢpᵢHᵢ(α)]G₁
+	foldedDigest = v.curve.Add(foldedDigest, foldedPointsQuotients)
+
+	// -∑ᵢλᵢ[Qᵢ(α)]G₁
+	// foldedQuotients.Neg(&foldedQuotients)
+	foldedQuotients = v.curve.Neg(foldedQuotients)
+
+	// pairing check
+	err = v.pairing.PairingCheck(
+		[]*G1El{foldedDigest, foldedQuotients},
+		[]*G2El{&vk.G2[0], &vk.G2[1]},
+	)
+	if err != nil {
+		return fmt.Errorf("pairingcheck: %w", err)
+	}
+
+	return err
+}
+
+func (v *Verifier[FR, G1El, G2El, GTEl]) FoldProof(digests []Commitment[G1El], batchOpeningProof BatchOpeningProof[FR, G1El], point emulated.Element[FR], dataTranscript ...frontend.Variable) (OpeningProof[FR, G1El], Commitment[G1El], error) {
+	var retP OpeningProof[FR, G1El]
+	var retC Commitment[G1El]
+	nbDigests := len(digests)
+
+	// check consistency between numbers of claims vs number of digests
+	if nbDigests != len(batchOpeningProof.ClaimedValues) {
+		return retP, retC, fmt.Errorf("length mismatch for digests and claimed values")
+	}
+
+	// derive the challenge γ, binded to the point and the commitments
+	gamma, err := v.deriveGamma(point, digests, batchOpeningProof.ClaimedValues, dataTranscript...)
+	if err != nil {
+		return retP, retC, err
+	}
+
+	// fold the claimed values and digests
+	// gammai = [1,γ,γ²,..,γⁿ⁻¹]
+	gammai := make([]*emulated.Element[FR], nbDigests)
+	gammai[0] = v.scalarApi.One()
+	if nbDigests > 1 {
+		gammai[1] = gamma
+	}
+	for i := 2; i < nbDigests; i++ {
+		gammai[i] = v.scalarApi.Mul(gammai[i-1], gamma)
+	}
+	foldedDigests, foldedEvaluations := v.fold(digests, batchOpeningProof.ClaimedValues, gammai)
+	return OpeningProof[FR, G1El]{
+		Quotient:     batchOpeningProof.Quotient,
+		ClaimedValue: *foldedEvaluations,
+	}, foldedDigests, nil
+
+}
+
+// deriveGamma derives a challenge using Fiat Shamir to fold proofs.
+// dataTranscript are supposed to be bits.
+func (v *Verifier[FR, G1El, G2El, GTEl]) deriveGamma(point emulated.Element[FR], digests []Commitment[G1El], claimedValues []emulated.Element[FR], dataTranscript ...frontend.Variable) (*emulated.Element[FR], error) {
+	var fr FR
+	fs, err := recursion.NewTranscript(v.api, fr.Modulus(), []string{"gamma"})
+	if err != nil {
+		return nil, fmt.Errorf("new transcript: %w", err)
+	}
+	if err := fs.Bind("gamma", v.curve.MarshalScalar(point)); err != nil {
+		return nil, fmt.Errorf("bind point: %w", err)
+	}
+
+	for i := range digests {
+		if err := fs.Bind("gamma", v.curve.MarshalG1(digests[i].G1El)); err != nil {
+			return nil, fmt.Errorf("bind %d-th commitment: %w", i, err)
+		}
+	}
+	for i := range claimedValues {
+		if err := fs.Bind("gamma", v.curve.MarshalScalar(claimedValues[i])); err != nil {
+			return nil, fmt.Errorf("bing %d-th claimed value: %w", i, err)
+		}
+	}
+
+	if err := fs.Bind("gamma", dataTranscript); err != nil {
+		return nil, fmt.Errorf("bind data transcript: %w", err)
+	}
+
+	gamma, err := fs.ComputeChallenge("gamma")
+	if err != nil {
+		return nil, fmt.Errorf("compute challenge: %w", err)
+	}
+	bGamma := v.api.ToBinary(gamma)
+	gammaS := v.scalarApi.FromBits(bGamma...)
+
+	return gammaS, nil
+}
+
+func (v *Verifier[FR, G1El, G2El, GTEl]) fold(digests []Commitment[G1El], fai []emulated.Element[FR], ci []*emulated.Element[FR]) (Commitment[G1El], *emulated.Element[FR]) {
+	// length inconsistency between digests and evaluations should have been done before calling this function
+	nbDigests := len(digests)
+
+	// fold the claimed values ∑ᵢcᵢf(aᵢ)
+	var tmp *emulated.Element[FR]
+	foldedEvaluations := v.scalarApi.Zero()
+	for i := 0; i < nbDigests; i++ {
+		tmp = v.scalarApi.Mul(&fai[i], ci[i])
+		foldedEvaluations = v.scalarApi.Add(foldedEvaluations, tmp)
+	}
+
+	// fold the digests ∑ᵢ[cᵢ]([fᵢ(α)]G₁)
+	foldedDigest := v.curve.ScalarMul(&digests[0].G1El, ci[0])
+	for i := 1; i < nbDigests; i++ {
+		tmp := v.curve.ScalarMul(&digests[i].G1El, ci[i])
+		foldedDigest = v.curve.Add(tmp, foldedDigest)
+	}
+
+	// folding done
+	return Commitment[G1El]{
+		G1El: *foldedDigest,
+	}, foldedEvaluations
+
+}
diff --git a/std/commitments/kzg/verifier_test.go b/std/commitments/kzg/verifier_test.go
index 2d3eefff79..9d57b62b4f 100644
--- a/std/commitments/kzg/verifier_test.go
+++ b/std/commitments/kzg/verifier_test.go
@@ -3,6 +3,7 @@ package kzg
 import (
 	"crypto/rand"
 	"fmt"
+	"math/big"
 	"testing"
 
 	"github.com/consensys/gnark-crypto/ecc"
@@ -21,6 +22,7 @@ import (
 	bw6761 "github.com/consensys/gnark-crypto/ecc/bw6-761"
 	fr_bw6761 "github.com/consensys/gnark-crypto/ecc/bw6-761/fr"
 	kzg_bw6761 "github.com/consensys/gnark-crypto/ecc/bw6-761/kzg"
+	"github.com/consensys/gnark/backend"
 	"github.com/consensys/gnark/frontend"
 	"github.com/consensys/gnark/std/algebra"
 	"github.com/consensys/gnark/std/algebra/emulated/sw_bls12381"
@@ -29,6 +31,7 @@ import (
 	"github.com/consensys/gnark/std/algebra/native/sw_bls12377"
 	"github.com/consensys/gnark/std/algebra/native/sw_bls24315"
 	"github.com/consensys/gnark/std/math/emulated"
+	"github.com/consensys/gnark/std/recursion"
 	"github.com/consensys/gnark/test"
 )
 
@@ -38,22 +41,18 @@ const (
 )
 
 type KZGVerificationCircuit[FR emulated.FieldParams, G1El algebra.G1ElementT, G2El algebra.G2ElementT, GTEl algebra.GtElementT] struct {
-	VerifyingKey[G2El]
+	VerifyingKey[G1El, G2El]
 	Commitment[G1El]
 	OpeningProof[FR, G1El]
+	Point emulated.Element[FR]
 }
 
 func (c *KZGVerificationCircuit[FR, G1El, G2El, GTEl]) Define(api frontend.API) error {
-	curve, err := algebra.GetCurve[FR, G1El](api)
+	verifier, err := NewVerifier[FR, G1El, G2El, GTEl](api)
 	if err != nil {
-		return fmt.Errorf("get curve: %w", err)
+		return fmt.Errorf("new verifier: %w", err)
 	}
-	pairing, err := algebra.GetPairing[G1El, G2El, GTEl](api)
-	if err != nil {
-		return fmt.Errorf("get pairing: %w", err)
-	}
-	verifier := NewVerifier(c.VerifyingKey, curve, pairing)
-	if err := verifier.AssertProof(c.Commitment, c.OpeningProof); err != nil {
+	if err := verifier.CheckOpeningProof(c.Commitment, c.OpeningProof, c.Point, c.VerifyingKey); err != nil {
 		return fmt.Errorf("assert proof: %w", err)
 	}
 	return nil
@@ -86,15 +85,18 @@ func TestKZGVerificationEmulated(t *testing.T) {
 
 	wCmt, err := ValueOfCommitment[sw_bn254.G1Affine](com)
 	assert.NoError(err)
-	wProof, err := ValueOfOpeningProof[sw_bn254.ScalarField, sw_bn254.G1Affine](point, proof)
+	wProof, err := ValueOfOpeningProof[sw_bn254.ScalarField, sw_bn254.G1Affine](proof)
+	assert.NoError(err)
+	wVk, err := ValueOfVerifyingKey[sw_bn254.G1Affine, sw_bn254.G2Affine](srs.Vk)
 	assert.NoError(err)
-	wVk, err := ValueOfVerifyingKey[sw_bn254.G2Affine](srs.Vk)
+	wPt, err := ValueOfScalar[sw_bn254.ScalarField](point)
 	assert.NoError(err)
 
 	assignment := KZGVerificationCircuit[sw_bn254.ScalarField, sw_bn254.G1Affine, sw_bn254.G2Affine, sw_bn254.GTEl]{
 		VerifyingKey: wVk,
 		Commitment:   wCmt,
 		OpeningProof: wProof,
+		Point:        wPt,
 	}
 	assert.CheckCircuit(&KZGVerificationCircuit[sw_bn254.ScalarField, sw_bn254.G1Affine, sw_bn254.G2Affine, sw_bn254.GTEl]{}, test.WithValidAssignment(&assignment))
 }
@@ -126,15 +128,18 @@ func TestKZGVerificationEmulated2(t *testing.T) {
 
 	wCmt, err := ValueOfCommitment[sw_bls12381.G1Affine](com)
 	assert.NoError(err)
-	wProof, err := ValueOfOpeningProof[sw_bls12381.ScalarField, sw_bls12381.G1Affine](point, proof)
+	wProof, err := ValueOfOpeningProof[sw_bls12381.ScalarField, sw_bls12381.G1Affine](proof)
 	assert.NoError(err)
-	wVk, err := ValueOfVerifyingKey[sw_bls12381.G2Affine](srs.Vk)
+	wVk, err := ValueOfVerifyingKey[sw_bls12381.G1Affine, sw_bls12381.G2Affine](srs.Vk)
+	assert.NoError(err)
+	wPt, err := ValueOfScalar[sw_bls12381.ScalarField](point)
 	assert.NoError(err)
 
 	assignment := KZGVerificationCircuit[sw_bls12381.ScalarField, sw_bls12381.G1Affine, sw_bls12381.G2Affine, sw_bls12381.GTEl]{
 		VerifyingKey: wVk,
 		Commitment:   wCmt,
 		OpeningProof: wProof,
+		Point:        wPt,
 	}
 	assert.CheckCircuit(&KZGVerificationCircuit[sw_bls12381.ScalarField, sw_bls12381.G1Affine, sw_bls12381.G2Affine, sw_bls12381.GTEl]{}, test.WithValidAssignment(&assignment))
 }
@@ -166,15 +171,18 @@ func TestKZGVerificationEmulated3(t *testing.T) {
 
 	wCmt, err := ValueOfCommitment[sw_bw6761.G1Affine](com)
 	assert.NoError(err)
-	wProof, err := ValueOfOpeningProof[sw_bw6761.ScalarField, sw_bw6761.G1Affine](point, proof)
+	wProof, err := ValueOfOpeningProof[sw_bw6761.ScalarField, sw_bw6761.G1Affine](proof)
+	assert.NoError(err)
+	wVk, err := ValueOfVerifyingKey[sw_bw6761.G1Affine, sw_bw6761.G2Affine](srs.Vk)
 	assert.NoError(err)
-	wVk, err := ValueOfVerifyingKey[sw_bw6761.G2Affine](srs.Vk)
+	wPt, err := ValueOfScalar[sw_bw6761.ScalarField](point)
 	assert.NoError(err)
 
 	assignment := KZGVerificationCircuit[sw_bw6761.ScalarField, sw_bw6761.G1Affine, sw_bw6761.G2Affine, sw_bw6761.GTEl]{
 		VerifyingKey: wVk,
 		Commitment:   wCmt,
 		OpeningProof: wProof,
+		Point:        wPt,
 	}
 	assert.CheckCircuit(&KZGVerificationCircuit[sw_bw6761.ScalarField, sw_bw6761.G1Affine, sw_bw6761.G2Affine, sw_bw6761.GTEl]{}, test.WithValidAssignment(&assignment), test.WithCurves(ecc.BN254))
 }
@@ -206,15 +214,18 @@ func TestKZGVerificationTwoChain(t *testing.T) {
 
 	wCmt, err := ValueOfCommitment[sw_bls12377.G1Affine](com)
 	assert.NoError(err)
-	wProof, err := ValueOfOpeningProof[sw_bls12377.ScalarField, sw_bls12377.G1Affine](point, proof)
+	wProof, err := ValueOfOpeningProof[sw_bls12377.ScalarField, sw_bls12377.G1Affine](proof)
 	assert.NoError(err)
-	wVk, err := ValueOfVerifyingKey[sw_bls12377.G2Affine](srs.Vk)
+	wVk, err := ValueOfVerifyingKey[sw_bls12377.G1Affine, sw_bls12377.G2Affine](srs.Vk)
+	assert.NoError(err)
+	wPt, err := ValueOfScalar[sw_bls12377.ScalarField](point)
 	assert.NoError(err)
 
 	assignment := KZGVerificationCircuit[sw_bls12377.ScalarField, sw_bls12377.G1Affine, sw_bls12377.G2Affine, sw_bls12377.GT]{
 		VerifyingKey: wVk,
 		Commitment:   wCmt,
 		OpeningProof: wProof,
+		Point:        wPt,
 	}
 
 	assert.CheckCircuit(&KZGVerificationCircuit[sw_bls12377.ScalarField, sw_bls12377.G1Affine, sw_bls12377.G2Affine, sw_bls12377.GT]{}, test.WithValidAssignment(&assignment), test.WithCurves(ecc.BW6_761))
@@ -247,15 +258,18 @@ func TestKZGVerificationTwoChain2(t *testing.T) {
 
 	wCmt, err := ValueOfCommitment[sw_bls24315.G1Affine](com)
 	assert.NoError(err)
-	wProof, err := ValueOfOpeningProof[sw_bls24315.ScalarField, sw_bls24315.G1Affine](point, proof)
+	wProof, err := ValueOfOpeningProof[sw_bls24315.ScalarField, sw_bls24315.G1Affine](proof)
+	assert.NoError(err)
+	wVk, err := ValueOfVerifyingKey[sw_bls24315.G1Affine, sw_bls24315.G2Affine](srs.Vk)
 	assert.NoError(err)
-	wVk, err := ValueOfVerifyingKey[sw_bls24315.G2Affine](srs.Vk)
+	wPt, err := ValueOfScalar[sw_bls24315.ScalarField](point)
 	assert.NoError(err)
 
 	assignment := KZGVerificationCircuit[sw_bls24315.ScalarField, sw_bls24315.G1Affine, sw_bls24315.G2Affine, sw_bls24315.GT]{
 		VerifyingKey: wVk,
 		Commitment:   wCmt,
 		OpeningProof: wProof,
+		Point:        wPt,
 	}
 
 	assert.CheckCircuit(&KZGVerificationCircuit[sw_bls24315.ScalarField, sw_bls24315.G1Affine, sw_bls24315.G2Affine, sw_bls24315.GT]{}, test.WithValidAssignment(&assignment), test.WithCurves(ecc.BW6_633))
@@ -306,7 +320,7 @@ func TestValueOfOpeningProof(t *testing.T) {
 			H:            G1,
 			ClaimedValue: value,
 		}
-		assignment, err := ValueOfOpeningProof[sw_bn254.ScalarField, sw_bn254.G1Affine](point, proof)
+		assignment, err := ValueOfOpeningProof[sw_bn254.ScalarField, sw_bn254.G1Affine](proof)
 		assert.NoError(err)
 		_ = assignment
 	}, "bn254")
@@ -319,7 +333,7 @@ func TestValueOfOpeningProof(t *testing.T) {
 			H:            G1,
 			ClaimedValue: value,
 		}
-		assignment, err := ValueOfOpeningProof[sw_bls12377.ScalarField, sw_bls12377.G1Affine](point, proof)
+		assignment, err := ValueOfOpeningProof[sw_bls12377.ScalarField, sw_bls12377.G1Affine](proof)
 		assert.NoError(err)
 		_ = assignment
 	}, "bls12377")
@@ -332,7 +346,7 @@ func TestValueOfOpeningProof(t *testing.T) {
 			H:            G1,
 			ClaimedValue: value,
 		}
-		assignment, err := ValueOfOpeningProof[sw_bls12381.ScalarField, sw_bls12381.G1Affine](point, proof)
+		assignment, err := ValueOfOpeningProof[sw_bls12381.ScalarField, sw_bls12381.G1Affine](proof)
 		assert.NoError(err)
 		_ = assignment
 	}, "bls12381")
@@ -345,7 +359,7 @@ func TestValueOfOpeningProof(t *testing.T) {
 			H:            G1,
 			ClaimedValue: value,
 		}
-		assignment, err := ValueOfOpeningProof[sw_bw6761.ScalarField, sw_bw6761.G1Affine](point, proof)
+		assignment, err := ValueOfOpeningProof[sw_bw6761.ScalarField, sw_bw6761.G1Affine](proof)
 		assert.NoError(err)
 		_ = assignment
 	}, "bw6761")
@@ -358,7 +372,7 @@ func TestValueOfOpeningProof(t *testing.T) {
 			H:            G1,
 			ClaimedValue: value,
 		}
-		assignment, err := ValueOfOpeningProof[sw_bls24315.ScalarField, sw_bls24315.G1Affine](point, proof)
+		assignment, err := ValueOfOpeningProof[sw_bls24315.ScalarField, sw_bls24315.G1Affine](proof)
 		assert.NoError(err)
 		_ = assignment
 	}, "bls24315")
@@ -371,7 +385,7 @@ func TestValueOfSRS(t *testing.T) {
 		vk := kzg_bn254.VerifyingKey{
 			G2: [2]bn254.G2Affine{G2, G2},
 		}
-		assignment, err := ValueOfVerifyingKey[sw_bn254.G2Affine](vk)
+		assignment, err := ValueOfVerifyingKey[sw_bn254.G1Affine, sw_bn254.G2Affine](vk)
 		assert.NoError(err)
 		_ = assignment
 	}, "bn254")
@@ -380,7 +394,7 @@ func TestValueOfSRS(t *testing.T) {
 		vk := kzg_bls12377.VerifyingKey{
 			G2: [2]bls12377.G2Affine{G2, G2},
 		}
-		assignment, err := ValueOfVerifyingKey[sw_bls12377.G2Affine](vk)
+		assignment, err := ValueOfVerifyingKey[sw_bls12377.G1Affine, sw_bls12377.G2Affine](vk)
 		assert.NoError(err)
 		_ = assignment
 	}, "bls12377")
@@ -389,7 +403,7 @@ func TestValueOfSRS(t *testing.T) {
 		vk := kzg_bls12381.VerifyingKey{
 			G2: [2]bls12381.G2Affine{G2, G2},
 		}
-		assignment, err := ValueOfVerifyingKey[sw_bls12381.G2Affine](vk)
+		assignment, err := ValueOfVerifyingKey[sw_bls12381.G1Affine, sw_bls12381.G2Affine](vk)
 		assert.NoError(err)
 		_ = assignment
 	}, "bls12381")
@@ -398,7 +412,7 @@ func TestValueOfSRS(t *testing.T) {
 		vk := kzg_bw6761.VerifyingKey{
 			G2: [2]bw6761.G2Affine{G2, G2},
 		}
-		assignment, err := ValueOfVerifyingKey[sw_bw6761.G2Affine](vk)
+		assignment, err := ValueOfVerifyingKey[sw_bw6761.G1Affine, sw_bw6761.G2Affine](vk)
 		assert.NoError(err)
 		_ = assignment
 	}, "bw6761")
@@ -407,8 +421,276 @@ func TestValueOfSRS(t *testing.T) {
 		vk := kzg_bls24315.VerifyingKey{
 			G2: [2]bls24315.G2Affine{G2, G2},
 		}
-		assignment, err := ValueOfVerifyingKey[sw_bls24315.G2Affine](vk)
+		assignment, err := ValueOfVerifyingKey[sw_bls24315.G1Affine, sw_bls24315.G2Affine](vk)
 		assert.NoError(err)
 		_ = assignment
 	}, "bls24315")
 }
+
+type FoldProofTest[FR emulated.FieldParams, G1El, G2El, GTEl any] struct {
+	Point                emulated.Element[FR]
+	Digests              [10]Commitment[G1El]
+	BatchOpeningProof    BatchOpeningProof[FR, G1El]
+	ExpectedFoldedProof  OpeningProof[FR, G1El]
+	ExpectedFoldedDigest Commitment[G1El]
+}
+
+func (c *FoldProofTest[FR, G1El, G2El, GTEl]) Define(api frontend.API) error {
+	verifier, err := NewVerifier[FR, G1El, G2El, GTEl](api)
+	if err != nil {
+		return fmt.Errorf("get pairing: %w", err)
+	}
+
+	foldedProof, foldedDigests, err := verifier.FoldProof(c.Digests[:], c.BatchOpeningProof, c.Point)
+	if err != nil {
+		return err
+	}
+
+	curve, err := algebra.GetCurve[FR, G1El](api)
+	if err != nil {
+		return err
+	}
+
+	curve.AssertIsEqual(&foldedDigests.G1El, &c.ExpectedFoldedDigest.G1El)
+	curve.AssertIsEqual(&foldedProof.Quotient, &c.ExpectedFoldedProof.Quotient)
+
+	f, err := emulated.NewField[FR](api)
+	if err != nil {
+		return err
+	}
+	f.AssertIsEqual(&foldedProof.ClaimedValue, &c.ExpectedFoldedProof.ClaimedValue)
+
+	return nil
+}
+func TestFoldProof(t *testing.T) {
+
+	assert := test.NewAssert(t)
+
+	// prepare test data
+	alpha, err := rand.Int(rand.Reader, ecc.BN254.ScalarField())
+	assert.NoError(err)
+	srs, err := kzg_bn254.NewSRS(kzgSize, alpha)
+	assert.NoError(err)
+
+	var polynomials [10][]fr_bn254.Element
+	var coms [10]kzg_bn254.Digest
+	for i := 0; i < 10; i++ {
+		polynomials[i] = make([]fr_bn254.Element, polynomialSize)
+		for j := 0; j < polynomialSize; j++ {
+			polynomials[i][j].SetRandom()
+		}
+		coms[i], err = kzg_bn254.Commit(polynomials[i], srs.Pk)
+		assert.NoError(err)
+	}
+
+	var point fr_bn254.Element
+	point.SetRandom()
+	var target big.Int
+	target.SetUint64(1)
+	nbBits := ecc.BLS12_381.ScalarField().BitLen()
+	nn := ((nbBits+7)/8)*8 - 8
+	target.Lsh(&target, uint(nn))
+	h, err := recursion.NewShort(ecc.BLS12_381.ScalarField(), &target)
+	assert.NoError(err)
+
+	batchOpeningProof, err := kzg_bn254.BatchOpenSinglePoint(polynomials[:], coms[:], point, h, srs.Pk)
+	assert.NoError(err)
+
+	foldedProofs, foldedDigest, err := kzg_bn254.FoldProof(coms[:], &batchOpeningProof, point, h)
+	assert.NoError(err)
+
+	// prepare witness
+	wPoint, err := ValueOfScalar[emulated.BN254Fr](point)
+	assert.NoError(err)
+	var wDigests [10]Commitment[sw_bn254.G1Affine]
+	for i := 0; i < 10; i++ {
+		wDigests[i], err = ValueOfCommitment[sw_bn254.G1Affine](coms[i])
+		assert.NoError(err)
+	}
+	wBatchOpeningProof, err := ValueOfBatchOpeningProof[emulated.BN254Fr, sw_bn254.G1Affine](batchOpeningProof)
+	assert.NoError(err)
+	wExpectedFoldedProof, err := ValueOfOpeningProof[emulated.BN254Fr, sw_bn254.G1Affine](foldedProofs)
+	assert.NoError(err)
+	wExpectedFoldedDigest, err := ValueOfCommitment[sw_bn254.G1Affine](foldedDigest)
+	assert.NoError(err)
+
+	assignment := FoldProofTest[emulated.BN254Fr, sw_bn254.G1Affine, sw_bn254.G2Affine, sw_bn254.GTEl]{
+		Point:                wPoint,
+		Digests:              wDigests,
+		BatchOpeningProof:    wBatchOpeningProof,
+		ExpectedFoldedProof:  wExpectedFoldedProof,
+		ExpectedFoldedDigest: wExpectedFoldedDigest,
+	}
+
+	var circuit FoldProofTest[emulated.BN254Fr, sw_bn254.G1Affine, sw_bn254.G2Affine, sw_bn254.GTEl]
+	circuit.BatchOpeningProof.ClaimedValues = make([]emulated.Element[emulated.BN254Fr], 10)
+	assert.CheckCircuit(&circuit, test.WithValidAssignment(&assignment), test.WithCurves(ecc.BLS12_381), test.WithBackends(backend.PLONK))
+
+}
+
+type BatchVerifySinglePointTest[S emulated.FieldParams, G1El, G2El, GTEl any] struct {
+	Vk                VerifyingKey[G1El, G2El]
+	Point             emulated.Element[S]
+	Digests           [10]Commitment[G1El]
+	BatchOpeningProof BatchOpeningProof[S, G1El]
+}
+
+func (c *BatchVerifySinglePointTest[S, G1El, G2El, GTEl]) Define(api frontend.API) error {
+	verifier, err := NewVerifier[S, G1El, G2El, GTEl](api)
+	if err != nil {
+		return fmt.Errorf("get pairing: %w", err)
+	}
+	verifier.BatchVerifySinglePoint(c.Digests[:], c.BatchOpeningProof, c.Point, c.Vk)
+
+	return nil
+}
+
+func TestBatchVerifySinglePoint(t *testing.T) {
+
+	assert := test.NewAssert(t)
+
+	// prepare test data
+	alpha, err := rand.Int(rand.Reader, ecc.BN254.ScalarField())
+	assert.NoError(err)
+	srs, err := kzg_bn254.NewSRS(kzgSize, alpha)
+	assert.NoError(err)
+
+	var polynomials [10][]fr_bn254.Element
+	var coms [10]kzg_bn254.Digest
+	for i := 0; i < 10; i++ {
+		polynomials[i] = make([]fr_bn254.Element, polynomialSize)
+		for j := 0; j < polynomialSize; j++ {
+			polynomials[i][j].SetRandom()
+		}
+		coms[i], err = kzg_bn254.Commit(polynomials[i], srs.Pk)
+		assert.NoError(err)
+	}
+
+	// random point at which the polynomials are evaluated
+	var point fr_bn254.Element
+	point.SetRandom()
+
+	// build short hash, we pick a number one byte less than the snark field...
+	var target big.Int
+	target.SetUint64(1)
+	nbBits := ecc.BLS12_381.ScalarField().BitLen()
+	nn := ((nbBits+7)/8)*8 - 8
+	target.Lsh(&target, uint(nn))
+	h, err := recursion.NewShort(ecc.BLS12_381.ScalarField(), &target)
+	assert.NoError(err)
+
+	batchOpeningProof, err := kzg_bn254.BatchOpenSinglePoint(polynomials[:], coms[:], point, h, srs.Pk)
+	assert.NoError(err)
+
+	err = kzg_bn254.BatchVerifySinglePoint(coms[:], &batchOpeningProof, point, h, srs.Vk)
+	assert.NoError(err)
+
+	// prepare witness
+	wVk, err := ValueOfVerifyingKey[sw_bn254.G1Affine, sw_bn254.G2Affine](srs.Vk)
+	assert.NoError(err)
+	wPoint, err := ValueOfScalar[emulated.BN254Fr](point)
+	assert.NoError(err)
+	var wDigests [10]Commitment[sw_bn254.G1Affine]
+	for i := 0; i < 10; i++ {
+		wDigests[i], err = ValueOfCommitment[sw_bn254.G1Affine](coms[i])
+		assert.NoError(err)
+	}
+	wBatchOpeningProof, err := ValueOfBatchOpeningProof[emulated.BN254Fr, sw_bn254.G1Affine](batchOpeningProof)
+	assert.NoError(err)
+
+	assignment := BatchVerifySinglePointTest[emulated.BN254Fr, sw_bn254.G1Affine, sw_bn254.G2Affine, sw_bn254.GTEl]{
+		Vk:                wVk,
+		Point:             wPoint,
+		Digests:           wDigests,
+		BatchOpeningProof: wBatchOpeningProof,
+	}
+
+	var circuit BatchVerifySinglePointTest[emulated.BN254Fr, sw_bn254.G1Affine, sw_bn254.G2Affine, sw_bn254.GTEl]
+	circuit.BatchOpeningProof.ClaimedValues = make([]emulated.Element[emulated.BN254Fr], 10)
+	assert.CheckCircuit(&circuit, test.WithValidAssignment(&assignment), test.WithCurves(ecc.BLS12_381), test.WithBackends(backend.PLONK))
+
+}
+
+type BatchVerifyMultiPointsTest[S emulated.FieldParams, G1El, G2El, GTEl any] struct {
+	Vk      VerifyingKey[G1El, G2El]
+	Digests [4]Commitment[G1El]
+	Proofs  [4]OpeningProof[S, G1El]
+	Points  [4]emulated.Element[S]
+}
+
+func (circuit *BatchVerifyMultiPointsTest[S, G1El, G2El, GTEl]) Define(api frontend.API) error {
+
+	verifier, err := NewVerifier[S, G1El, G2El, GTEl](api)
+	if err != nil {
+		return fmt.Errorf("get pairing: %w", err)
+	}
+
+	verifier.BatchVerifyMultiPoints(circuit.Digests[:], circuit.Proofs[:], circuit.Points[:], circuit.Vk)
+
+	return nil
+}
+
+func TestBatchVerifyMultiPoints(t *testing.T) {
+
+	assert := test.NewAssert(t)
+
+	// prepare test data
+	alpha, err := rand.Int(rand.Reader, ecc.BN254.ScalarField())
+	assert.NoError(err)
+	srs, err := kzg_bn254.NewSRS(kzgSize, alpha)
+	assert.NoError(err)
+
+	var polynomials [4][]fr_bn254.Element
+	var coms [4]kzg_bn254.Digest
+	for i := 0; i < 4; i++ {
+		polynomials[i] = make([]fr_bn254.Element, polynomialSize)
+		for j := 0; j < polynomialSize; j++ {
+			polynomials[i][j].SetRandom()
+		}
+		coms[i], err = kzg_bn254.Commit(polynomials[i], srs.Pk)
+		assert.NoError(err)
+	}
+
+	// random points at which the polynomials are evaluated
+	var points [4]fr_bn254.Element
+	for i := 0; i < 4; i++ {
+		points[i].SetRandom()
+	}
+
+	// build opening proofs
+	var openingProofs [4]kzg_bn254.OpeningProof
+	for i := 0; i < 4; i++ {
+		openingProofs[i], err = kzg_bn254.Open(polynomials[i], points[i], srs.Pk)
+		assert.NoError(err)
+	}
+
+	// check that the proofs are correct
+	err = kzg_bn254.BatchVerifyMultiPoints(coms[:], openingProofs[:], points[:], srs.Vk)
+	assert.NoError(err)
+
+	// prepare witness
+	wVk, err := ValueOfVerifyingKey[sw_bn254.G1Affine, sw_bn254.G2Affine](srs.Vk)
+	assert.NoError(err)
+	var wDigests [4]Commitment[sw_bn254.G1Affine]
+	var wPoints [4]emulated.Element[emulated.BN254Fr]
+	var wOpeningProofs [4]OpeningProof[emulated.BN254Fr, sw_bn254.G1Affine]
+	for i := 0; i < 4; i++ {
+		wPoints[i], err = ValueOfScalar[emulated.BN254Fr](points[i])
+		assert.NoError(err)
+		wDigests[i], err = ValueOfCommitment[sw_bn254.G1Affine](coms[i])
+		assert.NoError(err)
+		wOpeningProofs[i], err = ValueOfOpeningProof[emulated.BN254Fr, sw_bn254.G1Affine](openingProofs[i])
+		assert.NoError(err)
+	}
+
+	assignment := BatchVerifyMultiPointsTest[emulated.BN254Fr, sw_bn254.G1Affine, sw_bn254.G2Affine, sw_bn254.GTEl]{
+		Vk:      wVk,
+		Points:  wPoints,
+		Digests: wDigests,
+		Proofs:  wOpeningProofs,
+	}
+
+	var circuit BatchVerifyMultiPointsTest[emulated.BN254Fr, sw_bn254.G1Affine, sw_bn254.G2Affine, sw_bn254.GTEl]
+	assert.CheckCircuit(&circuit, test.WithValidAssignment(&assignment), test.WithCurves(ecc.BLS12_381), test.WithBackends(backend.PLONK))
+
+}