Skip to content

Commit

Permalink
Clean up zeroCiphertext & noiseBudget APIs (#52)
Browse files Browse the repository at this point in the history
  • Loading branch information
fboemer authored Aug 9, 2024
1 parent 9388f6a commit db8a0b7
Show file tree
Hide file tree
Showing 8 changed files with 80 additions and 59 deletions.
6 changes: 4 additions & 2 deletions Sources/HomomorphicEncryption/Bfv/Bfv+Encrypt.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ extension Bfv {

@inlinable
// swiftlint:disable:next missing_docs attributes
public static func zeroCiphertext(context: Context<Self>, moduliCount: Int) throws -> CoeffCiphertext {
public static func zeroCiphertextCoeff(context: Context<Self>, moduliCount: Int?) throws -> CoeffCiphertext {
let moduliCount = moduliCount ?? context.ciphertextContext.moduli.count
let zeroPoly = try PolyRq<Scalar, Coeff>.zero(
context: context.ciphertextContext
.getContext(moduliCount: moduliCount))
Expand All @@ -31,7 +32,8 @@ extension Bfv {

@inlinable
// swiftlint:disable:next missing_docs attributes
public static func zeroCiphertext(context: Context<Self>, moduliCount: Int) throws -> EvalCiphertext {
public static func zeroCiphertextEval(context: Context<Self>, moduliCount: Int?) throws -> EvalCiphertext {
let moduliCount = moduliCount ?? context.ciphertextContext.moduli.count
let zeroPoly = try PolyRq<Scalar, Eval>.zero(
context: context.ciphertextContext
.getContext(moduliCount: moduliCount))
Expand Down
34 changes: 34 additions & 0 deletions Sources/HomomorphicEncryption/Ciphertext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,40 @@ public struct Ciphertext<Scheme: HeScheme, Format: PolyFormat>: Equatable, Senda
self.seed = seed
}

/// Generates a ciphertext of zeros.
///
/// A zero ciphertext may arise from HE computations, e.g., by subtracting a ciphertext from itself, or multiplying
/// a ciphertext with a zero plaintext.
///
/// - Parameters:
/// - context: Context for HE computation.
/// - moduliCount: Number of moduli in the zero ciphertext. If `nil`, the ciphertext will have the ciphertext
/// context with all the coefficient moduli in `context`.
/// - Returns: A zero ciphertext.
/// - Throws: Error upon failure to encode.
/// - Warning: a zero ciphertext is *transparent*, i.e., everyone can see the the underlying plaintext, zero in
/// this case. Transparency can propagate to ciphertexts operating with transparent ciphertexts, e.g.
/// ```
/// transparentCiphertext * ciphertext = transparentCiphertext
/// transparentCiphertext * plaintext = transparentCiphertext
/// transparentCiphertext + plaintext = transparentCiphertext
/// ```
/// - seelaso: ``Ciphertext/isTransparent()``
@inlinable
public static func zero(context: Context<Scheme>, moduliCount: Int? = nil) throws -> Ciphertext<Scheme, Format> {
if Format.self == Coeff.self {
let coeffCiphertext = try Scheme.zeroCiphertextCoeff(context: context, moduliCount: moduliCount)
// swiftlint:disable:next force_cast
return coeffCiphertext as! Ciphertext<Scheme, Format>
}
if Format.self == Eval.self {
let evalCiphertext = try Scheme.zeroCiphertextEval(context: context, moduliCount: moduliCount)
// swiftlint:disable:next force_cast
return evalCiphertext as! Ciphertext<Scheme, Format>
}
throw HeError.unsupportedHeOperation()
}

// MARK: ciphertext += plaintext

@inlinable
Expand Down
29 changes: 15 additions & 14 deletions Sources/HomomorphicEncryption/HeScheme.swift
Original file line number Diff line number Diff line change
Expand Up @@ -195,19 +195,20 @@ public protocol HeScheme {
///
/// - Parameters:
/// - context: Context for HE computation.
/// - moduliCount: Number of moduli in the zero ciphertext.
/// - moduliCount: Number of moduli in the zero ciphertext. If `nil`, the ciphertext will have the ciphertext
/// context with all the coefficient moduli in `context`.
/// - Returns: A zero ciphertext.
/// - Throws: Error upon failure to generate a zero ciphertext..
/// - Warning: a zero ciphertext is *transparent*, i.e., everyone can see the the underlying plaintext, zero in this
/// case.
/// Transparency can propagate to ciphertexts operating with transparent ciphertexts, e.g.
/// - Throws: Error upon failure to generate a zero ciphertext.
/// - Warning: a zero ciphertext is *transparent*, i.e., everyone can see the the underlying plaintext, zero in
/// this case. Transparency can propagate to ciphertexts operating with transparent ciphertexts, e.g.
/// ```
/// transparentCiphertext * ciphertext = transparentCiphertext
/// transparentCiphertext * plaintext = transparentCiphertext
/// transparentCiphertext + plaintext = transparentCiphertext
/// ```
/// - seealso: ``HeScheme/isTransparent(ciphertext:)``
static func zeroCiphertext(context: Context<Self>, moduliCount: Int) throws -> CoeffCiphertext
/// - seealso: ``Ciphertext/zero(context:moduliCount:)`` for an alternative API.
static func zeroCiphertextCoeff(context: Context<Self>, moduliCount: Int?) throws -> CoeffCiphertext

/// Generates a ciphertext of zeros in ``Eval`` format.
///
Expand All @@ -216,20 +217,20 @@ public protocol HeScheme {
///
/// - Parameters:
/// - context: Context for HE computation.
/// - moduliCount: Number of moduli in the zero ciphertext.
/// - moduliCount: Number of moduli in the zero ciphertext. If `nil`, the ciphertext will have the ciphertext
/// context with all the coefficient moduli in `context`.
/// - Returns: A zero ciphertext.
/// - Throws: Error upon failure to encode.
/// - Warning: a zero ciphertext is *transparent*, i.e., everyone can see the the underlying plaintext, zero in
/// this
/// case.
/// Transparency can propagate to ciphertexts operating with transparent ciphertexts, e.g.
/// - Throws: Error upon failure to generate a zero ciphertext.
/// - Warning: a zero ciphertext is *transparent*, i.e., everyone can see the the underlying plaintext, zero in
/// this case. Transparency can propagate to ciphertexts operating with transparent ciphertexts, e.g.
/// ```
/// transparentCiphertext * ciphertext = transparentCiphertext
/// transparentCiphertext * plaintext = transparentCiphertext
/// transparentCiphertext + plaintext = transparentCiphertext
/// ```
/// - seealso: ``HeScheme/isTransparent(ciphertext:)``
static func zeroCiphertext(context: Context<Self>, moduliCount: Int) throws -> EvalCiphertext
/// - seealso: ``Ciphertext/zero(context:moduliCount:)`` for an alternative API.
static func zeroCiphertextEval(context: Context<Self>, moduliCount: Int?) throws -> EvalCiphertext

/// Computes whether a ciphertext is transparent.
///
Expand Down Expand Up @@ -862,7 +863,7 @@ extension HeScheme {
}
return try (zip(ciphertexts, plaintexts).map { ciphertext, plaintext in
guard let plaintext else {
return try Self.zeroCiphertext(
return try EvalCiphertext.zero(
context: firstCiphertext.context,
moduliCount: firstCiphertext.moduli.count)
}
Expand Down
4 changes: 2 additions & 2 deletions Sources/HomomorphicEncryption/NoOpScheme.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,15 @@ public enum NoOpScheme: HeScheme {
try decode(plaintext: plaintext.inverseNtt(), format: format)
}

public static func zeroCiphertext(context: Context<Self>, moduliCount _: Int) throws -> CoeffCiphertext {
public static func zeroCiphertextCoeff(context: Context<Self>, moduliCount _: Int?) throws -> CoeffCiphertext {
NoOpScheme
.CoeffCiphertext(
context: context,
polys: [PolyRq.zero(context: context.plaintextContext)],
correctionFactor: 1)
}

public static func zeroCiphertext(context: Context<Self>, moduliCount _: Int) throws -> EvalCiphertext {
public static func zeroCiphertextEval(context: Context<Self>, moduliCount _: Int?) throws -> EvalCiphertext {
NoOpScheme
.EvalCiphertext(
context: context,
Expand Down
3 changes: 1 addition & 2 deletions Sources/HomomorphicEncryption/SerializedPlaintext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@ extension Plaintext where Format == Eval {
/// - serialized: Serialized plaintext.
/// - context: Context to associate with the plaintext.
/// - moduliCount: Optional number of moduli to associate with the plaintext. If not set, the plaintext will have
/// the top-level ciphertext context with all the
/// moduli.
/// the top-level ciphertext context with all themoduli.
/// - Throws: Error upon failure to deserialize.
public init(deserialize serialized: SerializedPlaintext, context: Context<Scheme>, moduliCount: Int? = nil) throws {
self.context = context
Expand Down
2 changes: 1 addition & 1 deletion Sources/PrivateInformationRetrieval/KeywordDatabase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ public enum ProcessKeywordDatabase {
let clock = ContinuousClock()
var minNoiseBudget = Double.infinity
let computeTimes = try (0..<trials).map { trial in
let secretKey = try Scheme.generateSecretKey(context: context)
let secretKey = try context.generateSecretKey()
let trialEvaluationKey = try client.generateEvaluationKey(using: secretKey)
let trialQuery = try client.generateQuery(at: row.keyword, using: secretKey)
let computeTime = try clock.measure {
Expand Down
2 changes: 1 addition & 1 deletion Sources/TestUtilities/TestUtilities.swift
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ extension TestUtils {
try EncryptionParameters<Scheme>(
polyDegree: testPolyDegree,
plaintextModulus: Scheme.Scalar(testPlaintextModulus),
coefficientModuli: testCoefficientModuli(Scheme.Scalar.self).map { Scheme.Scalar($0) },
coefficientModuli: testCoefficientModuli(Scheme.Scalar.self),
errorStdDev: ErrorStdDev.stdDev32,
securityLevel: SecurityLevel.unchecked)
}
Expand Down
59 changes: 22 additions & 37 deletions Tests/HomomorphicEncryptionTests/HeAPITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,15 @@ class HeAPITests: XCTestCase {
self.coeffPlaintext2 = try context.encode(values: data2, format: format)
self.evalPlaintext1 = try context.encode(values: data1, format: format)
self.evalPlaintext2 = try context.encode(values: data2, format: format)
self.secretKey = try Scheme.generateSecretKey(context: context)
self.ciphertext1 = try Scheme.encrypt(coeffPlaintext1, using: secretKey)
self.ciphertext2 = try Scheme.encrypt(coeffPlaintext2, using: secretKey)
self.secretKey = try context.generateSecretKey()
self.ciphertext1 = try coeffPlaintext1.encrypt(using: secretKey)
self.ciphertext2 = try coeffPlaintext2.encrypt(using: secretKey)
self.evalCiphertext1 = try ciphertext1.convertToEvalFormat()
let evaluationkeyConfig = EvaluationKeyConfiguration(
galoisElements: galoisElements,
hasRelinearizationKey: true)
self.evaluationKey = if context.supportsEvaluationKey, !galoisElements.isEmpty || relinearizationKey {
try Scheme.generateEvaluationKey(
context: context,
try context.generateEvaluationKey(
configuration: evaluationkeyConfig,
using: secretKey)
} else {
Expand Down Expand Up @@ -185,12 +184,8 @@ class HeAPITests: XCTestCase {
let testEnv = try TestEnv(context: context, format: .coefficient)
let zeros = [Scheme.Scalar](repeating: 0, count: context.degree)

let coeffCiphertext: Ciphertext<Scheme, Coeff> = try Scheme.zeroCiphertext(
context: context,
moduliCount: context.ciphertextContext.moduli.count)
let evalCiphertext: Ciphertext<Scheme, Eval> = try Scheme.zeroCiphertext(
context: context,
moduliCount: context.ciphertextContext.moduli.count)
let coeffCiphertext = try Ciphertext<Scheme, Coeff>.zero(context: context)
let evalCiphertext = try Ciphertext<Scheme, Eval>.zero(context: context)
var canonicalCiphertext = try coeffCiphertext.convertToCanonicalFormat()
try canonicalCiphertext.modSwitchDownToSingle()

Expand All @@ -200,7 +195,7 @@ class HeAPITests: XCTestCase {

let zeroPlaintext: Scheme.CoeffPlaintext = try context.encode(values: zeros,
format: .coefficient)
let nonTransparentZero = try Scheme.encrypt(zeroPlaintext, using: testEnv.secretKey)
let nonTransparentZero = try zeroPlaintext.encrypt(using: testEnv.secretKey)
if Scheme.self != NoOpScheme.self {
XCTAssertFalse(nonTransparentZero.isTransparent())
}
Expand All @@ -214,9 +209,7 @@ class HeAPITests: XCTestCase {
let testEnv = try TestEnv(context: context, format: .coefficient)
let expected = [Scheme.Scalar](repeating: 0, count: context.degree)

let zeroCoeffCiphertext: Ciphertext<Scheme, Coeff> = try Scheme.zeroCiphertext(
context: context,
moduliCount: context.ciphertextContext.moduli.count)
let zeroCoeffCiphertext = try Ciphertext<Scheme, Coeff>.zero(context: context)
let zeroCiphertext = try zeroCoeffCiphertext.convertToCanonicalFormat()

let sum1 = try zeroCiphertext + zeroCiphertext
Expand All @@ -238,9 +231,7 @@ class HeAPITests: XCTestCase {
let testEnv = try TestEnv(context: context, format: .coefficient)
let expected = [Scheme.Scalar](repeating: 0, count: context.degree)

let zeroCiphertext: Ciphertext<Scheme, Eval> = try Scheme.zeroCiphertext(
context: context,
moduliCount: testEnv.evalPlaintext1.moduli.count)
let zeroCiphertext = try Ciphertext<Scheme, Eval>.zero(context: context)
let product = try zeroCiphertext * testEnv.evalPlaintext1
XCTAssert(product.isTransparent())

Expand Down Expand Up @@ -947,7 +938,7 @@ class HeAPITests: XCTestCase {
}

let testEnv = try TestEnv(context: context, format: .coefficient)
let newSecretKey = try Bfv<T>.generateSecretKey(context: context)
let newSecretKey = try context.generateSecretKey()

let keySwitchKey = try Bfv<T>.generateKeySwitchKey(context: context,
currentKey: testEnv.secretKey.poly,
Expand All @@ -958,42 +949,36 @@ class HeAPITests: XCTestCase {
keySwitchingKey: keySwitchKey)
switchedPolys[0] += testEnv.ciphertext1.polys[0]
let switchedCiphertext = Ciphertext(context: context, polys: switchedPolys, correctionFactor: 1)
let plaintext = try Bfv<T>.decrypt(switchedCiphertext, using: newSecretKey)
let decrypted: [T] = try Bfv<T>.decode(plaintext: plaintext, format: .coefficient)
let plaintext = try switchedCiphertext.decrypt(using: newSecretKey)
let decrypted: [T] = try plaintext.decode(format: .coefficient)

XCTAssertEqual(testEnv.data1, decrypted)
}

private func bfvNoiseBudgetTest<T>(context: Context<Bfv<T>>) throws {
let testEnv = try TestEnv(context: context, format: .coefficient)

let zeroCoeffCiphertext: Bfv<T>.CoeffCiphertext = try Bfv<T>.zeroCiphertext(
context: context,
moduliCount: 1)
let zeroCoeffCiphertext = try Bfv<T>.CoeffCiphertext.zero(context: context, moduliCount: 1)
XCTAssertEqual(
try Bfv<T>.noiseBudget(of: zeroCoeffCiphertext, using: testEnv.secretKey, variableTime: true),
try zeroCoeffCiphertext.noiseBudget(using: testEnv.secretKey, variableTime: true),
Double.infinity)
let zeroEvalCiphertext: Bfv<T>.CoeffCiphertext = try Bfv<T>.zeroCiphertext(
context: context,
moduliCount: 1)
let zeroEvalCiphertext = try Bfv<T>.EvalCiphertext.zero(context: context, moduliCount: 1)
XCTAssertEqual(
try Bfv<T>.noiseBudget(of: zeroEvalCiphertext, using: testEnv.secretKey, variableTime: true),
try zeroEvalCiphertext.noiseBudget(using: testEnv.secretKey, variableTime: true),
Double.infinity)

var coeffCiphertext = testEnv.ciphertext1
var expected = testEnv.coeffPlaintext1
try coeffCiphertext.modSwitchDownToSingle()
var ciphertext = try coeffCiphertext.convertToEvalFormat()

var noiseBudget = try Bfv<T>.noiseBudget(of: ciphertext, using: testEnv.secretKey, variableTime: true)
var noiseBudget = try ciphertext.noiseBudget(using: testEnv.secretKey, variableTime: true)
XCTAssert(noiseBudget > 0)

let coeffNoiseBudget = try Bfv<T>.noiseBudget(
of: ciphertext.convertToCoeffFormat(),
let coeffNoiseBudget = try ciphertext.convertToCoeffFormat().noiseBudget(
using: testEnv.secretKey,
variableTime: true)
let canonicalNoiseBudget = try Bfv<T>.noiseBudget(
of: ciphertext.convertToCanonicalFormat(),
let canonicalNoiseBudget = try ciphertext.convertToCanonicalFormat().noiseBudget(
using: testEnv.secretKey,
variableTime: true)
XCTAssertEqual(coeffNoiseBudget, noiseBudget)
Expand All @@ -1002,19 +987,19 @@ class HeAPITests: XCTestCase {
while noiseBudget > Bfv<T>.minNoiseBudget + 1 {
ciphertext = try ciphertext + ciphertext
try expected += expected
let newNoiseBudget = try Bfv<T>.noiseBudget(of: ciphertext, using: testEnv.secretKey, variableTime: true)
let newNoiseBudget = try ciphertext.noiseBudget(using: testEnv.secretKey, variableTime: true)
XCTAssertIsClose(newNoiseBudget, noiseBudget - 1)
noiseBudget = newNoiseBudget

let decrypted = try Bfv<T>.decrypt(ciphertext, using: testEnv.secretKey)
let decrypted = try ciphertext.decrypt(using: testEnv.secretKey)
XCTAssertEqual(decrypted, expected)
}
// Two more additions yields incorrect results
ciphertext = try ciphertext + ciphertext
ciphertext = try ciphertext + ciphertext
try expected += expected
try expected += expected
let decrypted = try Bfv<T>.decrypt(ciphertext, using: testEnv.secretKey)
let decrypted = try ciphertext.decrypt(using: testEnv.secretKey)
XCTAssertNotEqual(decrypted, expected)
}

Expand Down

0 comments on commit db8a0b7

Please sign in to comment.