Skip to content

Commit 6fe2483

Browse files
authored
fix: initialize in-circuit KZG SRS at compile time (#1738)
1 parent 6e69608 commit 6fe2483

File tree

1 file changed

+28
-23
lines changed

1 file changed

+28
-23
lines changed

std/evmprecompiles/10-kzg_point_evaluation.go

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -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

3128
func init() {
32-
fixedKzgSrsVk, fixedKzgSrsVk16 = fixedVerificationKey() // initialize the fixed verifying key
29+
fixedNativeKzgSrsVk = decodeNativeVerifyingKey()
3330
}
3431

3532
var (
@@ -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

8990
const (
@@ -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

Comments
 (0)