Skip to content

Commit

Permalink
Adds PrivateNearestNeighbhorsSearch Client (#72)
Browse files Browse the repository at this point in the history
  • Loading branch information
fboemer authored Aug 23, 2024
1 parent 7affb01 commit 9e1e876
Show file tree
Hide file tree
Showing 19 changed files with 615 additions and 92 deletions.
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

0 comments on commit 9e1e876

Please sign in to comment.