optimize the preprocessing by using the subgroup structure for the second IFFT-s in the convolution

This commit is contained in:
Balazs Komuves 2026-06-19 01:19:51 +02:00
parent ed363001e4
commit 1f35ef18d2
No known key found for this signature in database
GPG Key ID: F63B7AEF18435562
9 changed files with 228 additions and 31 deletions

View File

@ -154,6 +154,67 @@ proc groupConvolveWithWVecBar*( D: Domain, gs: seq[G1] ): seq[G1] =
inplaceScalarMulByFFTofWVecBar( gsHat )
return inverseGroupFFT( gsHat , D)
#---------------------------------------
# only computes the result on a subgroup
proc fieldConvolveWithWVecBarOnSubgroup*( sg: Subgroup, xs: seq[F] ): seq[F] =
let N = xs.len
assert( N == sg.bigDomain.domainSize )
let D = sg.bigDomain
let K = sg.smallDomain.domainSize
let ell = N div K
let fN = intToFr( N )
let invN = invFr( fN )
let fell : F = intToFr(ell)
let invN_per_fell : F = invN / fell
let xsHat = forwardNTT( xs , D )
var ysHat : seq[F] = newSeq[F]( K )
let c0 = divBy2Fr(oneFr - fN) * invN_per_fell
for k in 0..<K:
var sum: F = zeroFr
for i in 0..<ell:
let kk = k + K*i
let kk_rev = (if kk==0: 0 else: N-kk)
let u = c0 + intToFr(kk_rev) * invN_per_fell
sum += u * xsHat[kk]
ysHat[k] = sum
return inverseNTT( ysHat , sg.smallDomain )
#---------------------------------------
proc groupConvolveWithWVecOnSubgroup*( sg: Subgroup, pts: seq[G1] ): seq[G1] =
let N = pts.len
assert( N == sg.bigDomain.domainSize )
let D = sg.bigDomain
let K = sg.smallDomain.domainSize
let ell = N div K
let fN = intToFr( N )
let invN = invFr( fN )
let fell : F = intToFr(ell)
let invN_per_fell : F = invN / fell
var ptsHat = forwardGroupFFT( pts , D )
var small : seq[G1] = newSeq[G1]( K )
var miniPts : seq[G1] = newSeq[G1]( ell )
var miniCfs : seq[F] = newSeq[F] ( ell )
let c0 = divBy2Fr(oneFr - fN) * invN_per_fell
for k in 0..<K:
for i in 0..<ell:
let kk = k + K*i
miniCfs[i] = c0 + intToFr(kk) * invN_per_fell
miniPts[i] = ptsHat[kk]
small[k] = msmConstantineG1( miniCfs , miniPts )
return inverseGroupFFT( small , sg.smallDomain )
#-------------------------------------------------------------------------------
# computes the vectors A*z, B*z (but skips C*z)

View File

@ -9,8 +9,8 @@ import std/options
import taskpools
import constantine/math/arithmetic
import constantine/named/properties_fields
#import constantine/math/arithmetic
#import constantine/named/properties_fields
import groth16/bn128
import groth16/bn128/arrays
@ -20,9 +20,9 @@ import groth16/zkey_types
import groth16/files/witness
import groth16/math/domain
import groth16/math/ntt
import groth16/math/poly
import groth16/math/convolution
#import groth16/math/poly
#import groth16/math/ntt
import groth16/prover/types
import groth16/prover/shared
@ -30,7 +30,6 @@ import groth16/partial/finish
import groth16/dynamic/shared
import groth16/dynamic/v1/types
import groth16/dynamic/v1/setup
#-------------------------------------------------------------------------------

View File

@ -10,12 +10,10 @@
# this is V1, the version closest to the Dynark paper
#
import std/options
import taskpools
import constantine/math/arithmetic
import constantine/named/properties_fields
#import constantine/named/properties_fields
import groth16/bn128
import groth16/bn128/arrays
@ -24,7 +22,6 @@ import groth16/misc
import groth16/zkey_types
import groth16/math/domain
import groth16/math/convolution
import groth16/math/poly
import groth16/partial/precalc
@ -32,7 +29,6 @@ import groth16/partial/precalc
import groth16/dynamic/shared
import groth16/dynamic/v1/types
import groth16/dynamic/v1/setup
import groth16/dynamic/v1/finish
#-------------------------------------------------------------------------------

View File

@ -6,8 +6,8 @@
import taskpools
import groth16/bn128
import groth16/bn128/arrays
import groth16/misc
#import groth16/bn128/arrays
import groth16/math/domain
import groth16/math/group_fft

View File

@ -4,7 +4,7 @@
#import constantine/named/properties_fields
import groth16/bn128
import groth16/math/domain
#import groth16/math/domain
#import groth16/math/ntt
#import groth16/math/poly

View File

@ -26,22 +26,70 @@ import groth16/partial/precalc
import groth16/dynamic/shared
import groth16/dynamic/permute
import groth16/dynamic/v2/types as v2types
# import groth16/dynamic/v2/types as v2types
import groth16/dynamic/v2/preprocess
import groth16/dynamic/v3/types
import groth16/dynamic/v3/setup
#-------------------------------------------------------------------------------
func convert_preprocess_V2_to_V3( v2: DynaPreprocessV2 ): DynaPreprocessV3 =
return DynaPreprocessV3( unified: v2.unified )
func projectionElementsV3*(setup: DynaSetupV3, As: seq[F] ): seq[G1] =
let sg = setup.imageSubgroup
let D = sg.bigDomain
let N = D.domainSize
let K = sg.smallDomain.domainSize
let ell = N div K
func convert_setup_V3_to_V2( v3: DynaSetupV3 ): DynaSetupV2 =
let fakePhi: seq[G1] = newSeq[G1]()
return DynaSetupV2( weightVec : v3.weightVec ,
pointsDeltaLZ : v3.pointsDeltaLZ ,
wConvDeltaLZ : v3.wConvDeltaLZ ,
diagPhiPoints : fakePhi )
var Us: seq[G1] = newSeq[G1]( K )
let fldN : F = intToFr( N )
let sumW : F = sumOfWVec( N )
let WBarStarA : seq[F] = fieldConvolveWithWVecBarOnSubgroup( sg , As )
let ALstarW : seq[G1] = groupConvolveWithWVecOnSubgroup( sg , pointwiseScaleG1( As , setup.pointsDeltaLZ ) )
# 2*d scalar multiplications + the group convolution above
for k in 0..<K:
let ellk = ell*k
let cf : F = WBarStarA[k] - As[ellk] * sumW
Us[k] = ALstarW[k] - (As[ellk] ** setup.wConvDeltaLZ[ellk]) + (cf ** setup.pointsDeltaLZ[ellk])
return Us
#-------------------------------------------------------------------------------
proc dynaPreprocessV3*(zkey: ZKey, setup: DynaSetupV3, partialAB: PartialAB, partial_mask: seq[bool], zdeltaMask: seq[bool] ): DynaPreprocessV3 =
let sg = setup.imageSubgroup
let D = sg.bigDomain
let K = sg.smallDomain.domainSize
let N = D.domainSize
let ell = N div K
var projA_mini: seq[G1]
var projB_mini: seq[G1]
withMeasureTime(true," + the \"projection\" points U_k, V_k"):
projA_mini = projectionElementsV3( setup , partialAB.valuesAz )
projB_mini = projectionElementsV3( setup , partialAB.valuesBz )
let m = countTrues(zdeltaMask)
var Xs: seq[G1] = newSeq[G1]( m )
let points_C1 = compactifyPointsC1( zkey, partial_mask )
withMeasureTime(true," + the \"unified\" points X_j"):
let matABC = zkeyToSparseMatrices(zkey)
let colsA = selectTrues( zdeltaMask , matABC.A.columns )
let colsB = selectTrues( zdeltaMask , matABC.B.columns )
for j in 0..<m:
var h : G1 = points_C1[j]
for (k,a) in colsA[j].pairs: h += (a ** projB_mini[k div ell])
for (k,b) in colsB[j].pairs: h += (b ** projA_mini[k div ell])
Xs[j] = h
return DynaPreprocessV3( unified: Xs )
#-------------------------------------------------------------------------------
@ -84,15 +132,14 @@ proc dynaPreProofV3*(zkey: ZKey, setup: DynaSetupV3, partialWitness: PartialWitn
partialProof.partial_pi_c += nonlin
var preprocessV3 : DynaPreprocessV3
var preprocess : DynaPreprocessV3
withMeasureTime(printTimings,"precomputing the \"projection\" and then the unified points"):
let setupV2 = convert_setup_V3_to_V2( setup )
preprocessV3 = convert_preprocess_V2_to_V3( dynaPreprocessV2( zkey, setupV2, partialAB, partialMask, zdeltaMask ))
preprocess = dynaPreprocessV3( zkey, setup, partialAB, partialMask, zdeltaMask )
return DynaPreProofV3( partialProof : partialProof ,
deltaImages : deltaImages ,
dynaSetup : setup ,
dynaPreprocess : preprocessV3 )
dynaPreprocess : preprocess )
#-------------------------------------------------------------------------------

View File

@ -32,12 +32,86 @@ func groupConvolution*(xs: seq[F], gs: seq[G1]): seq[G1] =
assert( N == gs.len )
let D = createDomain( N )
let us = forwardNTT(xs, D)
let hs = forwardGroupFFT(gs, D)
let us = forwardNTT(xs, D)
let hs = forwardGroupFFT(gs, D)
let rs = pointwiseScaleG1(us, hs)
return inverseGroupFFT(rs, D)
#-------------------------------------------------------------------------------
func fieldConvolutionOnSubgroup*(sg: Subgroup, xs: seq[F], ys: seq[F]): seq[F] =
let N = xs.len
assert( N == ys.len )
assert( N == sg.bigDomain.domainSize )
let D = sg.bigDomain
let K = sg.smallDomain.domainSize
let ell = N div K
let fell : F = intToFr(ell)
let invfell : F = invFr(fell)
let us = forwardNTT(xs, D)
let vs = forwardNTT(ys, D)
var ws: seq[F] = newSeq[F]( K )
for k in 0..<K:
var sum: F = zeroFr
for i in 0..<ell:
sum += us[k + i*K] * vs[k + i*K]
ws[k] = sum * invfell
return inverseNTT(ws, sg.smallDomain)
#---------------------------------------
# only computes the result in a subgroup
func groupConvolutionOnSubgroup*(sg: Subgroup, xs: seq[F], gs: seq[G1]): seq[G1] =
let N = xs.len
assert( N == gs.len )
assert( N == sg.bigDomain.domainSize )
let D = sg.bigDomain
let K = sg.smallDomain.domainSize
let ell = N div K
let fell : F = intToFr(ell)
let invfell : F = invFr(fell)
let us = forwardNTT(xs, D)
let hs = forwardGroupFFT(gs, D)
var rs : seq[G1] = newSeq[G1]( K )
var miniPts : seq[G1] = newSeq[G1]( ell )
var miniCfs : seq[F] = newSeq[F] ( ell )
for k in 0..<K:
for i in 0..<ell:
miniCfs[i] = us[k + K*i] * invfell
miniPts[i] = hs[k + K*i]
rs[k] = msmConstantineG1( miniCfs , miniPts )
return inverseGroupFFT(rs, sg.smallDomain)
#---------------------------------------
proc testFieldConvolutionOnSubgroup*(N: int, K: int): bool =
let sg = createSubgroup( createDomain(N) , K )
let xs = randFrSeq(N)
let ys = randFrSeq(N)
let target = selectOnSubgroup( sg , fieldConvolution(xs, ys) )
let smart = fieldConvolutionOnSubgroup( sg, xs, ys )
return isEqualFrSeq(target , smart)
proc testGroupConvolutionOnSubgroup*(N: int, K: int): bool =
let sg = createSubgroup( createDomain(N) , K )
let xs = randFrSeq(N)
let ps = randG1Seq(N)
let target = selectOnSubgroup( sg , groupConvolution(xs, ps) )
let smart = groupConvolutionOnSubgroup( sg, xs, ps )
return isEqualG1Seq(target , smart)
#-------------------------------------------------------------------------------
# naive reference implementations for testing

View File

@ -17,9 +17,9 @@ import groth16/math/convolution
import groth16/dynamic/types
import groth16/dynamic/shared
import groth16/dynamic/setup
import groth16/dynamic/preprocess
import groth16/dynamic/finish
import groth16/dynamic/v1/preprocess
#import groth16/dynamic/v1/setup
#import groth16/dynamic/v1/finish
#-------------------------------------------------------------------------------
@ -30,7 +30,9 @@ func ones(N : int): seq[F] = constSeq(N, oneFr)
suite "dynamic proof (Dynark) tests":
let N : int = 128
let D = createDomain( N )
let K : int = 32
let D = createDomain( N )
let sg = createSubgroup( D , K )
let tau : F = randFr()
let delta : F = randFr()
@ -75,6 +77,12 @@ suite "dynamic proof (Dynark) tests":
let rhs = fieldConvolution( xs , calculateWVecBar(D) )
check isEqualFrSeq( lhs , rhs )
test "field convolution with WvecBar on a subgroup":
let xs = randFrSeq( N )
let lhs = fieldConvolveWithWVecBarOnSubgroup( sg , xs )
let rhs = selectOnSubgroup( sg , fieldConvolution( xs , calculateWVecBar(D) ) )
check isEqualFrSeq( lhs , rhs )
test "group convolution with Wvec":
let gs = randG1Seq( N )
let lhs = groupConvolveWithWVec( D , gs )
@ -87,6 +95,12 @@ suite "dynamic proof (Dynark) tests":
let rhs = groupConvolution( calculateWVecBar(D) , gs )
check isEqualG1Seq( lhs , rhs )
test "group convolution with Wvec on a subgroup":
let gs = randG1Seq( N )
let lhs = groupConvolveWithWVecOnSubgroup( sg , gs )
let rhs = selectOnSubgroup( sg , groupConvolution( calculateWVec(D) , gs ) )
check isEqualG1Seq( lhs , rhs )
test "expansion of the product of Lagrange polynomials":
var ok = true
for i in 0..<N:

View File

@ -84,4 +84,10 @@ suite "convolution checks":
test "FFT group convolutions vs. slow reference":
check isEqualG1Seq( groupConvolution(xs,gs) , naiveGroupConvolution(xs,gs) )
test "FFT field convolution evaluated on a subgroup":
check testFieldConvolutionOnSubgroup( 32 , 8 )
test "FFT group convolution evaluated on a subgroup":
check testGroupConvolutionOnSubgroup( 128 , 16 )
#-------------------------------------------------------------------------------