Skip to content

Commit d72868d

Browse files
authored
Add _HomomorphicEncryptionExtras (#226)
1 parent 4596544 commit d72868d

File tree

8 files changed

+204
-106
lines changed

8 files changed

+204
-106
lines changed

Package.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ let package = Package(
4040
.library(
4141
name: "HomomorphicEncryptionProtobuf",
4242
targets: ["HomomorphicEncryptionProtobuf"]),
43+
.library(
44+
name: "_HomomorphicEncryptionExtras",
45+
targets: ["_HomomorphicEncryptionExtras"]),
4346
.library(
4447
name: "PrivateInformationRetrieval",
4548
targets: ["PrivateInformationRetrieval"]),
@@ -96,6 +99,10 @@ let package = Package(
9699
.product(name: "SwiftProtobuf", package: "swift-protobuf")],
97100
exclude: ["generated/README.md"],
98101
swiftSettings: librarySettings),
102+
.target(
103+
name: "_HomomorphicEncryptionExtras",
104+
dependencies: ["HomomorphicEncryption"],
105+
swiftSettings: librarySettings),
99106
.target(
100107
name: "PrivateInformationRetrieval",
101108
dependencies: ["HomomorphicEncryption",
@@ -114,6 +121,7 @@ let package = Package(
114121
dependencies: [
115122
.product(name: "Algorithms", package: "swift-algorithms"),
116123
"HomomorphicEncryption",
124+
"_HomomorphicEncryptionExtras",
117125
],
118126
swiftSettings: librarySettings),
119127
.target(
@@ -128,6 +136,7 @@ let package = Package(
128136
name: "_TestUtilities",
129137
dependencies: [
130138
"HomomorphicEncryption",
139+
"_HomomorphicEncryptionExtras",
131140
"PrivateInformationRetrieval",
132141
"PrivateNearestNeighborSearch",
133142
.product(name: "Numerics", package: "swift-numerics"),

Sources/HomomorphicEncryption/Ciphertext.swift

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -240,24 +240,6 @@ public struct Ciphertext<Scheme: HeScheme, Format: PolyFormat>: Equatable, Senda
240240
try Scheme.rotateColumns(of: &self, by: step, using: evaluationKey)
241241
}
242242

243-
/// Rotates the columns of the ciphertext by combining multiple rotation steps corresponding to Galois elements
244-
/// available in the `evaluationKey`.
245-
///
246-
/// - Parameters:
247-
/// - step: Number of slots to rotate. Negative values indicate a left rotation, and positive values indicate a
248-
/// right rotation. Must have absolute value in `[1, N / 2 - 1]` where `N` is the RLWE ring dimension, given by
249-
/// ``EncryptionParameters/polyDegree``.
250-
/// - evaluationKey: Evaluation key to use in the HE computation. Must contain Galois elements which can be
251-
/// combined for the desired rotation step.
252-
/// - Throws: Error upon failure to rotate ciphertext's columns.
253-
/// - seealso: `HeScheme/rotateColumnsMultiStep(of:by:using:)` for an alternative API and more information.
254-
@inlinable
255-
package mutating func rotateColumnsMultiStep(by step: Int, using evaluationKey: EvaluationKey<Scheme>) throws
256-
where Format == Scheme.CanonicalCiphertextFormat
257-
{
258-
try Scheme.rotateColumnsMultiStep(of: &self, by: step, using: evaluationKey)
259-
}
260-
261243
/// Swaps the rows of a ciphertext.
262244
///
263245
/// A plaintext in ``EncodeFormat/simd`` format can be viewed a `2 x (N / 2)` matrix of coefficients.

Sources/HomomorphicEncryption/HeScheme.swift

Lines changed: 0 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1285,88 +1285,6 @@ extension HeScheme {
12851285
try applyGalois(ciphertext: &ciphertext, element: element, using: evaluationKey)
12861286
}
12871287

1288-
@inlinable
1289-
package static func rotateColumnsMultiStep(
1290-
of ciphertext: inout CanonicalCiphertext,
1291-
by step: Int,
1292-
using evaluationKey: EvaluationKey) throws
1293-
{
1294-
if step == 0 {
1295-
return
1296-
}
1297-
1298-
guard let galoisKey = evaluationKey.galoisKey else {
1299-
throw HeError.missingGaloisKey
1300-
}
1301-
1302-
// Short-circuit to single rotation if possible.
1303-
let degree = ciphertext.degree
1304-
let galoisElement = try GaloisElement.rotatingColumns(by: step, degree: degree)
1305-
if galoisKey.keys.keys.contains(galoisElement) {
1306-
try ciphertext.rotateColumns(by: step, using: evaluationKey)
1307-
return
1308-
}
1309-
1310-
let galoisElements = Array(galoisKey.keys.keys)
1311-
let steps = GaloisElement.stepsFor(elements: galoisElements, degree: degree).values.compactMap(\.self)
1312-
1313-
let positiveStep = if step < 0 {
1314-
step + degree / 2
1315-
} else {
1316-
step
1317-
}
1318-
1319-
let plan = try GaloisElement.planMultiStep(supportedSteps: steps, step: positiveStep, degree: degree)
1320-
guard let plan else {
1321-
throw HeError.invalidRotationStep(step: step, degree: degree)
1322-
}
1323-
for (step, count) in plan {
1324-
try (0..<count).forEach { _ in try ciphertext.rotateColumns(by: step, using: evaluationKey) }
1325-
}
1326-
}
1327-
1328-
@inlinable
1329-
package static func rotateColumnsMultiStepAsync(
1330-
of ciphertext: inout CanonicalCiphertext,
1331-
by step: Int,
1332-
using evaluationKey: EvaluationKey) async throws
1333-
{
1334-
if step == 0 {
1335-
return
1336-
}
1337-
1338-
guard let galoisKey = evaluationKey.galoisKey else {
1339-
throw HeError.missingGaloisKey
1340-
}
1341-
1342-
// Short-circuit to single rotation if possible.
1343-
let degree = ciphertext.degree
1344-
let galoisElement = try GaloisElement.rotatingColumns(by: step, degree: degree)
1345-
if galoisKey.keys.keys.contains(galoisElement) {
1346-
try await rotateColumnsAsync(of: &ciphertext, by: step, using: evaluationKey)
1347-
return
1348-
}
1349-
1350-
let galoisElements = Array(galoisKey.keys.keys)
1351-
let steps = GaloisElement.stepsFor(elements: galoisElements, degree: degree).values.compactMap(\.self)
1352-
1353-
let positiveStep = if step < 0 {
1354-
step + degree / 2
1355-
} else {
1356-
step
1357-
}
1358-
1359-
let plan = try GaloisElement.planMultiStep(supportedSteps: steps, step: positiveStep, degree: degree)
1360-
guard let plan else {
1361-
throw HeError.invalidRotationStep(step: step, degree: degree)
1362-
}
1363-
for (step, count) in plan {
1364-
for _ in 0..<count {
1365-
try await rotateColumnsAsync(of: &ciphertext, by: step, using: evaluationKey)
1366-
}
1367-
}
1368-
}
1369-
13701288
@inlinable
13711289
// swiftlint:disable:next missing_docs attributes
13721290
public static func modSwitchDownToSingle(_ ciphertext: inout CanonicalCiphertext) throws {

Sources/HomomorphicEncryption/Keys.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ extension SecretKey: PolyCollection {
5050
/// - seealso: ``HeScheme/relinearize(_:using:)`` and ``HeScheme/applyGalois(ciphertext:element:using:)`` for more
5151
/// details.
5252
@usableFromInline
53-
struct KeySwitchKey<Scheme: HeScheme>: Equatable, Sendable {
53+
package struct KeySwitchKey<Scheme: HeScheme>: Equatable, Sendable {
5454
/// The context used for key-switching operations.
5555
@usableFromInline let context: Context<Scheme>
5656
/// The ciphertexts of the key-switching key.
@@ -73,7 +73,7 @@ extension KeySwitchKey: PolyCollection {
7373
}
7474

7575
@usableFromInline
76-
struct RelinearizationKey<Scheme: HeScheme>: Equatable, Sendable {
76+
package struct RelinearizationKey<Scheme: HeScheme>: Equatable, Sendable {
7777
@usableFromInline let keySwitchKey: KeySwitchKey<Scheme>
7878

7979
@inlinable
@@ -92,8 +92,8 @@ extension RelinearizationKey: PolyCollection {
9292
}
9393

9494
@usableFromInline
95-
struct GaloisKey<Scheme: HeScheme>: Equatable, Sendable {
96-
@usableFromInline let keys: [Int: KeySwitchKey<Scheme>]
95+
package struct GaloisKey<Scheme: HeScheme>: Equatable, Sendable {
96+
@usableFromInline package let keys: [Int: KeySwitchKey<Scheme>]
9797

9898
@inlinable
9999
init(keys: [Int: KeySwitchKey<Scheme>]) {
@@ -118,8 +118,8 @@ extension GaloisKey: PolyCollection {
118118
///
119119
/// Associated with a ``SecretKey``.
120120
public struct EvaluationKey<Scheme: HeScheme>: Equatable, Sendable {
121-
@usableFromInline let galoisKey: GaloisKey<Scheme>?
122-
@usableFromInline let relinearizationKey: RelinearizationKey<Scheme>?
121+
@usableFromInline package let galoisKey: GaloisKey<Scheme>?
122+
@usableFromInline package let relinearizationKey: RelinearizationKey<Scheme>?
123123

124124
/// Returns the configuration for the evaluation key.
125125
public var config: EvaluationKeyConfig {

Sources/PrivateNearestNeighborSearch/MatrixMultiplication.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
import _HomomorphicEncryptionExtras
1516
import Algorithms
1617
import Foundation
1718
import HomomorphicEncryption
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright 2024-2025 Apple Inc. and the Swift Homomorphic Encryption project authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import HomomorphicEncryption
16+
17+
extension Ciphertext {
18+
/// Rotates the columns of the ciphertext by combining multiple rotation steps corresponding to Galois elements
19+
/// available in the `evaluationKey`.
20+
///
21+
/// - Parameters:
22+
/// - step: Number of slots to rotate. Negative values indicate a left rotation, and positive values indicate a
23+
/// right rotation. Must have absolute value in `[1, N / 2 - 1]` where `N` is the RLWE ring dimension, given by
24+
/// `EncryptionParameters/polyDegree`.
25+
/// - evaluationKey: Evaluation key to use in the HE computation. Must contain Galois elements which can be
26+
/// combined for the desired rotation step.
27+
/// - Throws: Error upon failure to rotate ciphertext's columns.
28+
/// - seealso: `HeScheme/_rotateColumnsMultiStep(of:by:using:)` for an alternative API and more information.
29+
@inlinable
30+
public mutating func rotateColumnsMultiStep(by step: Int, using evaluationKey: EvaluationKey<Scheme>) throws
31+
where Format == Scheme.CanonicalCiphertextFormat
32+
{
33+
try Scheme.rotateColumnsMultiStep(of: &self, by: step, using: evaluationKey)
34+
}
35+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// Copyright 2024-2025 Apple Inc. and the Swift Homomorphic Encryption project authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import HomomorphicEncryption
16+
17+
extension HeScheme {
18+
/// Rotates the columns of a ciphertext throw multiple steps in async way.
19+
/// - seealso: `HeScheme/rotateColumnsAsync(of:by:using:)` for the single-step API.
20+
@inlinable
21+
public static func rotateColumnsMultiStepAsync(
22+
of ciphertext: inout CanonicalCiphertext,
23+
by step: Int,
24+
using evaluationKey: EvaluationKey) async throws
25+
{
26+
if step == 0 {
27+
return
28+
}
29+
30+
guard let galoisKey = evaluationKey.galoisKey else {
31+
throw HeError.missingGaloisKey
32+
}
33+
34+
// Short-circuit to single rotation if possible.
35+
let degree = ciphertext.degree
36+
let galoisElement = try GaloisElement.rotatingColumns(by: step, degree: degree)
37+
if galoisKey.keys.keys.contains(galoisElement) {
38+
try await rotateColumnsAsync(of: &ciphertext, by: step, using: evaluationKey)
39+
return
40+
}
41+
42+
let galoisElements = Array(galoisKey.keys.keys)
43+
let steps = GaloisElement.stepsFor(elements: galoisElements, degree: degree).values.compactMap(\.self)
44+
45+
let positiveStep = if step < 0 {
46+
step + degree / 2
47+
} else {
48+
step
49+
}
50+
51+
let plan = try GaloisElement.planMultiStep(supportedSteps: steps, step: positiveStep, degree: degree)
52+
guard let plan else {
53+
throw HeError.invalidRotationStep(step: step, degree: degree)
54+
}
55+
for (step, count) in plan {
56+
for _ in 0..<count {
57+
try await rotateColumnsAsync(of: &ciphertext, by: step, using: evaluationKey)
58+
}
59+
}
60+
}
61+
62+
/// Rotates the columns of a ciphertext through one or more rotations.
63+
/// - seealso: `HeScheme/rotateColumns(of:by:using:)` for the single-step API.
64+
@inlinable
65+
public static func rotateColumnsMultiStep(
66+
of ciphertext: inout CanonicalCiphertext,
67+
by step: Int,
68+
using evaluationKey: EvaluationKey) throws
69+
{
70+
if step == 0 {
71+
return
72+
}
73+
74+
guard let galoisKey = evaluationKey.galoisKey else {
75+
throw HeError.missingGaloisKey
76+
}
77+
78+
// Short-circuit to single rotation if possible.
79+
let degree = ciphertext.degree
80+
let galoisElement = try GaloisElement.rotatingColumns(by: step, degree: degree)
81+
if galoisKey.keys.keys.contains(galoisElement) {
82+
try ciphertext.rotateColumns(by: step, using: evaluationKey)
83+
return
84+
}
85+
86+
let galoisElements = Array(galoisKey.keys.keys)
87+
let steps = GaloisElement.stepsFor(elements: galoisElements, degree: degree).values.compactMap(\.self)
88+
89+
let positiveStep = if step < 0 {
90+
step + degree / 2
91+
} else {
92+
step
93+
}
94+
95+
let plan = try GaloisElement.planMultiStep(supportedSteps: steps, step: positiveStep, degree: degree)
96+
guard let plan else {
97+
throw HeError.invalidRotationStep(step: step, degree: degree)
98+
}
99+
for (step, count) in plan {
100+
try (0..<count).forEach { _ in try ciphertext.rotateColumns(by: step, using: evaluationKey) }
101+
}
102+
}
103+
104+
/// Sum up an array of ciphertexts after rotate their columns one-by-one.
105+
///
106+
/// The i-th (starting from 0) ciphertext is rotated by i * `step` steps before adding up.
107+
/// - Parameters:
108+
/// - ciphertexts: ciphertexts to be added up.
109+
/// - step: the rotation steps for each ciphertext.
110+
/// - evaluationKey: the evaluation key for rotation.
111+
/// - Throws: Error upon failure to compute the inverse.
112+
@inlinable
113+
public static func rotateColumnsAndSumAsync(
114+
_ ciphertexts: consuming [CanonicalCiphertext],
115+
by step: Int,
116+
using evaluationKey: EvaluationKey) async throws -> CanonicalCiphertext
117+
{
118+
guard var accumulator = ciphertexts.popLast() else {
119+
preconditionFailure("No ciphertexts to sum up.")
120+
}
121+
if ciphertexts.isEmpty {
122+
return accumulator
123+
}
124+
125+
for ciphertext in ciphertexts.reversed() {
126+
try await rotateColumnsMultiStepAsync(
127+
of: &accumulator,
128+
by: step,
129+
using: evaluationKey)
130+
try await addAssignAsync(&accumulator, ciphertext)
131+
}
132+
return accumulator
133+
}
134+
135+
/// Sum up two ciphertexts after swap the row of second one.
136+
///
137+
/// - Parameters:
138+
/// - ciphertexts: ciphertexts to be added up.
139+
/// - step: the rotation steps for each ciphertext.
140+
/// - evaluationKey: the evaluation key for rotation.
141+
/// - Throws: Error upon failure to compute the inverse.
142+
@inlinable
143+
public static func swapRowsAndAddAsync(
144+
swapping ciphertext0: consuming CanonicalCiphertext,
145+
addingTo ciphertext1: consuming CanonicalCiphertext,
146+
using evaluationKey: EvaluationKey) async throws -> CanonicalCiphertext
147+
{
148+
try await swapRowsAsync(of: &ciphertext0, using: evaluationKey)
149+
try await addAssignAsync(&ciphertext0, ciphertext1)
150+
return ciphertext0
151+
}
152+
}

Sources/_TestUtilities/HeApiTestUtils.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
import _HomomorphicEncryptionExtras
1516
import HomomorphicEncryption
1617
import ModularArithmetic
1718
import Testing

0 commit comments

Comments
 (0)