-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
429 additions
and
64 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import Foundation | ||
|
||
enum HeError: Error, Equatable { | ||
case compositeModulus(_ modulus: Int) | ||
case coprimeModuli(moduli: [Int]) | ||
case invalidDegree(_ degree: Int) | ||
case invalidNttModulus(modulus: Int, degree: Int) | ||
case polyFormatMismatch(got: PolyFormat, expected: PolyFormat) | ||
} | ||
|
||
extension HeError: LocalizedError { | ||
var errorDescription: String? { | ||
switch self { | ||
case let .compositeModulus(modulus): | ||
"Composite modulus \(modulus)" | ||
case let .coprimeModuli(moduli): | ||
"Coprime moduli \(moduli)" | ||
case let .invalidDegree(degree): | ||
"Invalid degree \(degree)" | ||
case let .invalidNttModulus(modulus, degree): | ||
"Invalid NTT modulus \(modulus) for degree \(degree)" | ||
case let .polyFormatMismatch(got, expected): | ||
"PolyFormat mismatch: got \(got), expected \(expected)" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
extension FixedWidthInteger { | ||
func isNttModulus(for degree: Self) -> Bool { | ||
degree.isPowerOfTwo && isPrime(variableTime: true) && self % (Self(2) * degree) == 1 | ||
} | ||
} | ||
|
||
extension UnsignedInteger where Self: FixedWidthInteger { | ||
func isPrimitiveRootOfUnity(degree: Int, modulus: Self) -> Bool { | ||
// For degree a power of two, it suffices to check root^(degree/2) == -1 mod p | ||
// This implies root^degree == 1 mod p. Also, note 2 is the only prime factor of | ||
// degree. See | ||
// https://en.wikipedia.org/wiki/Root_of_unity_modulo_n#Testing_whether_x_is_a_primitive_k-th_root_of_unity_modulo_n | ||
precondition(degree.isPowerOfTwo) | ||
return powMod(exponent: Self(degree / 2), modulus: modulus, variableTime: true) == modulus - 1 | ||
} | ||
|
||
/// Generates a primitive `degree'th` root of unity for integers mod `self` | ||
func generatePrimitiveRootOfUnity(degree: Int) -> Self? { | ||
precondition(degree.isPowerOfTwo) | ||
precondition(isPrime(variableTime: true)) | ||
|
||
// See https://en.wikipedia.org/wiki/Root_of_unity_modulo_n#Finding_a_primitive_k-th_root_of_unity_modulo_n | ||
// Carmichael function lambda(p) = p - 1 for p prime | ||
let lambdaP = self - 1 | ||
|
||
// "If k does not divide lambda(n), then there will be no k-th roots of unity, at all." | ||
if !lambdaP.isMultiple(of: Self(degree)) { | ||
return nil | ||
} | ||
|
||
// The number of primitive roots mod p for p prime is phi(p-1), where phi is | ||
// Euler's totient function. We know phi(p-1) > p / (e^gamma log(log(p)) + 3 / | ||
// log(log(p)) (https://en.wikipedia.org/wiki/Euler%27s_totient_function#Growth_rate). | ||
// So the probability that a random value in [0, p-1] is a primitive root is at | ||
// least phi(p-1)/p > 1 / (e^gamma log(log(p)) + 3 / log(log(p)) > 1/8 for p | ||
// < 2^64 and where gamma is the Euler–Mascheroni constant ~= 0.577. That | ||
// is, we have at least 1/8 chance of finding a root on each attempt. So, (1 - | ||
// 1/8)^T < 2^{-128} yields T = 665 trials suffices for less than 2^{-128} | ||
// chance of failure. | ||
let trialCount = 665 | ||
var rng = SystemRandomNumberGenerator() | ||
for _ in 0..<trialCount { | ||
var root = Self.random(in: 0..<self, using: &rng) | ||
// root^(lambda(p)/degree) will be a primitive degree'th root of unity if root | ||
// is a lambda(p)'th root | ||
root = root.powMod(exponent: lambdaP / Self(degree), modulus: self, variableTime: true) | ||
if root.isPrimitiveRootOfUnity(degree: degree, modulus: self) { | ||
return root | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
/// Generates the smallest primitive `degree`'th primitive root for integers mod `self` | ||
/// Degree must be a power of two that divides p - 1 | ||
/// p must be prime | ||
func minPrimitiveRootOfUnity(degree: Int) -> Self? { | ||
guard var smallestGenerator = generatePrimitiveRootOfUnity(degree: degree) else { | ||
return nil | ||
} | ||
var currentGenerator = smallestGenerator | ||
|
||
// Given a generator g, g^l is a degree'th root of unity iff l and degree are | ||
// co-prime. Since degree is a power of two, we can check g, g^3, g^5, ... | ||
// See https://en.wikipedia.org/wiki/Root_of_unity_modulo_n#Finding_multiple_primitive_k-th_roots_modulo_n | ||
let generatorSquared = currentGenerator.powMod(exponent: 2, modulus: self, variableTime: true) | ||
let modulus = ReduceModulus(modulus: self, bound: ReduceModulus.InputBound.ModulusSquared, variableTime: true) | ||
for _ in 0..<degree / 2 { | ||
if currentGenerator < smallestGenerator { | ||
smallestGenerator = currentGenerator | ||
} | ||
currentGenerator = modulus.multiplyMod(currentGenerator, generatorSquared) | ||
} | ||
return smallestGenerator | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
extension Sequence where Element: Hashable { | ||
func allUnique() -> Bool { | ||
var seen = Set<Self.Element>() | ||
for element in self { | ||
guard seen.insert(element).inserted else { | ||
return false | ||
} | ||
} | ||
return true | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
// | ||
// NttTests.swift | ||
// SwiftHeTests | ||
// | ||
// Created by Fabian Boemer on 2/22/24. | ||
// | ||
|
||
@testable import SwiftHe | ||
import XCTest | ||
|
||
final class NttTests: XCTestCase { | ||
func testIsPrimitiveRootOfUnity() { | ||
XCTAssertTrue(UInt32(12).isPrimitiveRootOfUnity(degree: 2, modulus: 13)) | ||
XCTAssertFalse(UInt32(11).isPrimitiveRootOfUnity(degree: 2, modulus: 13)) | ||
XCTAssertFalse(UInt32(12).isPrimitiveRootOfUnity(degree: 4, modulus: 13)) | ||
|
||
XCTAssertTrue(UInt64(28).isPrimitiveRootOfUnity(degree: 2, modulus: 29)) | ||
XCTAssertTrue(UInt64(12).isPrimitiveRootOfUnity(degree: 4, modulus: 29)) | ||
XCTAssertFalse(UInt64(12).isPrimitiveRootOfUnity(degree: 2, modulus: 29)) | ||
XCTAssertFalse(UInt64(12).isPrimitiveRootOfUnity(degree: 8, modulus: 29)) | ||
|
||
XCTAssertTrue(UInt64(1_234_565_440).isPrimitiveRootOfUnity(degree: 2, modulus: 1_234_565_441)) | ||
XCTAssertTrue(UInt64(960_907_033).isPrimitiveRootOfUnity(degree: 8, modulus: 1_234_565_441)) | ||
XCTAssertTrue(UInt64(1_180_581_915).isPrimitiveRootOfUnity(degree: 16, modulus: 1_234_565_441)) | ||
XCTAssertFalse(UInt64(1_180_581_915).isPrimitiveRootOfUnity(degree: 32, modulus: 1_234_565_441)) | ||
XCTAssertFalse(UInt64(1_180_581_915).isPrimitiveRootOfUnity(degree: 8, modulus: 1_234_565_441)) | ||
XCTAssertFalse(UInt64(1_180_581_915).isPrimitiveRootOfUnity(degree: 2, modulus: 1_234_565_441)) | ||
} | ||
|
||
func testMinPrimitiveRootOfUnity() { | ||
XCTAssertEqual(UInt32(11).minPrimitiveRootOfUnity(degree: 2), 10) | ||
XCTAssertEqual(UInt32(29).minPrimitiveRootOfUnity(degree: 2), 28) | ||
XCTAssertEqual(UInt32(29).minPrimitiveRootOfUnity(degree: 4), 12) | ||
XCTAssertEqual(UInt64(1_234_565_441).minPrimitiveRootOfUnity(degree: 2), 1_234_565_440) | ||
XCTAssertEqual(UInt64(1_234_565_441).minPrimitiveRootOfUnity(degree: 8), 249_725_733) | ||
} | ||
} |
Oops, something went wrong.