clock-curve-math 1.1.3

High-performance, constant-time, cryptography-grade number theory library for ClockCurve ecosystem
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
#!/usr/bin/env python3
"""
Full FFT/NTT Implementation for Large Integer Multiplication

This module provides complete implementations of:
1. Fast Fourier Transform (FFT) using Cooley-Tukey algorithm
2. Number Theoretic Transform (NTT) for modular arithmetic
3. Polynomial multiplication for large integer multiplication

These algorithms achieve O(n log n) complexity for multiplication of n-bit numbers.
"""

import sage.all as sage
from sage.all import *
import math
import numpy as np
from typing import List, Tuple, Optional


class FFTNTTMultiplication:
    """Complete FFT/NTT implementation for large integer multiplication."""

    def __init__(self, ntt_modulus: Optional[int] = None):
        """
        Initialize FFT/NTT multiplication.

        Args:
            ntt_modulus: Prime modulus for NTT. If None, uses FFT instead.
        """
        self.ntt_modulus = ntt_modulus
        if ntt_modulus:
            self.ntt_params = self._find_ntt_parameters(ntt_modulus)

    def _find_ntt_parameters(self, modulus: int) -> dict:
        """
        Find NTT parameters for a given modulus.

        Returns dict with:
        - modulus: The prime modulus
        - primitive_root: A primitive root modulo modulus
        - max_length: Maximum transform length supported
        """
        # Find a primitive root
        for g in range(2, modulus):
            if pow(g, modulus-1, modulus) == 1:
                # Check if it's a primitive root
                is_primitive = True
                for i in range(2, modulus-1):
                    if pow(g, i, modulus) == 1:
                        is_primitive = False
                        break
                if is_primitive:
                    primitive_root = g
                    break
        else:
            raise ValueError(f"No primitive root found for modulus {modulus}")

        # Find maximum power of 2 that divides modulus-1
        max_length = 1
        while (modulus - 1) % (2 * max_length) == 0:
            max_length *= 2

        return {
            'modulus': modulus,
            'primitive_root': primitive_root,
            'max_length': max_length
        }

    def cooley_tukey_fft(self, coeffs: List[complex], inverse: bool = False) -> List[complex]:
        """
        Cooley-Tukey Fast Fourier Transform.

        Args:
            coeffs: Input coefficients
            inverse: If True, compute inverse FFT

        Returns:
            FFT of input coefficients
        """
        n = len(coeffs)
        if n <= 1:
            return coeffs

        # Check if n is power of 2
        if n & (n - 1) != 0:
            raise ValueError("FFT length must be power of 2")

        # Split into even and odd indices
        even = self.cooley_tukey_fft(coeffs[::2], inverse)
        odd = self.cooley_tukey_fft(coeffs[1::2], inverse)

        # Combine results
        result = [0] * n
        angle = (-2 if inverse else 2) * pi * I / n

        for k in range(n//2):
            t = exp(angle * k) * odd[k]
            result[k] = even[k] + t
            result[k + n//2] = even[k] - t

        return result

    def number_theoretic_transform(self, coeffs: List[int], inverse: bool = False) -> List[int]:
        """
        Number Theoretic Transform (NTT) for modular arithmetic.

        Args:
            coeffs: Input coefficients (integers)
            inverse: If True, compute inverse NTT

        Returns:
            NTT of input coefficients
        """
        if not self.ntt_modulus:
            raise ValueError("NTT modulus not set")

        n = len(coeffs)
        if n <= 1:
            return coeffs

        # Check if n is power of 2 and divides modulus-1
        if n & (n - 1) != 0:
            raise ValueError("NTT length must be power of 2")

        params = self.ntt_params
        modulus = params['modulus']
        primitive_root = params['primitive_root']

        # Compute root of unity
        if inverse:
            # For inverse NTT, use w^(-1) where w is the n-th root of unity
            root = pow(primitive_root, (modulus - 1) // n, modulus)
            root = pow(root, modulus - 2, modulus)  # Modular inverse
        else:
            root = pow(primitive_root, (modulus - 1) // n, modulus)

        # Cooley-Tukey NTT algorithm
        def ntt_recursive(coeffs, root):
            n = len(coeffs)
            if n == 1:
                return coeffs

            # Split into even and odd
            even = ntt_recursive(coeffs[::2], pow(root, 2, modulus))
            odd = ntt_recursive(coeffs[1::2], pow(root, 2, modulus))

            result = [0] * n
            current_root = 1

            for k in range(n//2):
                t = (current_root * odd[k]) % modulus
                result[k] = (even[k] + t) % modulus
                result[k + n//2] = (even[k] - t) % modulus
                current_root = (current_root * root) % modulus

            return result

        result = ntt_recursive(coeffs, root)

        # Scale by n^(-1) for inverse transform
        if inverse:
            n_inv = pow(n, modulus - 2, modulus)  # Modular inverse of n
            result = [(x * n_inv) % modulus for x in result]

        return result

    def polynomial_multiply_fft(self, a: List[complex], b: List[complex]) -> List[complex]:
        """
        Multiply two polynomials using FFT.

        Args:
            a, b: Coefficient lists of polynomials

        Returns:
            Coefficients of product polynomial
        """
        # Pad to next power of 2
        n = 1
        while n < len(a) + len(b) - 1:
            n *= 2

        a_padded = a + [0] * (n - len(a))
        b_padded = b + [0] * (n - len(b))

        # FFT
        a_fft = self.cooley_tukey_fft(a_padded)
        b_fft = self.cooley_tukey_fft(b_padded)

        # Pointwise multiplication
        c_fft = [x * y for x, y in zip(a_fft, b_fft)]

        # Inverse FFT
        c = self.cooley_tukey_fft(c_fft, inverse=True)

        # Scale by 1/n and take real part
        result = [abs(x)/n for x in c]

        # Round to nearest integers
        return [round(x) for x in result]

    def polynomial_multiply_ntt(self, a: List[int], b: List[int]) -> List[int]:
        """
        Multiply two polynomials using NTT.

        Args:
            a, b: Coefficient lists of polynomials

        Returns:
            Coefficients of product polynomial
        """
        if not self.ntt_modulus:
            raise ValueError("NTT modulus not set")

        # Pad to next power of 2
        n = 1
        while n < len(a) + len(b) - 1:
            n *= 2

        a_padded = a + [0] * (n - len(a))
        b_padded = b + [0] * (n - len(b))

        # NTT
        a_ntt = self.number_theoretic_transform(a_padded)
        b_ntt = self.number_theoretic_transform(b_padded)

        # Pointwise multiplication modulo the NTT modulus
        modulus = self.ntt_modulus
        c_ntt = [(x * y) % modulus for x, y in zip(a_ntt, b_ntt)]

        # Inverse NTT
        c = self.number_theoretic_transform(c_ntt, inverse=True)

        return c

    def big_integer_multiply_fft(self, a: int, b: int) -> int:
        """
        Multiply two large integers using FFT.

        Args:
            a, b: Large integers to multiply

        Returns:
            Product a * b
        """
        # Convert to base-10^9 digits for better precision
        base = 10**9

        def to_digits(n):
            if n == 0:
                return [0]
            digits = []
            while n > 0:
                digits.append(n % base)
                n //= base
            return digits

        a_digits = to_digits(a)
        b_digits = to_digits(b)

        # Multiply as polynomials
        product_coeffs = self.polynomial_multiply_fft(a_digits, b_digits)

        # Convert back to integer
        result = 0
        for i, coeff in enumerate(product_coeffs):
            result += int(coeff) * (base ** i)

        return result

    def big_integer_multiply_ntt(self, a: int, b: int) -> int:
        """
        Multiply two large integers using NTT.

        Args:
            a, b: Large integers to multiply

        Returns:
            Product a * b
        """
        if not self.ntt_modulus:
            raise ValueError("NTT modulus not set")

        # Convert to digits that fit within the modulus
        modulus = self.ntt_modulus
        base = modulus // 10  # Conservative base choice

        def to_digits(n):
            if n == 0:
                return [0]
            digits = []
            while n > 0:
                digits.append(n % base)
                n //= base
            return digits

        a_digits = to_digits(a)
        b_digits = to_digits(b)

        # Multiply as polynomials
        product_coeffs = self.polynomial_multiply_ntt(a_digits, b_digits)

        # Convert back to integer
        result = 0
        for i, coeff in enumerate(product_coeffs):
            result += coeff * (base ** i)

        return result

    def multiply(self, a: int, b: int, method: str = 'ntt') -> int:
        """
        Multiply two large integers using the specified method.

        Args:
            a, b: Integers to multiply
            method: 'fft' or 'ntt'

        Returns:
            Product a * b
        """
        if method == 'fft':
            return self.big_integer_multiply_fft(a, b)
        elif method == 'ntt':
            return self.big_integer_multiply_ntt(a, b)
        else:
            raise ValueError(f"Unknown method: {method}")

    def test_correctness(self):
        """Test correctness of multiplication algorithms."""
        print("Testing FFT/NTT multiplication correctness...")

        # Test cases
        test_cases = [
            (0, 0),
            (1, 1),
            (123, 456),
            (1234, 5678),
            (10**10, 10**10),
            (2**100, 2**100),
        ]

        # Test FFT
        print("\nTesting FFT:")
        for a, b in test_cases:
            try:
                expected = a * b
                result = self.big_integer_multiply_fft(a, b)
                correct = (result == expected)
                print(f"  {a} * {b}: {'' if correct else ''}")
                if not correct:
                    print(f"    Expected: {expected}")
                    print(f"    Got:      {result}")
            except Exception as e:
                print(f"  {a} * {b}: Error - {e}")

        # Test NTT with different moduli
        print("\nTesting NTT:")
        ntt_moduli = [13, 17, 97, 193, 257, 7681]  # NTT-friendly primes

        for modulus in ntt_moduli:
            try:
                self.ntt_modulus = modulus
                self.ntt_params = self._find_ntt_parameters(modulus)

                print(f"\n  Modulus {modulus}:")
                for a, b in test_cases[:3]:  # Test fewer cases for NTT
                    expected = a * b
                    result = self.big_integer_multiply_ntt(a, b)
                    correct = (result == expected)
                    print(f"    {a} * {b}: {'' if correct else ''}")
            except Exception as e:
                print(f"    Modulus {modulus}: Error - {e}")

    def benchmark(self, sizes=None):
        """Benchmark multiplication algorithms."""
        if sizes is None:
            sizes = [10, 100, 1000, 10000]

        print("\nBenchmarking FFT/NTT multiplication...")
        print("Digits | FFT Time (s) | NTT Time (s) | Standard (s)")
        print("-" * 50)

        import time

        for digits in sizes:
            # Generate test numbers
            a = 10**digits - 1  # All 9's
            b = 10**(digits//2) - 1  # Smaller number

            # Benchmark FFT
            try:
                start = time.time()
                fft_result = self.big_integer_multiply_fft(a, b)
                fft_time = time.time() - start
            except Exception as e:
                fft_time = float('inf')
                print(f"FFT error: {e}")

            # Benchmark NTT (with a suitable modulus)
            try:
                # Use a large NTT-friendly prime
                self.ntt_modulus = 998244353  # 2^23 * 119 + 1, NTT-friendly
                self.ntt_params = self._find_ntt_parameters(self.ntt_modulus)
                start = time.time()
                ntt_result = self.big_integer_multiply_ntt(a, b)
                ntt_time = time.time() - start
            except Exception as e:
                ntt_time = float('inf')
                print(f"NTT error: {e}")

            # Benchmark standard multiplication
            try:
                start = time.time()
                std_result = a * b
                std_time = time.time() - start
            except Exception as e:
                std_time = float('inf')

            # Verify correctness occasionally
            if digits <= 100:
                expected = a * b
                if 'fft_result' in locals() and fft_result != expected:
                    print(f"  FFT gave wrong result for {digits} digits!")
                if 'ntt_result' in locals() and ntt_result != expected:
                    print(f"  NTT gave wrong result for {digits} digits!")

            print(">6"
                  ">12.3f"
                  ">12.3f"
                  ">12.3f")


# Example usage and testing
if __name__ == "__main__":
    # Initialize with a good NTT modulus
    ntt_modulus = 998244353  # 2^23 * 119 + 1, good for NTT
    mult = FFTNTTMultiplication(ntt_modulus)

    # Test correctness
    mult.test_correctness()

    # Benchmark
    mult.benchmark([10, 50, 100])