@@ -20,16 +20,13 @@ import (
2020 "github.com/consensys/gnark/std/selector"
2121)
2222
23- // fixedKzgSrsVk is the verifying key for the KZG precompile. As it is fixed,
24- // then we can embed it into the circuit instead of passing as a witness
25- // argument.
26- var fixedKzgSrsVk * kzg.VerifyingKey [sw_bls12381.G1Affine , sw_bls12381.G2Affine ]
27-
28- // fixedKzgSrsVk16 is the verifying key for the KZG precompile using 16-bit limbs.
29- var fixedKzgSrsVk16 * kzg.VerifyingKey [sw_bls12381.G1Affine , sw_bls12381.G2Affine ]
23+ // fixedNativeKzgSrsVk is the KZG verifying key in native gnark-crypto form.
24+ // It is decoded once at startup and never modified thereafter, making it safe
25+ // to read from concurrent goroutines.
26+ var fixedNativeKzgSrsVk kzg_bls12381.VerifyingKey
3027
3128func init () {
32- fixedKzgSrsVk , fixedKzgSrsVk16 = fixedVerificationKey () // initialize the fixed verifying key
29+ fixedNativeKzgSrsVk = decodeNativeVerifyingKey ()
3330}
3431
3532var (
@@ -56,34 +53,38 @@ var (
5653 }
5754)
5855
59- func fixedVerificationKey () (* kzg.VerifyingKey [sw_bls12381.G1Affine , sw_bls12381.G2Affine ], * kzg.VerifyingKey [sw_bls12381.G1Affine , sw_bls12381.G2Affine ]) {
56+ // decodeNativeVerifyingKey decodes the SRS bytes into a native gnark-crypto
57+ // verifying key. The result contains no gnark circuit types and is safe to
58+ // store as a package-level variable.
59+ func decodeNativeVerifyingKey () kzg_bls12381.VerifyingKey {
6060 var vk kzg_bls12381.VerifyingKey
6161 dec := bls12381 .NewDecoder (bytes .NewBuffer (srs ), bls12381 .NoSubgroupChecks ())
62- err := dec .Decode (& vk .G1 )
63- if err != nil {
62+ if err := dec .Decode (& vk .G1 ); err != nil {
6463 panic (fmt .Sprintf ("failed to set G1 element: %v" , err ))
6564 }
66- err = dec .Decode (& vk .G2 [0 ])
67- if err != nil {
65+ if err := dec .Decode (& vk .G2 [0 ]); err != nil {
6866 panic (fmt .Sprintf ("failed to set G2[0] element: %v" , err ))
6967 }
70- err = dec .Decode (& vk .G2 [1 ])
71- if err != nil {
68+ if err := dec .Decode (& vk .G2 [1 ]); err != nil {
7269 panic (fmt .Sprintf ("failed to set G2[1] element: %v" , err ))
7370 }
7471 vk .Lines [0 ] = bls12381 .PrecomputeLines (vk .G2 [0 ])
7572 vk .Lines [1 ] = bls12381 .PrecomputeLines (vk .G2 [1 ])
76- vkw , err := kzg.ValueOfVerifyingKeyFixed [sw_bls12381.G1Affine , sw_bls12381.G2Affine ](vk )
77- if err != nil {
78- panic (fmt .Sprintf ("failed to convert verifying key to fixed: %v" , err ))
79- }
80- // as the constant values are decomposed automatically at circuit parsing time and this is
81- // a global variable, we need to create a separate instance for 16-bit limbs
82- vkw16 , err := kzg.ValueOfVerifyingKeyFixed [sw_bls12381.G1Affine , sw_bls12381.G2Affine ](vk )
73+ return vk
74+ }
75+
76+ // newVerifyingKey returns a fresh gnark circuit-level verifying key built
77+ // from the cached crypto key. It must be called once per circuit compilation:
78+ // kzg.ValueOfVerifyingKeyFixed produces emulated field elements whose metadata
79+ // (modReduced, bitsDecomposition, …) is mutated in-place during circuit
80+ // parsing, so sharing a single instance across concurrent compilations causes
81+ // a data race.
82+ func newVerifyingKey () * kzg.VerifyingKey [sw_bls12381.G1Affine , sw_bls12381.G2Affine ] {
83+ vk , err := kzg.ValueOfVerifyingKeyFixed [sw_bls12381.G1Affine , sw_bls12381.G2Affine ](fixedNativeKzgSrsVk )
8384 if err != nil {
8485 panic (fmt .Sprintf ("failed to convert verifying key to fixed: %v" , err ))
8586 }
86- return & vkw , & vkw16
87+ return & vk
8788}
8889
8990const (
@@ -163,6 +164,7 @@ func KzgPointEvaluation(
163164 // check expected modulus against 128-bit format
164165 api .AssertIsEqual (expectedBlsModulus [0 ], evmBlsModulusHi )
165166 api .AssertIsEqual (expectedBlsModulus [1 ], evmBlsModulusLo )
167+ fixedKzgSrsVk := newVerifyingKey ()
166168 return kzgPointEvaluation (api , fixedKzgSrsVk , versionedHash [:], evaluationPoint , claimedValue , commitmentCompressed [:], proofCompressed [:], expectedBlobSize [:], 16 )
167169}
168170
@@ -202,6 +204,7 @@ func KzgPointEvaluation16(
202204 for i := range evmBlsModulus16 {
203205 api .AssertIsEqual (expectedBlsModulus [i ], evmBlsModulus16 [i ])
204206 }
207+ fixedKzgSrsVk16 := newVerifyingKey ()
205208 return kzgPointEvaluation (api , fixedKzgSrsVk16 , versionedHash [:], evaluationPoint , claimedValue , commitmentCompressed [:], proofCompressed [:], expectedBlobSize [:], 2 )
206209}
207210
@@ -346,6 +349,7 @@ func KzgPointEvaluationFailure(
346349 if smallfields .IsSmallField (api .Compiler ().Field ()) {
347350 return fmt .Errorf ("KzgPointEvaluationFailure method cannot be called over small field compiler" )
348351 }
352+ fixedKzgSrsVk := newVerifyingKey ()
349353 return kzgPointEvaluationFailure (api , fixedKzgSrsVk , versionedHash [:], evaluationPoint , claimedValue , commitmentCompressed [:], proofCompressed [:], expectedBlobSize [:], expectedBlsModulus [:], 16 )
350354}
351355
@@ -387,6 +391,7 @@ func KzgPointEvaluationFailure16(
387391 if ! smallfields .IsSmallField (api .Compiler ().Field ()) {
388392 return fmt .Errorf ("KzgPointEvaluationFailure16 method cannot be called over large field compiler" )
389393 }
394+ fixedKzgSrsVk16 := newVerifyingKey ()
390395 return kzgPointEvaluationFailure (api , fixedKzgSrsVk16 , versionedHash [:], evaluationPoint , claimedValue , commitmentCompressed [:], proofCompressed [:], expectedBlobSize [:], expectedBlsModulus [:], 2 )
391396}
392397
0 commit comments