From e37da53af7092a9c6c91b4dba6b9c0dc64466dcf Mon Sep 17 00:00:00 2001 From: Christopher Patton Date: Tue, 13 Aug 2024 16:16:59 -0700 Subject: [PATCH] Rename FFT to NTT "Number theoretic transform" is the term preferred by most cryptographers. Also, remove an out-of-date comment related to NTT and padding. --- draft-irtf-cfrg-vdaf.md | 31 ++++++++++++++++++------------- poc/tests/test_field.py | 12 ++++++------ poc/tests/test_flp_bbcggi19.py | 10 +++++----- poc/tests/test_vdaf_prio3.py | 4 ++-- poc/vdaf_poc/field.py | 14 ++++++++++---- poc/vdaf_poc/flp_bbcggi19.py | 12 +++++------- poc/vdaf_poc/test_utils.py | 4 ++-- poc/vdaf_poc/vdaf_prio3.py | 4 ++-- 8 files changed, 50 insertions(+), 41 deletions(-) diff --git a/draft-irtf-cfrg-vdaf.md b/draft-irtf-cfrg-vdaf.md index 96606807..cd1cb513 100644 --- a/draft-irtf-cfrg-vdaf.md +++ b/draft-irtf-cfrg-vdaf.md @@ -233,6 +233,15 @@ informative: date: 2020 target: https://web.archive.org/web/20221025174046/https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/collection/origin.html + SML24: + title: "A Complete Beginner Guide to the Number Theoretic Transform (NTT)" + target: https://eprint.iacr.org/2024/585 + author: + - ins: A. Satriawan + - ins: R. Mareta + - ins: H. Lee + date: 2024 + --- abstract This document describes Verifiable Distributed Aggregation Functions (VDAFs), a @@ -1942,22 +1951,22 @@ def vec_neg(vec: list[F]) -> list[F]: ~~~ {: #field-helper-functions title="Common functions for finite fields."} -### FFT-Friendly Fields {#field-fft-friendly} +### NTT-Friendly Fields {#field-ntt-friendly} Some VDAFs require fields that are suitable for efficient computation of the -discrete Fourier transform, as this allows for fast polynomial interpolation. -(One example is Prio3 ({{prio3}}) when instantiated with the FLP of -{{flp-bbcggi19-construction}}.) Specifically, a field is said to be -"FFT-friendly" if, in addition to satisfying the interface described in +number theoretic transform (NTT) {{SML24}}, as this allows for fast polynomial +interpolation. (One example is Prio3 ({{prio3}}) when instantiated with the FLP +of {{flp-bbcggi19-construction}}.) Specifically, a field is said to be +"NTT-friendly" if, in addition to satisfying the interface described in {{field}}, it implements the following method: * `Field.gen() -> Field` returns the generator of a large subgroup of the - multiplicative group. To be FFT-friendly, the order of this subgroup MUST be a + multiplicative group. To be NTT-friendly, the order of this subgroup MUST be a power of 2. In addition, the size of the subgroup dictates how large interpolated polynomials can be. It is RECOMMENDED that a generator is chosen with order at least `2^20`. -FFT-friendly fields also define the following parameter: +NTT-friendly fields also define the following parameter: * `GEN_ORDER: int` is the order of a multiplicative subgroup generated by `Field.gen()`. @@ -2445,7 +2454,7 @@ of proofs. ## Construction {#prio3-construction} This section specifies `Prio3`, an implementation of the `Vdaf` interface -({{vdaf}}). It has three generic parameters: an `FftField ({{fft-field}}), an +({{vdaf}}). It has three generic parameters: an `NttField ({{ntt-field}}), an `Flp` ({{flp}}) and a `Xof` ({{xof}}). It also has an associated constant, `PROOFS`, with a value within the range of `[1, 256)`, denoting the number of FLPs generated by the Client ({{multiproofs}}). @@ -3294,7 +3303,7 @@ fixed points `alpha[0], ..., alpha[M-1]`, other than to require that the points are distinct. In this document, the fixed points are chosen so that the gadget polynomial can be constructed efficiently using the Cooley-Tukey FFT ("Fast Fourier Transform") algorithm. Note that this requires the field to be -"FFT-friendly" as defined in {{field-fft-friendly}}. +"NTT-friendly" as defined in {{field-ntt-friendly}}. Finally, the validity circuit in our FLP may have any number of outputs (at least one). The input is said to be valid if each of the outputs is zero. To @@ -3446,10 +3455,6 @@ follows: * Let `padded_w = w + field.zeros(P_i - len(w))`. - > NOTE We pad `w` to the nearest power of 2 so that we can use FFT for - > interpolating the wire polynomials. Perhaps there is some clever math for - > picking `wire_inp` in a way that avoids having to pad. - * Let `poly_wire_i[j-1]` be the lowest degree polynomial for which `poly_wire_i[j-1](alpha_i^k) == padded_w[k]` for all `k` in `[P_i]`. diff --git a/poc/tests/test_field.py b/poc/tests/test_field.py index bd654e77..bc75e7f9 100644 --- a/poc/tests/test_field.py +++ b/poc/tests/test_field.py @@ -1,7 +1,7 @@ import unittest -from vdaf_poc.field import (FftField, Field, Field2, Field64, Field96, - Field128, Field255, poly_eval, poly_interp) +from vdaf_poc.field import (Field, Field2, Field64, Field96, Field128, + Field255, NttField, poly_eval, poly_interp) class TestFields(unittest.TestCase): @@ -41,20 +41,20 @@ def run_field_test(self, cls: type[Field]) -> None: self.assertTrue(cls.decode_from_bit_vector( encoded).as_unsigned() == val) - def run_fft_field_test(self, cls: type[FftField]) -> None: + def run_ntt_field_test(self, cls: type[NttField]) -> None: self.run_field_test(cls) # Test generator. self.assertTrue(cls.gen()**cls.GEN_ORDER == cls(1)) def test_field64(self) -> None: - self.run_fft_field_test(Field64) + self.run_ntt_field_test(Field64) def test_field96(self) -> None: - self.run_fft_field_test(Field96) + self.run_ntt_field_test(Field96) def test_field128(self) -> None: - self.run_fft_field_test(Field128) + self.run_ntt_field_test(Field128) def test_field255(self) -> None: self.run_field_test(Field255) diff --git a/poc/tests/test_flp_bbcggi19.py b/poc/tests/test_flp_bbcggi19.py index fa7aa5f0..230ad431 100644 --- a/poc/tests/test_flp_bbcggi19.py +++ b/poc/tests/test_flp_bbcggi19.py @@ -1,6 +1,6 @@ from typing import TypeVar -from vdaf_poc.field import FftField, Field64, Field96, Field128 +from vdaf_poc.field import Field64, Field96, Field128, NttField from vdaf_poc.flp_bbcggi19 import (Count, FlpBBCGGI19, Histogram, Mul, MultihotCountVec, PolyEval, Range2, Sum, SumOfRangeCheckedInputs, SumVec, Valid) @@ -8,7 +8,7 @@ Measurement = TypeVar("Measurement") AggResult = TypeVar("AggResult") -F = TypeVar("F", bound=FftField) +F = TypeVar("F", bound=NttField) class MultiGadget(Valid[int, int, Field64]): @@ -140,14 +140,14 @@ def test_small(self) -> None: class TestSumVec(TestFlpBBCGGI19): - def run_encode_truncate_decode_with_fft_fields_test( + def run_encode_truncate_decode_with_ntt_fields_test( self, measurements: list[list[int]], length: int, bits: int, chunk_length: int) -> None: for field in [Field64, Field96, Field128]: - sumvec = SumVec[FftField](field, length, bits, chunk_length) + sumvec = SumVec[NttField](field, length, bits, chunk_length) self.assertEqual(sumvec.field, field) self.assertTrue(isinstance(sumvec, SumVec)) self.run_encode_truncate_decode_test( @@ -155,7 +155,7 @@ def run_encode_truncate_decode_with_fft_fields_test( def test(self) -> None: # SumVec with length 2, bits 4, chunk len 1. - self.run_encode_truncate_decode_with_fft_fields_test( + self.run_encode_truncate_decode_with_ntt_fields_test( [[1, 2], [3, 4], [5, 6], [7, 8]], 2, 4, diff --git a/poc/tests/test_vdaf_prio3.py b/poc/tests/test_vdaf_prio3.py index 564ea981..ff5d2894 100644 --- a/poc/tests/test_vdaf_prio3.py +++ b/poc/tests/test_vdaf_prio3.py @@ -2,7 +2,7 @@ from tests.test_flp import FlpTest from tests.test_flp_bbcggi19 import TestAverage -from vdaf_poc.field import FftField, Field64, Field128 +from vdaf_poc.field import Field64, Field128, NttField from vdaf_poc.flp_bbcggi19 import FlpBBCGGI19 from vdaf_poc.test_utils import TestVdaf from vdaf_poc.vdaf_prio3 import (Prio3, Prio3Count, Prio3Histogram, @@ -10,7 +10,7 @@ Prio3SumVecWithMultiproof) from vdaf_poc.xof import XofTurboShake128 -F = TypeVar("F", bound=FftField) +F = TypeVar("F", bound=NttField) class Prio3Average(Prio3): diff --git a/poc/vdaf_poc/field.py b/poc/vdaf_poc/field.py index 380b037d..616630fa 100644 --- a/poc/vdaf_poc/field.py +++ b/poc/vdaf_poc/field.py @@ -152,7 +152,13 @@ def as_unsigned(self) -> int: return int(self.gf(self.val)) -class FftField(Field): +class NttField(Field): + """ + A field that is suitable for use with the NTT ("number theoretic + transform") algorithm for efficient polynomial interpolation. Such a field + defines a large multiplicative subgroup whose order is a power of 2. + """ + # Order of the multiplicative group generated by `Field.gen()`. GEN_ORDER: int @@ -188,7 +194,7 @@ def conditional_select(self, inp: bytes) -> bytes: return bytes(map(lambda x: m & x, inp)) -class Field64(FftField): +class Field64(NttField): """The finite field GF(2^32 * 4294967295 + 1).""" MODULUS = 2**32 * 4294967295 + 1 @@ -203,7 +209,7 @@ def gen(cls) -> Self: return cls(7)**4294967295 -class Field96(FftField): +class Field96(NttField): """The finite field GF(2^64 * 4294966555 + 1).""" MODULUS = 2**64 * 4294966555 + 1 @@ -218,7 +224,7 @@ def gen(cls) -> Self: return cls(3)**4294966555 -class Field128(FftField): +class Field128(NttField): """The finite field GF(2^66 * 4611686018427387897 + 1).""" MODULUS = 2**66 * 4611686018427387897 + 1 diff --git a/poc/vdaf_poc/flp_bbcggi19.py b/poc/vdaf_poc/flp_bbcggi19.py index 0d77e238..e9859b91 100644 --- a/poc/vdaf_poc/flp_bbcggi19.py +++ b/poc/vdaf_poc/flp_bbcggi19.py @@ -5,13 +5,13 @@ from typing import Any, Generic, Optional, TypeVar, cast from vdaf_poc.common import front, next_power_of_2 -from vdaf_poc.field import (FftField, poly_eval, poly_interp, poly_mul, +from vdaf_poc.field import (NttField, poly_eval, poly_interp, poly_mul, poly_strip) from vdaf_poc.flp import Flp Measurement = TypeVar("Measurement") AggResult = TypeVar("AggResult") -F = TypeVar("F", bound=FftField) +F = TypeVar("F", bound=NttField) class Gadget(Generic[F], metaclass=ABCMeta): @@ -52,10 +52,10 @@ class Valid(Generic[Measurement, AggResult, F], metaclass=ABCMeta): Generic type parameters: Measurement -- the measurement type AggResult -- the aggregate result type - Field -- An FFT-friendly field + Field -- An NTT-friendly field Attributes: - field -- Class object for the FFT-friendly field. + field -- Class object for the NTT-friendly field. MEAS_LEN -- Length of the encoded measurement input to the validity circuit. JOINT_RAND_LEN -- Length of the random input of the validity circuit. @@ -320,9 +320,7 @@ def prove(self, meas: list[F], prove_rand: list[F], joint_rand: list[F]) -> list # Compute the wire polynomials for this gadget. # # NOTE We pad the wire inputs to the nearest power of 2 so that we - # can use FFT for interpolating the wire polynomials. Perhaps there - # is some clever math for picking `wire_inp` in a way that avoids - # having to pad. + # can use NTT for interpolating the wire polynomials. assert self.field.GEN_ORDER % P == 0 alpha = self.field.gen() ** (self.field.GEN_ORDER // P) wire_inp = [alpha ** k for k in range(P)] diff --git a/poc/vdaf_poc/test_utils.py b/poc/vdaf_poc/test_utils.py index b24f5b84..e4172869 100644 --- a/poc/vdaf_poc/test_utils.py +++ b/poc/vdaf_poc/test_utils.py @@ -5,7 +5,7 @@ from vdaf_poc.common import (gen_rand, next_power_of_2, print_wrapped_line, to_le_bytes) -from vdaf_poc.field import FftField, poly_eval +from vdaf_poc.field import NttField, poly_eval from vdaf_poc.flp import Flp, run_flp from vdaf_poc.flp_bbcggi19 import FlpBBCGGI19, Gadget from vdaf_poc.vdaf import Vdaf, run_vdaf @@ -20,7 +20,7 @@ PrepState = TypeVar("PrepState") PrepShare = TypeVar("PrepShare") PrepMessage = TypeVar("PrepMessage") -F = TypeVar("F", bound=FftField) +F = TypeVar("F", bound=NttField) def test_vec_gen_rand(length: int) -> bytes: diff --git a/poc/vdaf_poc/vdaf_prio3.py b/poc/vdaf_poc/vdaf_prio3.py index 9140e250..7a4c0468 100644 --- a/poc/vdaf_poc/vdaf_prio3.py +++ b/poc/vdaf_poc/vdaf_prio3.py @@ -5,7 +5,7 @@ from vdaf_poc import flp_bbcggi19 from vdaf_poc.common import byte, concat, front, vec_add, vec_sub, zeros -from vdaf_poc.field import FftField, Field64, Field128 +from vdaf_poc.field import Field64, Field128, NttField from vdaf_poc.flp import Flp from vdaf_poc.vdaf import Vdaf from vdaf_poc.xof import Xof, XofTurboShake128 @@ -20,7 +20,7 @@ Measurement = TypeVar("Measurement") AggResult = TypeVar("AggResult") -F = TypeVar("F", bound=FftField) +F = TypeVar("F", bound=NttField) Prio3InputShare: TypeAlias = \ tuple[ # leader input share