Skip to content
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
3 changes: 3 additions & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ excluded:
- "**/DoubleWidthUInt.swift"
- "**/.build/"

type_name:
allowed_symbols: "_"

line_length:
warning: 120
ignores_urls: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@

import _BenchmarkUtilities
import HomomorphicEncryption
import PrivateInformationRetrieval

nonisolated(unsafe) let benchmarks: () -> Void = {
pirProcessBenchmark(Bfv<UInt32>.self)()
pirProcessBenchmark(Bfv<UInt64>.self)()
pirProcessBenchmark(PirUtil<Bfv<UInt32>>.self)()
pirProcessBenchmark(PirUtil<Bfv<UInt64>>.self)()

indexPirBenchmark(Bfv<UInt32>.self)()
indexPirBenchmark(Bfv<UInt64>.self)()
indexPirBenchmark(PirUtil<Bfv<UInt32>>.self)()
indexPirBenchmark(PirUtil<Bfv<UInt64>>.self)()

keywordPirBenchmark(Bfv<UInt32>.self)()
keywordPirBenchmark(Bfv<UInt64>.self)()
keywordPirBenchmark(PirUtil<Bfv<UInt32>>.self)()
keywordPirBenchmark(PirUtil<Bfv<UInt64>>.self)()
}
4 changes: 2 additions & 2 deletions Benchmarks/RlweBenchmark/RlweBenchmark.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func getRandomPlaintextData<T: ScalarType>(count: Int, in range: Range<T>) -> [T

struct RlweBenchmarkContext<Scheme: HeScheme>: Sendable {
var encryptionParameters: EncryptionParameters<Scheme.Scalar>
var context: Context<Scheme>
var context: Scheme.Context

let data: [Scheme.Scalar]
let signedData: [Scheme.SignedScalar]
Expand All @@ -69,7 +69,7 @@ struct RlweBenchmarkContext<Scheme: HeScheme>: Sendable {
Scheme.Scalar.self),
errorStdDev: ErrorStdDev.stdDev32,
securityLevel: SecurityLevel.quantum128)
self.context = try Context(encryptionParameters: encryptionParameters)
self.context = try Scheme.Context(encryptionParameters: encryptionParameters)
self.secretKey = try context.generateSecretKey()
let columnElement = GaloisElement.swappingRows(degree: polyDegree)
let rowElement = try GaloisElement.rotatingColumns(by: rotateColumnsStep, degree: polyDegree)
Expand Down
18 changes: 18 additions & 0 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 20 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,22 @@ let executableSettings: [SwiftSetting] =

let benchmarkSettings: [SwiftSetting] = [.unsafeFlags(["-cross-module-optimization"], .when(configuration: .release))]

let enableFlags = "SWIFT_HOMOMORPHIC_ENCRYPTION_MODULAR_ARITHMETIC_EXTRA_SWIFT_FLAGS"
func shouldEnableFlags() -> Bool {
if let flag = ProcessInfo.processInfo.environment[enableFlags], flag != "0", flag != "false" {
return true
}
return false
}

var flags: [SwiftSetting] = []
let enableFlagsBool = shouldEnableFlags()
if enableFlagsBool {
print("Building with additional flags. To disable, unset \(enableFlags) in your environment.")
let flagsAsString = (ProcessInfo.processInfo.environment[enableFlags] ?? "") as String
flags += [.unsafeFlags(flagsAsString.components(separatedBy: ","))]
}

let package = Package(
name: "swift-homomorphic-encryption",
products: [
Expand Down Expand Up @@ -60,6 +76,7 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/apple/swift-algorithms", from: "1.2.0"),
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.0"),
.package(url: "https://github.com/apple/swift-async-algorithms.git", from: "1.0.2"),
.package(url: "https://github.com/apple/swift-crypto.git", from: "3.10.0"),
.package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-numerics", from: "1.0.0"),
Expand All @@ -71,7 +88,7 @@ let package = Package(
.target(
name: "ModularArithmetic",
dependencies: [],
swiftSettings: librarySettings),
swiftSettings: librarySettings + flags),
.target(
name: "CUtil",
dependencies: [],
Expand Down Expand Up @@ -100,12 +117,14 @@ let package = Package(
.target(
name: "PrivateInformationRetrieval",
dependencies: ["HomomorphicEncryption",
.product(name: "AsyncAlgorithms", package: "swift-async-algorithms"),
.product(name: "Numerics", package: "swift-numerics")],
swiftSettings: librarySettings),
.target(
name: "PrivateNearestNeighborSearch",
dependencies: [
.product(name: "Algorithms", package: "swift-algorithms"),
.product(name: "AsyncAlgorithms", package: "swift-async-algorithms"),
"HomomorphicEncryption",
"_HomomorphicEncryptionExtras",
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func summarize<Scheme: HeScheme>(
parameters: EncryptionParameters<Scheme.Scalar>, _: Scheme.Type) throws
{
let values = (0..<8).map { Scheme.Scalar($0) }
let context = try Context<Scheme>(encryptionParameters: parameters)
let context = try Scheme.Context(encryptionParameters: parameters)
let plaintext: Scheme.CoeffPlaintext = try context.encode(
values: values,
format: .coefficient)
Expand Down
4 changes: 2 additions & 2 deletions Sources/ApplicationProtobuf/PirConversion.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ extension Apple_SwiftHomomorphicEncryption_Pir_V1_EncryptedIndices {
/// - Parameter context: Context to associate with the native type.
/// - Returns: The converted native type.
/// - Throws: Error upon invalid protobuf object.
public func native<Scheme: HeScheme>(context: Context<Scheme>) throws -> Query<Scheme> {
public func native<Scheme: HeScheme>(context: Scheme.Context) throws -> Query<Scheme> {
let ciphertexts: [Scheme.CanonicalCiphertext] = try ciphertexts.map { ciphertext in
let serializedCiphertext: SerializedCiphertext<Scheme.Scalar> = try ciphertext.native()
return try Ciphertext(
Expand Down Expand Up @@ -54,7 +54,7 @@ extension ProcessedDatabaseWithParameters {
/// - Parameter context: The context that was used to create processed database.
/// - Returns: The PIR parameters protobuf object.
/// - Throws: Error when the parameters cannot be represented as a protobuf object.
public func proto(context: Context<Scheme>) throws -> Apple_SwiftHomomorphicEncryption_Pir_V1_PirParameters {
public func proto(context: Scheme.Context) throws -> Apple_SwiftHomomorphicEncryption_Pir_V1_PirParameters {
let encryptionParameters = context.encryptionParameters
return try Apple_SwiftHomomorphicEncryption_Pir_V1_PirParameters.with { params in
params.encryptionParameters = try encryptionParameters.proto(scheme: Scheme.self)
Expand Down
2 changes: 1 addition & 1 deletion Sources/ApplicationProtobuf/PirConversionApi.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ extension Apple_SwiftHomomorphicEncryption_Api_Pir_V1_PIRResponse {
/// - Parameter context: Context to associate with the native type.
/// - Returns: The converted native type.
/// - Throws: Error upon invalid protobuf object.
public func native<Scheme: HeScheme>(context: Context<Scheme>) throws -> Response<Scheme> {
public func native<Scheme: HeScheme>(context: Scheme.Context) throws -> Response<Scheme> {
let ciphertexts: [[Scheme.CoeffCiphertext]] = try replies.map { reply in
let serializedCiphertexts: [SerializedCiphertext<Scheme.Scalar>] = try reply.native()
return try serializedCiphertexts.map { serialized in
Expand Down
2 changes: 1 addition & 1 deletion Sources/ApplicationProtobuf/PnnsConversion.swift
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ extension [Apple_SwiftHomomorphicEncryption_Pnns_V1_SerializedCiphertextMatrix]
/// Converts the native object into a protobuf object.
/// - Returns: The converted protobuf object.
/// - Throws: Error upon unsupported object.
public func native<Scheme: HeScheme>(context: Context<Scheme>) throws -> Query<Scheme> {
public func native<Scheme: HeScheme>(context: Scheme.Context) throws -> Query<Scheme> {
let matrices: [CiphertextMatrix<Scheme, Coeff>] = try map { matrix in
let native: SerializedCiphertextMatrix<Scheme.Scalar> = try matrix.native()
return try CiphertextMatrix(deserialize: native, context: context)
Expand Down
2 changes: 1 addition & 1 deletion Sources/ApplicationProtobuf/PnnsConversionApi.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ extension Apple_SwiftHomomorphicEncryption_Api_Pnns_V1_PNNSShardResponse {
/// - Parameter contexts: Contexts to associate with the native type; one context per plaintext modulus.
/// - Returns: The converted native type.
/// - Throws: Error upon invalid protobuf object.
public func native<Scheme: HeScheme>(contexts: [Context<Scheme>]) throws -> Response<Scheme> {
public func native<Scheme: HeScheme>(contexts: [Scheme.Context]) throws -> Response<Scheme> {
precondition(contexts.count == reply.count)
let matrices: [CiphertextMatrix<Scheme, Coeff>] = try zip(reply, contexts).map { matrix, context in
let serialized: SerializedCiphertextMatrix<Scheme.Scalar> = try matrix.native()
Expand Down
32 changes: 32 additions & 0 deletions Sources/HomomorphicEncryption/Array2d.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,38 @@ public struct Array2d<T: Equatable & AdditiveArithmetic & Sendable>: Equatable,
rowCount: rowCount,
columnCount: columnCount)
}

/// Provides scoped access to the underlying buffer storing the array's data.
///
/// Use this method when you need temporary read-only access to the array's contiguous storage.
/// The buffer pointer is only valid for the duration of the closure's execution.
///
/// - Parameter body: A closure that takes an `UnsafeBufferPointer` to the array's data.
/// The buffer pointer argument is valid only for the duration of the closure's execution.
/// - Returns: The return value of the `body` closure.
/// - Throws: Rethrows any error thrown by the `body` closure.
public func withUnsafeData<Return>(_ body: (UnsafeBufferPointer<T>) throws -> Return) rethrows -> Return {
try data.withUnsafeBufferPointer { pointer in
try body(pointer)
}
}

/// Provides scoped access to the underlying buffer storing the array's data for mutation.
///
/// Use this method when you need temporary read-write access to the array's contiguous storage.
/// The buffer pointer is only valid for the duration of the closure's execution.
///
/// - Parameter body: A closure that takes an `UnsafeMutableBufferPointer` to the array's data.
/// The buffer pointer argument is valid only for the duration of the closure's execution.
/// - Returns: The return value of the `body` closure.
/// - Throws: Rethrows any error thrown by the `body` closure.
public mutating func withUnsafeMutableData<Return>(_ body: (UnsafeMutableBufferPointer<T>) throws
-> Return) rethrows -> Return
{
try data.withUnsafeMutableBufferPointer { pointer in
try body(pointer)
}
}
}

extension Array2d {
Expand Down
2 changes: 1 addition & 1 deletion Sources/HomomorphicEncryption/Bfv/Bfv+Decrypt.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ extension Bfv {
let rnsTool = ciphertext.context.getRnsTool(moduliCount: dotProduct.moduli.count)
let plaintext = try rnsTool.scaleAndRound(poly: dotProduct, scalingFactor: scalingFactor)

return CoeffPlaintext(context: ciphertext.context, poly: plaintext)
return try CoeffPlaintext(context: ciphertext.context, poly: plaintext)
}

/// Calculates the number of least significant bits (LSBs) per polynomial that can be excluded
Expand Down
75 changes: 71 additions & 4 deletions Sources/HomomorphicEncryption/Bfv/Bfv+Encode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import ModularArithmetic

extension Bfv {
@inlinable
// swiftlint:disable:next missing_docs attributes
Expand All @@ -24,23 +26,23 @@ extension Bfv {

@inlinable
// swiftlint:disable:next missing_docs attributes
public static func encode(context: Context<Bfv<T>>, values: some Collection<Scalar>,
public static func encode(context: Context, values: some Collection<Scalar>,
format: EncodeFormat) throws -> CoeffPlaintext
{
try context.encode(values: values, format: format)
}

@inlinable
// swiftlint:disable:next missing_docs attributes
public static func encode(context: Context<Bfv<T>>, signedValues: some Collection<SignedScalar>,
public static func encode(context: Context, signedValues: some Collection<SignedScalar>,
format: EncodeFormat) throws -> CoeffPlaintext
{
try context.encode(signedValues: signedValues, format: format)
}

@inlinable
// swiftlint:disable:next missing_docs attributes
public static func encode(context: Context<Bfv<T>>, values: some Collection<Scalar>, format: EncodeFormat,
public static func encode(context: Context, values: some Collection<Scalar>, format: EncodeFormat,
moduliCount: Int?) throws -> EvalPlaintext
{
let coeffPlaintext = try Self.encode(context: context, values: values, format: format)
Expand All @@ -50,7 +52,7 @@ extension Bfv {
@inlinable
// swiftlint:disable:next missing_docs attributes
public static func encode(
context: Context<Bfv<T>>,
context: Context,
signedValues: some Collection<SignedScalar>,
format: EncodeFormat,
moduliCount: Int?) throws -> EvalPlaintext
Expand Down Expand Up @@ -82,4 +84,69 @@ extension Bfv {
public static func decodeEval(plaintext: EvalPlaintext, format: EncodeFormat) throws -> [SignedScalar] {
try plaintext.convertToCoeffFormat().decode(format: format)
}

/// Calculates the number of least significant bits (LSBs) per polynomial that can be excluded
/// from serialization of a single-modulus ciphertext, when decryption is performed immediately after
/// deserialization.
///
/// In BFV, the LSB bits of each polynomial may be excluded from the serialization,
/// since they are rarely used in decryption. This yields a smaller
/// serialization size, at the cost of a small chance of decryption
/// error.
/// - seealso: Section 5.2 of <https://eprint.iacr.org/2022/207.pdf>.
@inlinable
public static func skipLSBsForDecryption(for parameters: EncryptionParameters<Scalar>) -> [Int] {
let q0 = parameters.coefficientModuli[0]
let t = parameters.plaintextModulus
// Note, Appendix F of the paper claims the low `l' = floor(log2(q/t))` bits of
// a message are unused during decryption. This is off by one, due to
// also needing the MSB decimal bit for correct rounding.
// Concretely, let x=7, t=5, q=64. Then, floor(log2(q/t)) = 3.
// Decrypting `x` yields `round(x * t / q) = round(0.546875) = 1`,
// whereas decrypting `(x >> 3) >> 3) = 0` yields `round(0 * t / q) = 0`.
// Hence, we subtract one from the definition of `l'` compared to the paper.
//
// Also, Appendix F fails to address ciphertext error. If the error
// is less than q/4t, then we have the error introduced by the dropped
// bits be less than q/4t so we can correctly decrypt.
let lPrime = if q0 >= 2 * t {
(q0 / t).log2 - 3
} else {
0
}

// Then, we want the error introduced by dropping
// bits to be `<= q/4p` since it is additive with the
// ciphertext error. Set number of bits dropped
// in `b` to `floor(log(q/8p))`. Next, estimate
// how many bits to drop from a so that
// w.h.p., the introduced error is less
// than q/8p.
//
// The paper uses `z_score * sqrt(2N/9) * 2^{l_a} + 2^{l_b} < 2^{l'}`
// Setting `l_b = l' - 1`, this yields
// `z_score * sqrt(2N/9) * 2^{l_a} < 2^{l'-1}`, iff
// `log2(z_score * sqrt(2N/9)) + l_a < l' - 1`, iff
// `l_a < l' - 1 - log2(z_score * sqrt(2N/9))`, which is true for
// `l_a = floor(l' - 1 - log2(z_score * sqrt(2N/9)))`
// The paper uses z_score = 7; we use a larger z_score since we are decrypting N
// coefficients, rather than a single LWE coefficient. This yields a
// a per-coefficient decryption error `Pr(|x| > z_score)`, where `x ~ N(0, 1)`.
// This yields a < 2^-49.5 per-coefficient decryption error.
// By union bound, the message decryption error is
// `< 2^(log2(N) - 49.5) = 2^-36.5` for `N=8192`
//
// We also add a check: if we're only dropping at most one bit in `a`, then
// it is safer to drop that bit in `b` instead since the error's
// dependence on `b` is deterministic.
var poly0SkipLSBs = max(lPrime, 0)
let zScore = 8.0
let tmp = Int(zScore * (2.0 * Double(parameters.polyDegree) / 9.0).squareRoot())
var poly1SkipLSBs = lPrime - (tmp == 0 ? 0 : tmp.ceilLog2)
if poly1SkipLSBs <= 1 {
poly0SkipLSBs = max(lPrime + 1, 0)
poly1SkipLSBs = 0
}
return [poly0SkipLSBs, poly1SkipLSBs]
}
}
Loading