Skip to content

Commit

Permalink
Add SymmetricPIR (#144)
Browse files Browse the repository at this point in the history
  • Loading branch information
akshaywadia authored Jan 14, 2025
1 parent 1dfa473 commit c880b77
Show file tree
Hide file tree
Showing 20 changed files with 1,135 additions and 84 deletions.
37 changes: 23 additions & 14 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"originHash" : "8fa2cd62efd12ea7c4d07fe29cae38bf3817c0818b3db7d057d5cde48d63b525",
"originHash" : "97a30c267f45840228fa0cbec6f6118fe33a73655d21926935400d86da3d5b87",
"pins" : [
{
"identity" : "hdrhistogram-swift",
Expand All @@ -15,8 +15,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/ordo-one/package-benchmark",
"state" : {
"revision" : "6889229e21c6d9d0421a050e9e11c8f5eb5bc279",
"version" : "1.23.5"
"revision" : "51fffb6bc58bcbaca9c1954e6e52b56f3c9c2d54",
"version" : "1.27.4"
}
},
{
Expand All @@ -42,8 +42,17 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-argument-parser.git",
"state" : {
"revision" : "0fbc8848e389af3bb55c182bc19ca9d5dc2f255b",
"version" : "1.4.0"
"revision" : "41982a3656a71c768319979febd796c6fd111d5c",
"version" : "1.5.0"
}
},
{
"identity" : "swift-asn1",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-asn1.git",
"state" : {
"revision" : "7faebca1ea4f9aaf0cda1cef7c43aecd2311ddf6",
"version" : "1.3.0"
}
},
{
Expand All @@ -60,23 +69,23 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-crypto.git",
"state" : {
"revision" : "46072478ca365fe48370993833cb22de9b41567f",
"version" : "3.5.2"
"revision" : "ff0f781cf7c6a22d52957e50b104f5768b50c779",
"version" : "3.10.0"
}
},
{
"identity" : "swift-docc-plugin",
"kind" : "remoteSourceControl",
"location" : "https://github.com/swiftlang/swift-docc-plugin",
"state" : {
"revision" : "26ac5758409154cc448d7ab82389c520fa8a8247",
"version" : "1.3.0"
"revision" : "85e4bb4e1cd62cec64a4b8e769dcefdf0c5b9d64",
"version" : "1.4.3"
}
},
{
"identity" : "swift-docc-symbolkit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-docc-symbolkit",
"location" : "https://github.com/swiftlang/swift-docc-symbolkit",
"state" : {
"revision" : "b45d1f2ed151d057b54504d653e0da5552844e34",
"version" : "1.0.0"
Expand All @@ -87,8 +96,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-log.git",
"state" : {
"revision" : "9cb486020ebf03bfa5b5df985387a14a98744537",
"version" : "1.6.1"
"revision" : "96a2f8a0fa41e9e09af4585e2724c4e825410b91",
"version" : "1.6.2"
}
},
{
Expand All @@ -114,8 +123,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-system",
"state" : {
"revision" : "6a9e38e7bd22a3b8ba80bddf395623cf68f57807",
"version" : "1.3.1"
"revision" : "c8a44d836fe7913603e246acab7c528c2e780168",
"version" : "1.4.0"
}
},
{
Expand Down
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// The swift-tools-version declares the minimum version of Swift required to build this package.
// Remember to update CI if changing

// Copyright 2024 Apple Inc. and the Swift Homomorphic Encryption project authors
// 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.
Expand Down Expand Up @@ -59,7 +59,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-crypto.git", from: "3.4.0"),
.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"),
.package(url: "https://github.com/apple/swift-protobuf", from: "1.28.1"), // Keep version in sync with README
Expand Down
31 changes: 30 additions & 1 deletion Sources/HomomorphicEncryption/Util.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2024 Apple Inc. and the Swift Homomorphic Encryption project authors
// 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.
Expand Down Expand Up @@ -39,6 +39,35 @@ extension FixedWidthInteger {
}
}

extension [UInt8] {
/// Initializes a byte array from a hexadecimal string.
///
/// Initializes to nil upon invalid hexadecimal string.
/// - Parameter hexString: A hexadecimal string, without leading "0x".
public init?(hexEncoded hexString: String) {
// Ensure the string has an even number of characters
guard hexString.count.isMultiple(of: 2) else {
return nil
}

var data = Array()
data.reserveCapacity(hexString.count / 2)
var index = hexString.startIndex

while index < hexString.endIndex {
let nextIndex = hexString.index(index, offsetBy: 2)
if let byte = UInt8(hexString[index..<nextIndex], radix: 16) {
data.append(byte)
} else {
return nil // Invalid hex string
}
index = nextIndex
}

self = data
}
}

extension Array where Element: FixedWidthInteger {
/// Computes the product of the elements in the array.
///
Expand Down
54 changes: 48 additions & 6 deletions Sources/PIRProcessDatabase/ProcessDatabase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,29 @@ enum TableSizeOption: Codable, Equatable, Hashable {
static let defaultExpansionFactor = 1.1
}

/// The configuration for Symmetric PIR.
struct SymmetricPirArguments: Codable, Hashable {
/// File path for key with which database will be encrypted.
let databaseEncryptionKeyFilePath: String
/// Config type for Symmetric PIR.
let configType: SymmetricPirConfigType

/// Returns a parsed `SymmetricPirConfig` for given parameters.
/// - Returns: Symmetric PIR config.
func resolve() throws -> SymmetricPirConfig {
do {
let secretKeyString = try String(contentsOfFile: databaseEncryptionKeyFilePath, encoding: .utf8)
guard let secretKey = Array(hexEncoded: secretKeyString) else {
throw PirError.invalidOPRFHexSecretKey
}
try configType.validateEncryptionKey(secretKey)
return try SymmetricPirConfig(oprfSecretKey: secretKey, configType: configType)
} catch {
throw PirError.failedToLoadOPRFKey(underlyingError: "\(error)", filePath: databaseEncryptionKeyFilePath)
}
}
}

/// A struct representing the arguments for the `cuckooTable` command.
struct CuckooTableArguments: Codable, Equatable, Hashable {
let hashFunctionCount: Int?
Expand Down Expand Up @@ -107,6 +130,18 @@ extension String {
}
}

extension KeywordDatabase {
/// Creates a new `KeywordDatabase` from a given path.
/// - Parameters:
/// - path: The path to the `KeywordDatabase` file.
/// - sharding: The sharding strategy to use.
/// - Throws: Error upon failure to initialize the database.
init(from path: String, sharding: Sharding) throws {
let database = try Apple_SwiftHomomorphicEncryption_Pir_V1_KeywordDatabase(from: path)
try self.init(rows: database.native(), sharding: sharding)
}
}

/// A struct that represents the database processing arguments.
struct Arguments: Codable, Equatable, Hashable, Sendable {
/// The default arguments.
Expand All @@ -129,6 +164,7 @@ struct Arguments: Codable, Equatable, Hashable, Sendable {
var keyCompression: PirKeyCompressionStrategy?
// swiftlint:disable:next discouraged_optional_boolean
var useMaxSerializedBucketSize: Bool?
var symmetricPirArguments: SymmetricPirArguments?
var trialsPerShard: Int?

static func defaultJsonString() -> String {
Expand Down Expand Up @@ -210,6 +246,7 @@ struct Arguments: Codable, Equatable, Hashable, Sendable {
algorithm: algorithm ?? .mulPir,
keyCompression: keyCompression ?? .noCompression,
useMaxSerializedBucketSize: useMaxSerializedBucketSize ?? false,
symmetricPirConfig: symmetricPirArguments?.resolve(),
trialsPerShard: trialsPerShard ?? 1)
}
}
Expand All @@ -227,6 +264,7 @@ struct ResolvedArguments: CustomStringConvertible, Encodable {
let algorithm: PirAlgorithm
let keyCompression: PirKeyCompressionStrategy
let useMaxSerializedBucketSize: Bool
let symmetricPirConfig: SymmetricPirConfig?
let trialsPerShard: Int

var description: String {
Expand All @@ -248,6 +286,7 @@ struct ResolvedArguments: CustomStringConvertible, Encodable {
/// - rlweParameters: RLWE parameters.
/// - algorithm: PIR algorithm.
/// - keyCompression: Evaluation key compression.
/// - symmetricPirConfig: Config for symmetric PIR.
/// - trialsPerShard: Number of test queries per shard.
init(
inputDatabase: String,
Expand All @@ -261,6 +300,7 @@ struct ResolvedArguments: CustomStringConvertible, Encodable {
algorithm: PirAlgorithm,
keyCompression: PirKeyCompressionStrategy,
useMaxSerializedBucketSize: Bool,
symmetricPirConfig: SymmetricPirConfig?,
trialsPerShard: Int) throws
{
self.inputDatabase = inputDatabase
Expand All @@ -274,6 +314,7 @@ struct ResolvedArguments: CustomStringConvertible, Encodable {
self.algorithm = algorithm
self.keyCompression = keyCompression
self.useMaxSerializedBucketSize = useMaxSerializedBucketSize
self.symmetricPirConfig = symmetricPirConfig
self.trialsPerShard = trialsPerShard

try validate()
Expand Down Expand Up @@ -330,7 +371,8 @@ struct ProcessDatabase: AsyncParsableCommand {
unevenDimensions: true,
keyCompression: config.keyCompression,
useMaxSerializedBucketSize: config.useMaxSerializedBucketSize,
shardingFunction: config.shardingFunction)
shardingFunction: config.shardingFunction,
symmetricPirClientConfig: config.symmetricPirConfig?.clientConfig())
let databaseConfig = KeywordDatabaseConfig(
sharding: config.sharding,
keywordPirConfig: keywordConfig)
Expand All @@ -340,18 +382,18 @@ struct ProcessDatabase: AsyncParsableCommand {
encryptionParameters: encryptionParameters,
algorithm: config.algorithm,
keyCompression: config.keyCompression,
trialsPerShard: config.trialsPerShard)

trialsPerShard: config.trialsPerShard,
symmetricPirConfig: config.symmetricPirConfig)
let context = try Context(encryptionParameters: processArgs.encryptionParameters)
let keywordDatabase = try KeywordDatabase(
rows: database,
sharding: processArgs.databaseConfig.sharding,
shardingFunction: config.shardingFunction)
shardingFunction: config.shardingFunction,
symmetricPirConfig: processArgs.symmetricPirConfig)
ProcessDatabase.logger.info("Sharded database into \(keywordDatabase.shards.count) shards")

let shards = keywordDatabase.shards.sorted { $0.0.localizedStandardCompare($1.0) == .orderedAscending }

var evaluationKeyConfig = EvaluationKeyConfig()

if parallel {
try await withThrowingTaskGroup(of: EvaluationKeyConfig.self) { group in
for (shardID, shard) in shards {
Expand Down
41 changes: 40 additions & 1 deletion Sources/PIRShardDatabase/ShardDatabase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,21 @@ struct ShardingArguments: ParsableArguments {
var otherShardCount: Int?
}

struct SymmetricPirArguments: ParsableArguments {
@Option(
help: """
path to file containing key for encrypting server database as a
hexadecimal key string, without leading '0x'.
""")
var databaseEncryptionKeyPath: String?
@Option(help: """
config type for symmetric pir; default is nil, unless `databaseEncryptionKeyPath`\
is specified, in which case the default is\
\(SymmetricPirConfigType.OPRF_P384_AES_GCM_192_NONCE_96_TAG_128.rawValue)
""")
var symmetricPirConfigType: SymmetricPirConfigType?
}

extension Sharding {
init?(from arguments: ShardingArguments) {
switch arguments.sharding {
Expand Down Expand Up @@ -77,6 +92,8 @@ extension String {
}
}

extension SymmetricPirConfigType: ExpressibleByArgument {}

let discussion =
"""
This executable allows one to divide a database into disjoint shards. \
Expand All @@ -95,6 +112,7 @@ struct ProcessCommand: ParsableCommand {
var outputDatabase: String

@OptionGroup var sharding: ShardingArguments
@OptionGroup var symmetricPirArguments: SymmetricPirArguments

func validate() throws {
try inputDatabase.validateProtoFilename(descriptor: "inputDatabase")
Expand All @@ -105,6 +123,11 @@ struct ProcessCommand: ParsableCommand {
guard Sharding(from: sharding) != nil else {
throw ValidationError("Invalid sharding \(sharding)")
}
if symmetricPirArguments.symmetricPirConfigType != nil {
guard symmetricPirArguments.databaseEncryptionKeyPath != nil else {
throw ValidationError("Missing databaseEncryptionKeyPath.")
}
}
}

mutating func run() throws {
Expand All @@ -114,7 +137,23 @@ struct ProcessCommand: ParsableCommand {
let shardingFunction = try ShardingFunction(from: self.sharding)
let database: [KeywordValuePair] =
try Apple_SwiftHomomorphicEncryption_Pir_V1_KeywordDatabase(from: inputDatabase).native()
let sharded = try KeywordDatabase(rows: database, sharding: sharding, shardingFunction: shardingFunction)
let symmetricPirConfig = try symmetricPirArguments.databaseEncryptionKeyPath.map { filePath in
let configType = symmetricPirArguments.symmetricPirConfigType ?? .OPRF_P384_AES_GCM_192_NONCE_96_TAG_128
do {
let secretKeyString = try String(contentsOfFile: filePath, encoding: .utf8)
guard let secretKey = Array(hexEncoded: secretKeyString) else {
throw PirError.invalidOPRFHexSecretKey
}
try configType.validateEncryptionKey(secretKey)
return try SymmetricPirConfig(oprfSecretKey: secretKey, configType: configType)
} catch {
throw PirError.failedToLoadOPRFKey(underlyingError: "\(error)", filePath: filePath)
}
}
let sharded = try KeywordDatabase(rows: database,
sharding: sharding,
shardingFunction: shardingFunction,
symmetricPirConfig: symmetricPirConfig)
for (shardID, shard) in sharded.shards {
let outputDatabaseFilename = outputDatabase.replacingOccurrences(
of: "SHARD_ID",
Expand Down
Loading

0 comments on commit c880b77

Please sign in to comment.