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

Adds PrivateNearestNeighbhorsSearch Client #72

Merged
merged 1 commit into from
Aug 23, 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
18 changes: 18 additions & 0 deletions Sources/HomomorphicEncryption/Array2d.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,19 @@

/// Stores values in a 2 dimensional array.
public struct Array2d<T: Equatable & AdditiveArithmetic & Sendable>: Equatable, Sendable {
/// Values stored in row-major order.
@usableFromInline package var data: [T]
@usableFromInline package var rowCount: Int
@usableFromInline package var columnCount: Int

@usableFromInline package var shape: (Int, Int) { (rowCount, columnCount) }
@usableFromInline package var count: Int { rowCount * columnCount }

@inlinable
package init(data: [[T]]) {
self.init(data: data.flatMap { $0 }, rowCount: data.count, columnCount: data[0].count)
}

@inlinable
package init(data: [T], rowCount: Int, columnCount: Int) {
precondition(data.count == rowCount * columnCount)
Expand Down Expand Up @@ -175,4 +181,16 @@ extension Array2d {
HomomorphicEncryption.zeroize(dataPointer.baseAddress!, zeroizeSize)
}
}

/// Returns the matrix after transforming each entry with a function.
/// - Parameter transform: A mapping closure. `transform` accepts an element of the array as its parameter and
/// returns a transformed value of the same or of a different type.
/// - Returns: The transformed matrix.
@inlinable
package func map<V: Equatable & AdditiveArithmetic & Sendable>(_ transform: (T) -> (V)) -> Array2d<V> {
Array2d<V>(
data: data.map { value in transform(value) },
rowCount: rowCount,
columnCount: columnCount)
}
}
13 changes: 7 additions & 6 deletions Sources/HomomorphicEncryption/Bfv/Bfv+Decrypt.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,14 @@ extension Bfv {
{
// See Definition 1 of
// https://www.microsoft.com/en-us/research/wp-content/uploads/2017/06/sealmanual_v2.2.pdf.
precondition(variableTime)
var vTimesT = try Self.dotProduct(ciphertext: ciphertext, with: secretKey)
vTimesT *= Array(repeating: ciphertext.context.plaintextModulus, count: vTimesT.moduli.count)
let rnsTool = ciphertext.context.getRnsTool(moduliCount: vTimesT.moduli.count)

func computeNoiseBudget<U: FixedWidthInteger>(of _: PolyRq<T, Coeff>, _: U.Type) throws -> Double {
let vTimesTComposed: [U] = try rnsTool.crtCompose(
poly: vTimesT,
variableTime: variableTime)

func computeNoiseBudget<U: FixedWidthInteger & UnsignedInteger>(of _: PolyRq<T, Coeff>,
_: U.Type) throws -> Double
{
let vTimesTComposed: [U] = try rnsTool.crtCompose(poly: vTimesT)
let q: U = vTimesT.moduli.product()
let qDiv2 = (q &+ 1) &>> 1
let noiseInfinityNorm = Double(vTimesTComposed.map { coeff in
Expand All @@ -78,10 +76,13 @@ extension Bfv {
case 0..<tMax:
return try computeNoiseBudget(of: vTimesT, T.self)
case tMax..<pow(tMax, 2):
precondition(variableTime)
return try computeNoiseBudget(of: vTimesT, T.DoubleWidth.self)
case tMax..<pow(tMax, 4):
precondition(variableTime)
return try computeNoiseBudget(of: vTimesT, QuadWidth<T>.self)
case tMax..<pow(tMax, 8):
precondition(variableTime)
return try computeNoiseBudget(of: vTimesT, OctoWidth<T>.self)
default:
preconditionFailure("crtMaxIntermediateValue \(crtMaxIntermediateValue) too large")
Expand Down
2 changes: 1 addition & 1 deletion Sources/HomomorphicEncryption/Ciphertext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ public struct Ciphertext<Scheme: HeScheme, Format: PolyFormat>: Equatable, Senda
/// ``HeScheme/minNoiseBudget``, decryption may yield inaccurate plaintexts.
/// - Parameters:
/// - secretKey: Secret key.
/// - variableTime: Must be `true`, indicating the secret key coefficients are leaked through timing.
/// - variableTime: If `true`, indicates the secret key coefficients may be leaked through timing.
/// - Returns: The noise budget.
/// - Throws: Error upon failure to compute the noise budget.
/// - Warning: Leaks `secretKey` through timing. Should be used for testing only.
Expand Down
116 changes: 116 additions & 0 deletions Sources/HomomorphicEncryption/CrtComposer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Copyright 2024 Apple Inc. and the Swift Homomorphic Encryption project authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/// Performs Chinese remainder theorem (CRT) composition of coefficients.
@usableFromInline
package struct CrtComposer<T: ScalarType>: Sendable {
/// Context for the CRT moduli `q_i`.
@usableFromInline let polyContext: PolyContext<T>

/// i'th entry stores `(q_i / q) % q_i`.
@usableFromInline let inversePuncturedProducts: [MultiplyConstantModulus<T>]

/// Creates a new ``CrtComposer``.
/// - Parameter polyContext: Context for the CRT moduli.
/// - Throws: Error upon failure to create a new ``CrtComposer``.
@inlinable
package init(polyContext: PolyContext<T>) throws {
self.polyContext = polyContext
self.inversePuncturedProducts = try polyContext.reduceModuli.map { qi in
var puncturedProduct = T(1)
for qj in polyContext.moduli where qj != qi.modulus {
let prod = puncturedProduct.multipliedFullWidth(by: qj)
puncturedProduct = qi.reduce(T.DoubleWidth(prod))
}
let inversePuncturedProduct = try puncturedProduct.inverseMod(
modulus: qi.modulus,
variableTime: true)
return MultiplyConstantModulus(
multiplicand: inversePuncturedProduct,
modulus: qi.modulus,
variableTime: true)
}
}

/// Returns an upper bound on the maximum value during a `crtCompose` call.
/// - Parameter moduli: Moduli in the polynomial context
/// - Returns: The upper bound.
@inlinable
package static func composeMaxIntermediateValue(moduli: [T]) -> Double {
let moduli = moduli.map { Double($0) }
if moduli.count == 1 {
return moduli[0]
}
let q = moduli.reduce(1.0, *)
return 2.0 * q
}

/// Performs Chinese remainder theorem (CRT) composition on a list of
/// coefficients.
///
/// The composition yields a polynomial with coefficients in `[0, q - 1]`.
/// - Parameter data:Data to compose. Each column must contain a
/// coefficient's residues mod each modulus.
/// - Returns: The composed coefficients. Each coefficient must be able to
/// store values up to
/// `crtComposeMaxIntermediateValue`.
/// - Throws: `HeError` upon failure to compose the polynomial.
/// - Warning: `V`'s operations must be constant time to prevent leaking
/// `poly` through timing.
@inlinable
package func compose<V: FixedWidthInteger &
UnsignedInteger>(data: Array2d<T>) throws -> [V]
{
precondition(data.rowCount == polyContext.moduli.count)
precondition(Double(V.max) >= Self
.composeMaxIntermediateValue(moduli: polyContext.moduli))
let q: V = polyContext.moduli.product()
let puncturedProducts = polyContext.moduli.map { qi in q / V(qi) }

var products: [V] = Array(repeating: 0, count: data.columnCount)
for row in 0..<data.rowCount {
let puncturedProduct = puncturedProducts[row]
let inversePuncturedProduct = inversePuncturedProducts[row]
for column in 0..<data.columnCount {
let tmp = V(inversePuncturedProduct.multiplyMod(data[
row,
column
]))
let addend = tmp &* puncturedProduct
products[column] = products[column].addMod(addend, modulus: q)
}
}
return products
}

/// Performs Chinese remainder theorem (CRT) composition on a polynomial's
/// coefficients.
///
/// The composition yields a polynomial with coefficients in `[0, q)`.
/// - Parameter poly: Polynomial whose coefficients to compose. Must have the
/// same context as ``polyContext``.
/// - Returns: The composed coefficients. Each coefficient must be able to
/// store values up to
/// `crtComposeMaxIntermediateValue`.
/// - Throws: `HeError` upon failure to compose the polynomial.
/// - Warning: `V`'s operations must be constant time to prevent leaking
/// `poly` through timing.
@inlinable
package func compose<V: FixedWidthInteger & UnsignedInteger>(poly: PolyRq<
T,
Coeff
>) throws -> [V] {
try compose(data: poly.data)
}
}
4 changes: 2 additions & 2 deletions Sources/HomomorphicEncryption/HeScheme.swift
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,7 @@ public protocol HeScheme {
/// - Parameters:
/// - ciphertext: Ciphertext whose noise budget to compute.
/// - secretKey: Secret key.
/// - variableTime: Must be `true`, indicating the secret key coefficients are leaked through timing.
/// - variableTime: If `true`, indicates the secret key coefficients may be leaked through timing.
/// - Returns: The noise budget.
/// - Throws: Error upon failure to compute the noise budget.
/// - Warning: Leaks `secretKey` through timing. Should be used for testing only.
Expand All @@ -651,7 +651,7 @@ public protocol HeScheme {
/// - Parameters:
/// - ciphertext: Ciphertext whose noise budget to compute.
/// - secretKey: Secret key.
/// - variableTime: Must be `true`, indicating the secret key coefficients are leaked through timing.
/// - variableTime: If `true`, indicates the secret key coefficients may be leaked through timing.
/// - Returns: The noise budget.
/// - Throws: Error upon failure to compute the noise budget.
/// - Warning: Leaks `secretKey` through timing. Should be used for testing only.
Expand Down
2 changes: 1 addition & 1 deletion Sources/HomomorphicEncryption/PolyRq/PolyContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public final class PolyContext<T: ScalarType>: Sendable {
/// Number `N` of coefficients in the polynomial, must be a power of two.
@usableFromInline let degree: Int
/// CRT-representation of the modulus `Q = product_{i=0}^{L-1} q_i`.
@usableFromInline let moduli: [T]
@usableFromInline package let moduli: [T]
/// Next context, typically formed by dropping `q_{L-1}`.
@usableFromInline let next: PolyContext<T>?
/// Operations mod `q_0` up to `q_{L-1}`.
Expand Down
71 changes: 14 additions & 57 deletions Sources/HomomorphicEncryption/RnsBaseConverter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,14 @@ struct RnsBaseConverter<T: ScalarType>: Sendable {
@usableFromInline let outputContext: PolyContext<T>
/// (i, j)'th entry stores `(q / q_i) % t_j`.
@usableFromInline let puncturedProducts: Array2d<T>
/// i'th entry stores `(q_i / q) % q_i``.
@usableFromInline let inversePuncturedProducts: [MultiplyConstantModulus<T>]

/// Composes polynomials with `inputContext`.
@usableFromInline let crtComposer: CrtComposer<T>

/// i'th entry stores `(q_i / q) % q_i`.
@usableFromInline var inversePuncturedProducts: [MultiplyConstantModulus<T>] {
crtComposer.inversePuncturedProducts
}

@inlinable
init(from inputContext: PolyContext<T>, to outputContext: PolyContext<T>) throws {
Expand All @@ -46,23 +52,12 @@ struct RnsBaseConverter<T: ScalarType>: Sendable {
rowCount: outputContext.moduli.count,
columnCount: inputContext.moduli.count)

self.inversePuncturedProducts = try inputContext.reduceModuli.map { qi in
var puncturedProduct = T(1)
for qj in inputContext.moduli where qj != qi.modulus {
let prod = puncturedProduct.multipliedFullWidth(by: qj)
puncturedProduct = qi.reduce(T.DoubleWidth(prod))
}
let inversePuncturedProduct = try puncturedProduct.inverseMod(modulus: qi.modulus, variableTime: true)
return MultiplyConstantModulus(
multiplicand: inversePuncturedProduct,
modulus: qi.modulus,
variableTime: true)
}
self.crtComposer = try CrtComposer(polyContext: inputContext)
}

/// Performs approximate base conversion.
///
/// Converts input polynomial with coefficients `x_i mod q` to `(x_i + a_x * q) % t` where `a_x \in [0, L-1]`, for
/// Converts input polynomial with coefficients `x_i mod q` to `(x_i + a_x * q) % t` where `a_x \in [0, L - 1]`, for
/// `L` the number of moduli in the input basis `q.
/// - Parameter poly: Input polynomial with base `q`.
/// - Returns: Converted polynomial with base `t`.
Expand All @@ -76,55 +71,17 @@ struct RnsBaseConverter<T: ScalarType>: Sendable {
return convertApproximate(using: poly)
}

/// Returns an upper bound on the maximum value during a `crtCompose` call.
@inlinable
func crtComposeMaxIntermediateValue() -> Double {
let moduli = inputContext.moduli.map { Double($0) }
if moduli.count == 1 {
return moduli[0]
}
let q = moduli.reduce(1.0, *)
return 2.0 * q
}

/// Performs Chinese remainder theorem (CRT) composition of each coefficient in `poly`.
///
/// The composition yields a polynomial with coefficients in `[0, q - 1]`.
/// - Parameters:
/// - poly: Polynomial to compose.
/// - variableTime: Must be `true`, indicating `poly`'s coefficients are leaked through timing.
/// - Parameter poly: Polynomial to compose.
/// - Returns: The coefficients in the composed polynomial. Each coefficient must be able to store values up to
/// `crtComposeMaxIntermediateValue`.
/// - Throws: `HeError` upon failure to compose the polynomial.
/// - Warning: Leaks `poly` through timing.
/// - Warning: `V`'s operations must be constant time to prevent leaking `poly` through timing.
@inlinable
func crtCompose<V: FixedWidthInteger>(poly: PolyRq<T, Coeff>, variableTime: Bool) throws -> [V] {
precondition(variableTime)
precondition(Double(V.max) >= crtComposeMaxIntermediateValue())
guard poly.context == inputContext else {
throw HeError.invalidPolyContext(poly.context)
}
if inputContext.moduli.count == 1 {
return poly.poly(rnsIndex: 0).map { V($0) }
}
let q: V = inputContext.moduli.product()
let puncturedProducts = inputContext.moduli.map { qi in
q / V(qi)
}
return poly.coeffIndices.map { coeffIndex in
var product: V = 0
for (rnsCoeff, (puncturedProduct, inversePuncturedProduct)) in zip(
poly.coefficient(coeffIndex: coeffIndex),
zip(puncturedProducts, inversePuncturedProducts))
{
let tmp = inversePuncturedProduct.multiplyMod(rnsCoeff)
product &+= V(tmp) &* puncturedProduct
if product >= q {
product &-= q
}
}
return product
}
func crtCompose<V: FixedWidthInteger & UnsignedInteger>(poly: PolyRq<T, Coeff>) throws -> [V] {
try crtComposer.compose(poly: poly)
}

/// Computes approximate products.
Expand Down
13 changes: 6 additions & 7 deletions Sources/HomomorphicEncryption/RnsTool.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// limitations under the License.

@usableFromInline
struct RnsTool<T: ScalarType>: Sendable {
package struct RnsTool<T: ScalarType>: Sendable {
/// `Q = q_0, ..., q_{L-1}`.
@usableFromInline let inputContext: PolyContext<T>
/// `t_0, ..., t_{M-1}`.
Expand Down Expand Up @@ -396,17 +396,16 @@ struct RnsTool<T: ScalarType>: Sendable {
/// - poly: Polynomial whose coefficients to compose.
/// - variableTime: Must be `true`, indicating the coefficients of the polynomial are leaked through timing.
/// - Returns: The coefficients of `poly`, each in `[0, Q - 1]`.
/// - Warning: Leaks `poly` through timing.
/// - Warning: `V`'s operations must be constant time to prevent leaking `poly` through timing.
@inlinable
func crtCompose<V: FixedWidthInteger>(poly: PolyRq<T, Coeff>, variableTime: Bool) throws -> [V] {
precondition(variableTime)
package func crtCompose<V: FixedWidthInteger & UnsignedInteger>(poly: PolyRq<T, Coeff>) throws -> [V] {
// Use arbitrary base converter that has same inputContext
return try rnsConvertQToBSk.crtCompose(poly: poly, variableTime: variableTime)
try rnsConvertQToBSk.crtCompose(poly: poly)
}

/// Returns an upper bound on the maximum value during a `crtCompose` call.
@inlinable
func crtComposeMaxIntermediateValue() -> Double {
rnsConvertQToBSk.crtComposeMaxIntermediateValue()
package func crtComposeMaxIntermediateValue() -> Double {
CrtComposer.composeMaxIntermediateValue(moduli: inputContext.moduli)
}
}
4 changes: 3 additions & 1 deletion Sources/HomomorphicEncryption/Scalar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ extension FixedWidthInteger {
}
}

extension ScalarType {
extension UnsignedInteger where Self: FixedWidthInteger {
/// Computes the high `Self.bitWidth` bits of `self * rhs`.
/// - Parameter rhs: Multiplicand.
/// - Returns: the high `Self.bitWidth` bits of `self * rhs`.
Expand Down Expand Up @@ -269,7 +269,9 @@ extension ScalarType {
let sum = self &+ modulus &- rhs
return sum.subtractIfExceeds(modulus)
}
}

extension ScalarType {
/// Computes modular exponentiation.
///
/// Computes self raised to the power of `exponent` mod `modulus, i.e., `self^exponent mod modulus`.
Expand Down
Loading
Loading