diff --git a/oss-fuzz.sh b/oss-fuzz.sh index 52d5b4f2a6..ed62fdc221 100644 --- a/oss-fuzz.sh +++ b/oss-fuzz.sh @@ -212,6 +212,10 @@ compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \ FuzzCrossG2Add fuzz_cross_g2_add \ $repo/tests/fuzzers/bls12381/bls12381_test.go +compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \ + FuzzCrossG2MultiExp fuzz_cross_g2_multiexp \ + $repo/tests/fuzzers/bls12381/bls12381_test.go + compile_fuzzer github.com/ethereum/go-ethereum/tests/fuzzers/bls12381 \ FuzzCrossPairing fuzz_cross_pairing\ $repo/tests/fuzzers/bls12381/bls12381_test.go diff --git a/tests/fuzzers/bls12381/bls12381_fuzz.go b/tests/fuzzers/bls12381/bls12381_fuzz.go index dacbcd474e..a3e0e9f72b 100644 --- a/tests/fuzzers/bls12381/bls12381_fuzz.go +++ b/tests/fuzzers/bls12381/bls12381_fuzz.go @@ -275,6 +275,57 @@ func fuzzCrossG2Mul(data []byte) int { return 1 } +func fuzzCrossG2MultiExp(data []byte) int { + var ( + input = bytes.NewReader(data) + gnarkScalars []fr.Element + gnarkPoints []gnark.G2Affine + blstScalars []*blst.Scalar + blstPoints []*blst.P2Affine + ) + // n random scalars (max 17) + for i := 0; i < 17; i++ { + // note that geth/crypto/bls12381 works only with scalars <= 32bytes + s, err := randomScalar(input, fr.Modulus()) + if err != nil { + break + } + // get a random G1 point as basis + cp1, bl1, err := getG2Points(input) + if err != nil { + break + } + + gnarkScalar := new(fr.Element).SetBigInt(s) + gnarkScalars = append(gnarkScalars, *gnarkScalar) + gnarkPoints = append(gnarkPoints, *cp1) + + blstScalar := new(blst.Scalar).FromBEndian(common.LeftPadBytes(s.Bytes(), 32)) + blstScalars = append(blstScalars, blstScalar) + blstPoints = append(blstPoints, bl1) + } + + if len(gnarkScalars) == 0 || len(gnarkScalars) != len(gnarkPoints) { + return 0 + } + + // gnark multi exp + cp := new(gnark.G2Affine) + cp.MultiExp(gnarkPoints, gnarkScalars, ecc.MultiExpConfig{}) + + expectedGnark := multiExpG2Gnark(gnarkPoints, gnarkScalars) + if !bytes.Equal(cp.Marshal(), expectedGnark.Marshal()) { + panic("g1 multi exponentiation mismatch") + } + + // blst multi exp + expectedBlst := blst.P2AffinesMult(blstPoints, blstScalars, 256).ToAffine() + if !bytes.Equal(cp.Marshal(), expectedBlst.Serialize()) { + panic("g1 multi exponentiation mismatch, gnark/blst") + } + return 1 +} + func getG1Points(input io.Reader) (*gnark.G1Affine, *blst.P1Affine, error) { // sample a random scalar s, err := randomScalar(input, fp.Modulus()) @@ -344,3 +395,16 @@ func multiExpG1Gnark(gs []gnark.G1Affine, scalars []fr.Element) gnark.G1Affine { } return res } + +// multiExpG1Gnark is a naive implementation of G1 multi-exponentiation +func multiExpG2Gnark(gs []gnark.G2Affine, scalars []fr.Element) gnark.G2Affine { + res := gnark.G2Affine{} + for i := 0; i < len(gs); i++ { + tmp := new(gnark.G2Affine) + sb := scalars[i].Bytes() + scalarBytes := new(big.Int).SetBytes(sb[:]) + tmp.ScalarMultiplication(&gs[i], scalarBytes) + res.Add(&res, tmp) + } + return res +} diff --git a/tests/fuzzers/bls12381/bls12381_test.go b/tests/fuzzers/bls12381/bls12381_test.go index 3e42de42b4..d4e5e20e04 100644 --- a/tests/fuzzers/bls12381/bls12381_test.go +++ b/tests/fuzzers/bls12381/bls12381_test.go @@ -27,6 +27,12 @@ func FuzzCrossPairing(f *testing.F) { }) } +func FuzzCrossG2MultiExp(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fuzzCrossG2MultiExp(data) + }) +} + func FuzzCrossG1Add(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { fuzzCrossG1Add(data)