diff --git a/Package.swift b/Package.swift index 71bd0781..44f7af2d 100644 --- a/Package.swift +++ b/Package.swift @@ -40,6 +40,9 @@ let package = Package( .library( name: "HomomorphicEncryptionProtobuf", targets: ["HomomorphicEncryptionProtobuf"]), + .library( + name: "_HomomorphicEncryptionExtras", + targets: ["_HomomorphicEncryptionExtras"]), .library( name: "PrivateInformationRetrieval", targets: ["PrivateInformationRetrieval"]), @@ -96,6 +99,10 @@ let package = Package( .product(name: "SwiftProtobuf", package: "swift-protobuf")], exclude: ["generated/README.md"], swiftSettings: librarySettings), + .target( + name: "_HomomorphicEncryptionExtras", + dependencies: ["HomomorphicEncryption"], + swiftSettings: librarySettings), .target( name: "PrivateInformationRetrieval", dependencies: ["HomomorphicEncryption", @@ -114,6 +121,7 @@ let package = Package( dependencies: [ .product(name: "Algorithms", package: "swift-algorithms"), "HomomorphicEncryption", + "_HomomorphicEncryptionExtras", ], swiftSettings: librarySettings), .target( @@ -128,6 +136,7 @@ let package = Package( name: "_TestUtilities", dependencies: [ "HomomorphicEncryption", + "_HomomorphicEncryptionExtras", "PrivateInformationRetrieval", "PrivateNearestNeighborSearch", .product(name: "Numerics", package: "swift-numerics"), diff --git a/Sources/HomomorphicEncryption/Ciphertext.swift b/Sources/HomomorphicEncryption/Ciphertext.swift index ce520640..9628ad7c 100644 --- a/Sources/HomomorphicEncryption/Ciphertext.swift +++ b/Sources/HomomorphicEncryption/Ciphertext.swift @@ -240,24 +240,6 @@ public struct Ciphertext: Equatable, Senda try Scheme.rotateColumns(of: &self, by: step, using: evaluationKey) } - /// Rotates the columns of the ciphertext by combining multiple rotation steps corresponding to Galois elements - /// available in the `evaluationKey`. - /// - /// - Parameters: - /// - step: Number of slots to rotate. Negative values indicate a left rotation, and positive values indicate a - /// right rotation. Must have absolute value in `[1, N / 2 - 1]` where `N` is the RLWE ring dimension, given by - /// ``EncryptionParameters/polyDegree``. - /// - evaluationKey: Evaluation key to use in the HE computation. Must contain Galois elements which can be - /// combined for the desired rotation step. - /// - Throws: Error upon failure to rotate ciphertext's columns. - /// - seealso: `HeScheme/rotateColumnsMultiStep(of:by:using:)` for an alternative API and more information. - @inlinable - package mutating func rotateColumnsMultiStep(by step: Int, using evaluationKey: EvaluationKey) throws - where Format == Scheme.CanonicalCiphertextFormat - { - try Scheme.rotateColumnsMultiStep(of: &self, by: step, using: evaluationKey) - } - /// Swaps the rows of a ciphertext. /// /// A plaintext in ``EncodeFormat/simd`` format can be viewed a `2 x (N / 2)` matrix of coefficients. diff --git a/Sources/HomomorphicEncryption/HeScheme.swift b/Sources/HomomorphicEncryption/HeScheme.swift index afce3aff..9b2b1be7 100644 --- a/Sources/HomomorphicEncryption/HeScheme.swift +++ b/Sources/HomomorphicEncryption/HeScheme.swift @@ -1285,88 +1285,6 @@ extension HeScheme { try applyGalois(ciphertext: &ciphertext, element: element, using: evaluationKey) } - @inlinable - package static func rotateColumnsMultiStep( - of ciphertext: inout CanonicalCiphertext, - by step: Int, - using evaluationKey: EvaluationKey) throws - { - if step == 0 { - return - } - - guard let galoisKey = evaluationKey.galoisKey else { - throw HeError.missingGaloisKey - } - - // Short-circuit to single rotation if possible. - let degree = ciphertext.degree - let galoisElement = try GaloisElement.rotatingColumns(by: step, degree: degree) - if galoisKey.keys.keys.contains(galoisElement) { - try ciphertext.rotateColumns(by: step, using: evaluationKey) - return - } - - let galoisElements = Array(galoisKey.keys.keys) - let steps = GaloisElement.stepsFor(elements: galoisElements, degree: degree).values.compactMap(\.self) - - let positiveStep = if step < 0 { - step + degree / 2 - } else { - step - } - - let plan = try GaloisElement.planMultiStep(supportedSteps: steps, step: positiveStep, degree: degree) - guard let plan else { - throw HeError.invalidRotationStep(step: step, degree: degree) - } - for (step, count) in plan { - try (0..: Equatable, Sendable { +package struct KeySwitchKey: Equatable, Sendable { /// The context used for key-switching operations. @usableFromInline let context: Context /// The ciphertexts of the key-switching key. @@ -73,7 +73,7 @@ extension KeySwitchKey: PolyCollection { } @usableFromInline -struct RelinearizationKey: Equatable, Sendable { +package struct RelinearizationKey: Equatable, Sendable { @usableFromInline let keySwitchKey: KeySwitchKey @inlinable @@ -92,8 +92,8 @@ extension RelinearizationKey: PolyCollection { } @usableFromInline -struct GaloisKey: Equatable, Sendable { - @usableFromInline let keys: [Int: KeySwitchKey] +package struct GaloisKey: Equatable, Sendable { + @usableFromInline package let keys: [Int: KeySwitchKey] @inlinable init(keys: [Int: KeySwitchKey]) { @@ -118,8 +118,8 @@ extension GaloisKey: PolyCollection { /// /// Associated with a ``SecretKey``. public struct EvaluationKey: Equatable, Sendable { - @usableFromInline let galoisKey: GaloisKey? - @usableFromInline let relinearizationKey: RelinearizationKey? + @usableFromInline package let galoisKey: GaloisKey? + @usableFromInline package let relinearizationKey: RelinearizationKey? /// Returns the configuration for the evaluation key. public var config: EvaluationKeyConfig { diff --git a/Sources/PrivateNearestNeighborSearch/MatrixMultiplication.swift b/Sources/PrivateNearestNeighborSearch/MatrixMultiplication.swift index 04518b52..7eb711ce 100644 --- a/Sources/PrivateNearestNeighborSearch/MatrixMultiplication.swift +++ b/Sources/PrivateNearestNeighborSearch/MatrixMultiplication.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import _HomomorphicEncryptionExtras import Algorithms import Foundation import HomomorphicEncryption diff --git a/Sources/_HomomorphicEncryptionExtras/Ciphertext.swift b/Sources/_HomomorphicEncryptionExtras/Ciphertext.swift new file mode 100644 index 00000000..3952b149 --- /dev/null +++ b/Sources/_HomomorphicEncryptionExtras/Ciphertext.swift @@ -0,0 +1,35 @@ +// Copyright 2024-2025 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. + +import HomomorphicEncryption + +extension Ciphertext { + /// Rotates the columns of the ciphertext by combining multiple rotation steps corresponding to Galois elements + /// available in the `evaluationKey`. + /// + /// - Parameters: + /// - step: Number of slots to rotate. Negative values indicate a left rotation, and positive values indicate a + /// right rotation. Must have absolute value in `[1, N / 2 - 1]` where `N` is the RLWE ring dimension, given by + /// `EncryptionParameters/polyDegree`. + /// - evaluationKey: Evaluation key to use in the HE computation. Must contain Galois elements which can be + /// combined for the desired rotation step. + /// - Throws: Error upon failure to rotate ciphertext's columns. + /// - seealso: `HeScheme/_rotateColumnsMultiStep(of:by:using:)` for an alternative API and more information. + @inlinable + public mutating func rotateColumnsMultiStep(by step: Int, using evaluationKey: EvaluationKey) throws + where Format == Scheme.CanonicalCiphertextFormat + { + try Scheme.rotateColumnsMultiStep(of: &self, by: step, using: evaluationKey) + } +} diff --git a/Sources/_HomomorphicEncryptionExtras/HeScheme.swift b/Sources/_HomomorphicEncryptionExtras/HeScheme.swift new file mode 100644 index 00000000..5b87165f --- /dev/null +++ b/Sources/_HomomorphicEncryptionExtras/HeScheme.swift @@ -0,0 +1,152 @@ +// Copyright 2024-2025 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. + +import HomomorphicEncryption + +extension HeScheme { + /// Rotates the columns of a ciphertext throw multiple steps in async way. + /// - seealso: `HeScheme/rotateColumnsAsync(of:by:using:)` for the single-step API. + @inlinable + public static func rotateColumnsMultiStepAsync( + of ciphertext: inout CanonicalCiphertext, + by step: Int, + using evaluationKey: EvaluationKey) async throws + { + if step == 0 { + return + } + + guard let galoisKey = evaluationKey.galoisKey else { + throw HeError.missingGaloisKey + } + + // Short-circuit to single rotation if possible. + let degree = ciphertext.degree + let galoisElement = try GaloisElement.rotatingColumns(by: step, degree: degree) + if galoisKey.keys.keys.contains(galoisElement) { + try await rotateColumnsAsync(of: &ciphertext, by: step, using: evaluationKey) + return + } + + let galoisElements = Array(galoisKey.keys.keys) + let steps = GaloisElement.stepsFor(elements: galoisElements, degree: degree).values.compactMap(\.self) + + let positiveStep = if step < 0 { + step + degree / 2 + } else { + step + } + + let plan = try GaloisElement.planMultiStep(supportedSteps: steps, step: positiveStep, degree: degree) + guard let plan else { + throw HeError.invalidRotationStep(step: step, degree: degree) + } + for (step, count) in plan { + for _ in 0.. CanonicalCiphertext + { + guard var accumulator = ciphertexts.popLast() else { + preconditionFailure("No ciphertexts to sum up.") + } + if ciphertexts.isEmpty { + return accumulator + } + + for ciphertext in ciphertexts.reversed() { + try await rotateColumnsMultiStepAsync( + of: &accumulator, + by: step, + using: evaluationKey) + try await addAssignAsync(&accumulator, ciphertext) + } + return accumulator + } + + /// Sum up two ciphertexts after swap the row of second one. + /// + /// - Parameters: + /// - ciphertexts: ciphertexts to be added up. + /// - step: the rotation steps for each ciphertext. + /// - evaluationKey: the evaluation key for rotation. + /// - Throws: Error upon failure to compute the inverse. + @inlinable + public static func swapRowsAndAddAsync( + swapping ciphertext0: consuming CanonicalCiphertext, + addingTo ciphertext1: consuming CanonicalCiphertext, + using evaluationKey: EvaluationKey) async throws -> CanonicalCiphertext + { + try await swapRowsAsync(of: &ciphertext0, using: evaluationKey) + try await addAssignAsync(&ciphertext0, ciphertext1) + return ciphertext0 + } +} diff --git a/Sources/_TestUtilities/HeApiTestUtils.swift b/Sources/_TestUtilities/HeApiTestUtils.swift index d6d822f6..d8eff79b 100644 --- a/Sources/_TestUtilities/HeApiTestUtils.swift +++ b/Sources/_TestUtilities/HeApiTestUtils.swift @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import _HomomorphicEncryptionExtras import HomomorphicEncryption import ModularArithmetic import Testing