diff --git a/src/eip7594/fft.c b/src/eip7594/fft.c index 91ddd83d8..dfb3a85ff 100644 --- a/src/eip7594/fft.c +++ b/src/eip7594/fft.c @@ -194,7 +194,7 @@ static void g1_fft_fast( * * @remark Will do nothing if given a zero length array. * @remark The array lengths must be a power of two. - * @remark Use g1_ifft() for inverse transformation. + * @remark Use g1_ifft_unscaled() for inverse transformation. */ C_KZG_RET g1_fft(g1_t *out, const g1_t *in, size_t n, const KZGSettings *s) { /* Handle zero length input */ @@ -222,8 +222,9 @@ C_KZG_RET g1_fft(g1_t *out, const g1_t *in, size_t n, const KZGSettings *s) { * @remark Will do nothing if given a zero length array. * @remark The array lengths must be a power of two. * @remark Use g1_fft() for forward transformation. + * @remark The result is not scaled by 1/n. The caller must account for the missing factor. */ -C_KZG_RET g1_ifft(g1_t *out, const g1_t *in, size_t n, const KZGSettings *s) { +C_KZG_RET g1_ifft_unscaled(g1_t *out, const g1_t *in, size_t n, const KZGSettings *s) { /* Handle zero length input */ if (n == 0) return C_KZG_OK; @@ -235,13 +236,6 @@ C_KZG_RET g1_ifft(g1_t *out, const g1_t *in, size_t n, const KZGSettings *s) { size_t stride = FIELD_ELEMENTS_PER_EXT_BLOB / n; g1_fft_fast(out, in, 1, s->reverse_roots_of_unity, stride, n); - fr_t inv_n; - fr_from_uint64(&inv_n, n); - blst_fr_eucl_inverse(&inv_n, &inv_n); - for (size_t i = 0; i < n; i++) { - g1_mul(&out[i], &out[i], &inv_n); - } - return C_KZG_OK; } diff --git a/src/eip7594/fft.h b/src/eip7594/fft.h index 494cc235e..97107556f 100644 --- a/src/eip7594/fft.h +++ b/src/eip7594/fft.h @@ -33,7 +33,7 @@ C_KZG_RET fr_fft(fr_t *out, const fr_t *in, size_t n, const KZGSettings *s); C_KZG_RET fr_ifft(fr_t *out, const fr_t *in, size_t n, const KZGSettings *s); C_KZG_RET g1_fft(g1_t *out, const g1_t *in, size_t n, const KZGSettings *s); -C_KZG_RET g1_ifft(g1_t *out, const g1_t *in, size_t n, const KZGSettings *s); +C_KZG_RET g1_ifft_unscaled(g1_t *out, const g1_t *in, size_t n, const KZGSettings *s); C_KZG_RET coset_fft(fr_t *out, const fr_t *in, size_t n, const KZGSettings *s); C_KZG_RET coset_ifft(fr_t *out, const fr_t *in, size_t n, const KZGSettings *s); diff --git a/src/eip7594/fk20.c b/src/eip7594/fk20.c index fe90bc267..b892b8c41 100644 --- a/src/eip7594/fk20.c +++ b/src/eip7594/fk20.c @@ -138,6 +138,7 @@ static void circulant_coeffs_stride(fr_t *out, const fr_t *in, size_t offset) { C_KZG_RET compute_fk20_cell_proofs(g1_t *out, const fr_t *poly, const KZGSettings *s) { C_KZG_RET ret; size_t circulant_domain_size; + fr_t inv_domain_size; blst_scalar *scalars = NULL; fr_t **coeffs = NULL; @@ -185,15 +186,24 @@ C_KZG_RET compute_fk20_cell_proofs(g1_t *out, const fr_t *poly, const KZGSetting u[i] = G1_IDENTITY; } - /* Phase 1, step 4: Compute the w_i columns */ + /* + * Phase 1, step 4: Compute the w_i columns and prescale by 1/circulant_domain_size. + * + * The prescaling absorbs the 1/n factor from the IFFT (step 6) into cheap field + * multiplications, avoiding 128 expensive EC scalar multiplications that would otherwise be + * needed in the IFFT scaling step. + */ + fr_from_uint64(&inv_domain_size, circulant_domain_size); + blst_fr_eucl_inverse(&inv_domain_size, &inv_domain_size); for (size_t i = 0; i < FIELD_ELEMENTS_PER_CELL; i++) { /* Select the coefficients c_i of poly that form the i-th circulant matrix */ circulant_coeffs_stride(circulant_coeffs, poly, i); /* Apply FFT to get w_i */ ret = fr_fft(circulant_coeffs_fft, circulant_coeffs, circulant_domain_size, s); if (ret != C_KZG_OK) goto out; + /* Transpose and prescale by 1/n in one pass */ for (size_t j = 0; j < circulant_domain_size; j++) { - coeffs[j][i] = circulant_coeffs_fft[j]; + blst_fr_mul(&coeffs[j][i], &circulant_coeffs_fft[j], &inv_domain_size); } } @@ -236,13 +246,14 @@ C_KZG_RET compute_fk20_cell_proofs(g1_t *out, const fr_t *poly, const KZGSetting } /* - * Phase 1, step 6: Apply the inverse FFT to the u vector. + * Phase 1, step 6: Apply the inverse FFT to the u vector (without 1/n scaling, since we + * already prescaled the coefficients by 1/n in the field). * * The result is almost the final v vector: the second half of the vector should be set to the * identity elements (commitments to zero coefficients). The v polynomial actually has degree * r-1, which is guaranteed by setting the last r+1 elements of c_i vectors to be identities. */ - ret = g1_ifft(v, u, circulant_domain_size, s); + ret = g1_ifft_unscaled(v, u, circulant_domain_size, s); if (ret != C_KZG_OK) goto out; /*