Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rename FFT to NTT #402

Merged
merged 1 commit into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 18 additions & 13 deletions draft-irtf-cfrg-vdaf.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1947,22 +1956,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()`.
Expand Down Expand Up @@ -2450,7 +2459,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}}).
Expand Down Expand Up @@ -3299,7 +3308,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
Expand Down Expand Up @@ -3451,10 +3460,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]`.

Expand Down
12 changes: 6 additions & 6 deletions poc/tests/test_field.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down Expand Up @@ -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)
Expand Down
10 changes: 5 additions & 5 deletions poc/tests/test_flp_bbcggi19.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
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)
from vdaf_poc.test_utils import TestFlpBBCGGI19

Measurement = TypeVar("Measurement")
AggResult = TypeVar("AggResult")
F = TypeVar("F", bound=FftField)
F = TypeVar("F", bound=NttField)


class MultiGadget(Valid[int, int, Field64]):
Expand Down Expand Up @@ -140,22 +140,22 @@ 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(
FlpBBCGGI19(sumvec), measurements)

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,
Expand Down
4 changes: 2 additions & 2 deletions poc/tests/test_vdaf_prio3.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

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,
Prio3MultihotCountVec, Prio3Sum, Prio3SumVec,
Prio3SumVecWithMultiproof)
from vdaf_poc.xof import XofTurboShake128

F = TypeVar("F", bound=FftField)
F = TypeVar("F", bound=NttField)


class Prio3Average(Prio3):
Expand Down
14 changes: 10 additions & 4 deletions poc/vdaf_poc/field.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
12 changes: 5 additions & 7 deletions poc/vdaf_poc/flp_bbcggi19.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)]
Expand Down
4 changes: 2 additions & 2 deletions poc/vdaf_poc/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions poc/vdaf_poc/vdaf_prio3.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Loading