Skip to content

Commit efb4833

Browse files
authored
Replace Ciphertext/multiplyInversePowerOfX with Ciphertext/multiplyPowerOfX (#249)
1 parent 9146fcf commit efb4833

File tree

11 files changed

+175
-65
lines changed

11 files changed

+175
-65
lines changed

Benchmarks/RlweBenchmark/RlweBenchmark.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -398,14 +398,14 @@ func ciphertextMultiplyBenchmark<Scheme: HeScheme>(_: Scheme.Type) -> () -> Void
398398
}
399399
}
400400

401-
func ciphertextMultiplyInversePowerOfXBenchmark<Scheme: HeScheme>(_: Scheme.Type) -> () -> Void {
401+
func ciphertextMultiplyPowerOfXBenchmark<Scheme: HeScheme>(_: Scheme.Type) -> () -> Void {
402402
{
403-
benchmark("CiphertextMultiplyInversePowerOfX", Scheme.self) { benchmark in
403+
benchmark("CiphertextMultiplyPowerOfX", Scheme.self) { benchmark in
404404
let benchmarkContext: RlweBenchmarkContext<Scheme> = try StaticRlweBenchmarkContext.getBenchmarkContext()
405405
benchmark.startMeasurement()
406406
var ciphertext = try benchmarkContext.ciphertext.convertToCoeffFormat()
407407
for _ in benchmark.scaledIterations {
408-
try blackHole(ciphertext.multiplyInversePowerOfX(power: ciphertext.context.degree / 2))
408+
try blackHole(ciphertext.multiplyPowerOfX(power: -ciphertext.context.degree / 2))
409409
}
410410
}
411411
}
@@ -765,8 +765,8 @@ nonisolated(unsafe) let benchmarks: () -> Void = {
765765
ciphertextSubtractBenchmark(Bfv<UInt32>.self)()
766766
ciphertextSubtractBenchmark(Bfv<UInt64>.self)()
767767

768-
ciphertextMultiplyInversePowerOfXBenchmark(Bfv<UInt32>.self)()
769-
ciphertextMultiplyInversePowerOfXBenchmark(Bfv<UInt64>.self)()
768+
ciphertextMultiplyPowerOfXBenchmark(Bfv<UInt32>.self)()
769+
ciphertextMultiplyPowerOfXBenchmark(Bfv<UInt64>.self)()
770770

771771
ciphertextMultiplyBenchmark(Bfv<UInt32>.self)()
772772
ciphertextMultiplyBenchmark(Bfv<UInt64>.self)()

Sources/HomomorphicEncryption/Array2d.swift

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -183,17 +183,27 @@ extension Array2d {
183183
}
184184

185185
extension Array2d {
186-
// rotate every `range` elements left by `step` elements
186+
/// Rotate columns.
187+
/// - Parameter step: Negative step indicates a left rotation. Positive step indicates a right rotation.
188+
/// - Warning: L:eaks `step` through timing.
187189
@inlinable
188-
mutating func rotate(range: Int, step: Int) throws {
189-
guard columnCount.isMultiple(of: range) else {
190-
throw HeError.invalidRotationParameter(range: range, columnCount: data.count)
190+
mutating func rotateColumns(by step: Int) throws {
191+
let effectiveStep = step.toRemainder(columnCount, variableTime: true)
192+
if effectiveStep == 0 {
193+
return
191194
}
192-
193-
let effectiveStep = step.toRemainder(range, variableTime: true)
194-
for index in stride(from: 0, to: data.count, by: range) {
195-
let replacement = data[index + effectiveStep..<index + range] + data[index..<index + effectiveStep]
196-
data.replaceSubrange(index..<index + range, with: replacement)
195+
if effectiveStep < 0 {
196+
for index in stride(from: 0, to: data.count, by: columnCount) {
197+
let replacement = data[index - effectiveStep..<index + columnCount] +
198+
data[index..<index - effectiveStep]
199+
data.replaceSubrange(index..<index + columnCount, with: replacement)
200+
}
201+
} else {
202+
for index in stride(from: 0, to: data.count, by: columnCount) {
203+
let cutoff = index + columnCount - effectiveStep
204+
let replacement = data[cutoff..<index + columnCount] + data[index..<cutoff]
205+
data.replaceSubrange(index..<index + columnCount, with: replacement)
206+
}
197207
}
198208
}
199209

Sources/HomomorphicEncryption/Ciphertext.swift

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -499,14 +499,13 @@ extension Ciphertext {
499499
}
500500

501501
extension Ciphertext where Format == Coeff {
502-
/// Computes `ciphertext * x^{-power}`.
502+
/// Computes `ciphertext * x^{power}`.
503503
///
504-
/// - Parameter power: Power in the monomial; must be positive.
505-
/// - Throws: Error upon failure to compute the inverse.
504+
/// - Parameter power: Power in the monomial
505+
/// - Throws: Error upon failure to compute.
506506
@inlinable
507-
public mutating func multiplyInversePowerOfX(power: Int) throws {
508-
precondition(power >= 0)
509-
try Scheme.multiplyInversePowerOfX(&self, power: power)
507+
public mutating func multiplyPowerOfX(power: Int) throws {
508+
try Scheme.multiplyPowerOfX(&self, power: power)
510509
}
511510
}
512511

Sources/HomomorphicEncryption/HeScheme.swift

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -993,17 +993,17 @@ public protocol HeScheme: Sendable {
993993
static func noiseBudgetEval(of ciphertext: EvalCiphertext, using secretKey: SecretKey, variableTime: Bool) throws
994994
-> Double
995995

996-
/// Computes `ciphertext * x^{-power}`.
996+
/// Computes `ciphertext * x^{power}`.
997997
///
998998
/// - Parameters:
999999
/// - ciphertext: ciphertext to be multiplied.
1000-
/// - power: Power in the monomial; must be positive.
1001-
/// - Throws: Error upon failure to compute the inverse.
1002-
/// - seealso: ``HeScheme/multiplyInversePowerOfXAsync(_:power:)`` for an async version of this API
1003-
static func multiplyInversePowerOfX(_ ciphertext: inout CoeffCiphertext, power: Int) throws
1000+
/// - power: Power in the monomial.
1001+
/// - Throws: Error upon failure to compute.
1002+
/// - seealso: ``HeScheme/multiplyPowerOfXAsync(_:power:)`` for an async version of this API.
1003+
static func multiplyPowerOfX(_ ciphertext: inout CoeffCiphertext, power: Int) throws
10041004

1005-
/// The async version of ``HeScheme/multiplyInversePowerOfX(_:power:)``.
1006-
static func multiplyInversePowerOfXAsync(_ ciphertext: inout CoeffCiphertext, power: Int) async throws
1005+
/// The async version of ``HeScheme/multiplyPowerOfX(_:power:)``.
1006+
static func multiplyPowerOfXAsync(_ ciphertext: inout CoeffCiphertext, power: Int) async throws
10071007
}
10081008

10091009
/// Codify different HE schemes.
@@ -1021,10 +1021,9 @@ public enum HeCryptoSystem: String {
10211021
extension HeScheme {
10221022
@inlinable
10231023
// swiftlint:disable:next missing_docs attributes
1024-
public static func multiplyInversePowerOfX(_ ciphertext: inout CoeffCiphertext, power: Int) throws {
1025-
precondition(power >= 0)
1024+
public static func multiplyPowerOfX(_ ciphertext: inout CoeffCiphertext, power: Int) throws {
10261025
for index in ciphertext.polys.indices {
1027-
try ciphertext.polys[index].multiplyInversePowerOfX(power)
1026+
try ciphertext.polys[index].multiplyPowerOfX(power)
10281027
}
10291028
}
10301029
}

Sources/HomomorphicEncryption/HeSchemeAsync.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,8 @@ extension HeScheme {
221221
}
222222

223223
@inlinable
224-
public static func multiplyInversePowerOfXAsync(_ ciphertext: inout CoeffCiphertext, power: Int) async throws {
225-
try multiplyInversePowerOfX(&ciphertext, power: power)
224+
public static func multiplyPowerOfXAsync(_ ciphertext: inout CoeffCiphertext, power: Int) async throws {
225+
try multiplyPowerOfX(&ciphertext, power: power)
226226
}
227227
// swiftlint:enable missing_docs
228228
}

Sources/HomomorphicEncryption/PolyRq/PolyRq.swift

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -408,22 +408,28 @@ extension PolyRq where F == Coeff {
408408

409409
extension PolyRq where F == Coeff {
410410
@inlinable
411-
mutating func multiplyInversePowerOfX(_ power: Int) throws {
412-
precondition(power >= 0)
413-
let effectiveStep = power % (degree &<< 1)
414-
if effectiveStep == 0 {
415-
return
411+
mutating func multiplyPowerOfX(_ power: Int) throws {
412+
// Calculate effective step once, handling both positive and negative powers
413+
let twiceDegree = degree &<< 1
414+
let absEffectiveStep = abs(power) % twiceDegree
415+
416+
if absEffectiveStep == 0 {
417+
return // No change needed for powers that are multiples of 2*degree
418+
}
419+
420+
// Determine rotation direction and effective step based on power sign
421+
let rotationStep = power < 0 ? -absEffectiveStep : absEffectiveStep
422+
try data.rotateColumns(by: rotationStep % degree)
423+
424+
let negateColumns = switch (power < 0, absEffectiveStep < degree) {
425+
case (true, true): (degree &- absEffectiveStep)..<degree
426+
case (true, false): 0..<(twiceDegree &- absEffectiveStep)
427+
case (false, true): 0..<absEffectiveStep
428+
case (false, false): (absEffectiveStep &- degree)..<degree
416429
}
417-
try data.rotate(range: degree, step: effectiveStep)
418430
for (rowIndex, modulus) in moduli.enumerated() {
419-
if effectiveStep < degree {
420-
for columnIndex in degree &- effectiveStep..<degree {
421-
data[rowIndex, columnIndex] = data[rowIndex, columnIndex].negateMod(modulus: modulus)
422-
}
423-
} else {
424-
for columnIndex in 0..<(degree &<< 1) &- effectiveStep {
425-
data[rowIndex, columnIndex] = data[rowIndex, columnIndex].negateMod(modulus: modulus)
426-
}
431+
for columnIndex in negateColumns {
432+
data[rowIndex, columnIndex] = data[rowIndex, columnIndex].negateMod(modulus: modulus)
427433
}
428434
}
429435
}

Sources/PrivateInformationRetrieval/IndexPir/PirUtil.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ extension PirUtilProtocol {
9999
var difference = ciphertext
100100
try await Scheme.subAssignAsync(&difference, c1)
101101
var differenceCoeff = try await difference.convertToCoeffFormat()
102-
try await Scheme.multiplyInversePowerOfXAsync(&differenceCoeff, power: shiftingPower)
102+
try await Scheme.multiplyPowerOfXAsync(&differenceCoeff, power: -shiftingPower)
103103
let differenceCanonical = try await differenceCoeff.convertToCanonicalFormat()
104104
try await Scheme.addAssignAsync(&c1, ciphertext)
105105
return (c1, differenceCanonical)

Sources/_TestUtilities/HeApiTestUtils.swift

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1376,34 +1376,68 @@ public enum HeAPITestHelpers {
13761376

13771377
/// Testing multiply inverse power of x.
13781378
@inlinable
1379-
public static func multiplyInverseTest<Scheme: HeScheme>(
1379+
public static func multiplyPowerOfXTest<Scheme: HeScheme>(
13801380
context: Scheme.Context,
13811381
scheme _: Scheme.Type) async throws
13821382
{
13831383
let testEnv = try HeAPITestHelpers.TestEnv<Scheme>(context: context, format: .coefficient)
13841384

1385-
var coeffCiphertext1 = try await testEnv.ciphertext1.convertToCoeffFormat()
1386-
var coeffCiphertext2 = coeffCiphertext1
1387-
var coeffCiphertext3 = coeffCiphertext1
1388-
var coeffCiphertext4 = coeffCiphertext1
1385+
var coeffCiphertext = try await testEnv.ciphertext1.convertToCoeffFormat()
1386+
var coeffCiphertexts = Array(repeating: coeffCiphertext, count: 8)
13891387
let degree = context.degree
13901388
let plaintextModulus = context.plaintextModulus
1389+
1390+
try Scheme.multiplyPowerOfX(&coeffCiphertext, power: 0)
1391+
try testEnv.checkDecryptsDecodes(ciphertext: coeffCiphertext, format: .coefficient, expected: testEnv.data1)
1392+
13911393
let power1 = Int.random(in: 0..<degree)
1394+
try Scheme.multiplyPowerOfX(&coeffCiphertexts[0], power: power1)
1395+
try await Scheme.multiplyPowerOfXAsync(&coeffCiphertexts[1], power: power1)
1396+
1397+
try Scheme.multiplyPowerOfX(&coeffCiphertexts[2], power: -power1)
1398+
try await Scheme.multiplyPowerOfXAsync(&coeffCiphertexts[3], power: -power1)
1399+
13921400
let power2 = Int.random(in: degree..<(degree << 1))
1393-
try Scheme.multiplyInversePowerOfX(&coeffCiphertext1, power: power1)
1394-
try Scheme.multiplyInversePowerOfX(&coeffCiphertext2, power: power2)
1395-
try await Scheme.multiplyInversePowerOfXAsync(&coeffCiphertext3, power: power1)
1396-
try await Scheme.multiplyInversePowerOfXAsync(&coeffCiphertext4, power: power2)
13971401

1398-
let expectedData1 = Array(testEnv.data1[power1..<degree] + testEnv.data1[0..<power1]
1402+
try Scheme.multiplyPowerOfX(&coeffCiphertexts[4], power: power2)
1403+
try await Scheme.multiplyPowerOfXAsync(&coeffCiphertexts[5], power: power2)
1404+
1405+
try Scheme.multiplyPowerOfX(&coeffCiphertexts[6], power: -power2)
1406+
try await Scheme.multiplyPowerOfXAsync(&coeffCiphertexts[7], power: -power2)
1407+
1408+
let expectedData1 = Array(testEnv.data1[degree - power1..<degree]
1409+
.map { $0.negateMod(modulus: plaintextModulus) } +
1410+
testEnv.data1[0..<degree - power1])
1411+
1412+
let expectedDataNeg1 = Array(testEnv.data1[power1..<degree] + testEnv.data1[0..<power1]
13991413
.map { $0.negateMod(modulus: plaintextModulus) })
1400-
let expectedData2 = Array(testEnv.data1[(power2 - degree)..<degree]
1414+
1415+
let expectedData2 = Array(testEnv.data1[(2 * degree - power2)..<degree] +
1416+
testEnv.data1[0..<(2 * degree - power2)].map { $0.negateMod(modulus: plaintextModulus) })
1417+
1418+
let expectedDataNeg2 = Array(testEnv.data1[(power2 - degree)..<degree]
14011419
.map { $0.negateMod(modulus: plaintextModulus) } + testEnv.data1[0..<(power2 - degree)])
14021420

1403-
try testEnv.checkDecryptsDecodes(ciphertext: coeffCiphertext1, format: .coefficient, expected: expectedData1)
1404-
try testEnv.checkDecryptsDecodes(ciphertext: coeffCiphertext2, format: .coefficient, expected: expectedData2)
1405-
try testEnv.checkDecryptsDecodes(ciphertext: coeffCiphertext3, format: .coefficient, expected: expectedData1)
1406-
try testEnv.checkDecryptsDecodes(ciphertext: coeffCiphertext4, format: .coefficient, expected: expectedData2)
1421+
try testEnv.checkDecryptsDecodes(ciphertext: coeffCiphertexts[0], format: .coefficient, expected: expectedData1)
1422+
try testEnv.checkDecryptsDecodes(ciphertext: coeffCiphertexts[1], format: .coefficient, expected: expectedData1)
1423+
try testEnv.checkDecryptsDecodes(
1424+
ciphertext: coeffCiphertexts[2],
1425+
format: .coefficient,
1426+
expected: expectedDataNeg1)
1427+
try testEnv.checkDecryptsDecodes(
1428+
ciphertext: coeffCiphertexts[3],
1429+
format: .coefficient,
1430+
expected: expectedDataNeg1)
1431+
try testEnv.checkDecryptsDecodes(ciphertext: coeffCiphertexts[4], format: .coefficient, expected: expectedData2)
1432+
try testEnv.checkDecryptsDecodes(ciphertext: coeffCiphertexts[5], format: .coefficient, expected: expectedData2)
1433+
try testEnv.checkDecryptsDecodes(
1434+
ciphertext: coeffCiphertexts[6],
1435+
format: .coefficient,
1436+
expected: expectedDataNeg2)
1437+
try testEnv.checkDecryptsDecodes(
1438+
ciphertext: coeffCiphertexts[7],
1439+
format: .coefficient,
1440+
expected: expectedDataNeg2)
14071441
}
14081442

14091443
/// Testing ntt and intt on ciphertexts.

Tests/HomomorphicEncryptionTests/Array2dTests.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,34 @@ struct Array2dTests {
103103
[8, 9, 10, 11, 12, 13, 14, 15])
104104
}
105105

106+
@Test
107+
func rotateColumns() throws {
108+
let data = (0..<12).map { UInt32($0) }
109+
var array = Array2d(data: data, rowCount: 3, columnCount: 4)
110+
try array.rotateColumns(by: 0)
111+
#expect(array.data == data)
112+
113+
try array.rotateColumns(by: -1)
114+
#expect(array.data == [1, 2, 3, 0, 5, 6, 7, 4, 9, 10, 11, 8])
115+
try array.rotateColumns(by: 1)
116+
#expect(array.data == data)
117+
118+
try array.rotateColumns(by: -2)
119+
#expect(array.data == [2, 3, 0, 1, 6, 7, 4, 5, 10, 11, 8, 9])
120+
try array.rotateColumns(by: 2)
121+
#expect(array.data == data)
122+
123+
try array.rotateColumns(by: -3)
124+
#expect(array.data == [3, 0, 1, 2, 7, 4, 5, 6, 11, 8, 9, 10])
125+
try array.rotateColumns(by: 3)
126+
#expect(array.data == data)
127+
128+
try array.rotateColumns(by: -5)
129+
#expect(array.data == [1, 2, 3, 0, 5, 6, 7, 4, 9, 10, 11, 8])
130+
try array.rotateColumns(by: 5)
131+
#expect(array.data == data)
132+
}
133+
106134
@Test
107135
func resizeColumn() {
108136
var array = Array2d(data: [Int](0..<6), rowCount: 2, columnCount: 3)

Tests/HomomorphicEncryptionTests/HeAPITests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ struct HeAPITests {
201201
try bfvTestKeySwitching(context: context)
202202
try HeAPITestHelpers.noiseBudgetTest(context: context, scheme: Bfv<T>.self)
203203
try await HeAPITestHelpers.repeatedAdditionTest(context: context, scheme: Bfv<T>.self)
204-
try await HeAPITestHelpers.multiplyInverseTest(context: context, scheme: Bfv<T>.self)
204+
try await HeAPITestHelpers.multiplyPowerOfXTest(context: context, scheme: Bfv<T>.self)
205205
try await HeAPITestHelpers.schemeTestNtt(context: context, scheme: Bfv<T>.self)
206206
try await HeAPITestHelpers.schemeTestFormats(context: context, scheme: Bfv<T>.self)
207207
}

0 commit comments

Comments
 (0)