Skip to content
Permalink

Comparing changes

This is a direct comparison between two commits made in this repository or its related repositories. View the default comparison for this range or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: apple/swift-homomorphic-encryption
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 19bce97d33e9e0a34365ead0dd4af378b9f4d0c1
Choose a base ref
..
head repository: apple/swift-homomorphic-encryption
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 6f38f384b9be8aae551463913b8fed7e5a60dfa4
Choose a head ref
Original file line number Diff line number Diff line change
@@ -260,7 +260,7 @@ struct PnnsBenchmarkContext<Scheme: HeScheme> {
.map { encryptionParams in try Context(encryptionParameters: encryptionParams) }
self.processedDatabase = try database.process(config: serverConfig, contexts: contexts)
self.client = try Client(config: clientConfig, contexts: contexts)
self.server = try Server(database: processedDatabase, config: serverConfig)
self.server = try Server(database: processedDatabase)
self.secretKey = try client.generateSecretKey()
self.evaluationKey = try client.generateEvaluationKey(using: secretKey)

5 changes: 4 additions & 1 deletion Sources/PIRGenerateDatabase/GenerateDatabase.swift
Original file line number Diff line number Diff line change
@@ -73,10 +73,13 @@ struct GenerateDatabaseCommand: ParsableCommand {

@Option var valueType: ValueTypeArguments

@Option(help: "The first keyword")
var firstKeyword: Int = 0

mutating func run() throws {
let databaseRows = (0..<rowCount)
.map { rowIndex in
let keyword = [UInt8](String(rowIndex).utf8)
let keyword = [UInt8](String(firstKeyword + rowIndex).utf8)
guard let valueSize = valueSize.range.randomElement() else {
preconditionFailure("Could not sample valueSize from range \(valueSize.range)")
}
Original file line number Diff line number Diff line change
@@ -23,11 +23,11 @@ The binary will be generated in `.build/release/PIRGenerateDatabase`.
PIRGenerateDatabase \
--output-database database.txtpb \
--row-count 100 \
--value-size '10..<20' \
--value-size '10...20' \
--value-type repeated
```

This will generate a database of 100 rows, with keywords 0 to 100, and each value repeating the keyword for 10 to 20 bytes.
This will generate a database of 100 rows, with keywords 0 to 99, and each value repeating the keyword for 10 to 20 bytes.

The database is a serialized [Apple_SwiftHomomorphicEncryption_Pir_V1_KeywordDatabase](https://swiftpackageindex.com/apple/swift-homomorphic-encryption/main/documentation/privateinformationretrievalprotobuf/apple_swifthomomorphicencryption_pir_v1_keyworddatabase).
For readability, the `.txtpb` extension ensures the output database will be saved in protocol buffer text format.
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# Reusing PIR Parameters

Learn how to reuse PIR parameters across database updates.

## Overview
Database updates may lead to different PIR configurations, which need to be synced to the client.
We can configure the database processing to yield the same PIR configuration across database updates.

### Requirements
This example assumes that you have the following binaries available on your `$PATH`.
The binaries are:
- `PIRGenerateDatabase`
- `PIRProcessDatabase`

The way to add these to your path is by first making sure that the `~/.swiftpm/bin` directory is on your `$PATH`. To do
so, add the following line to your `~/.zshrc` or appropriate shell configuration file.
```sh
export PATH="$HOME/.swiftpm/bin:$PATH"
```
Make sure to reload it (`source ~/.zshrc`) or by restarting your terminal emulator. Then we are going to use the
`experimental-install` feature of Swift Package Manager.

Change directory to a checkout of this repository and run the following command.
```sh
swift package experimental-install -c release --product PIRGenerateDatabase
swift package experimental-install -c release --product PIRProcessDatabase
```

### Example
#### Database Creation

1. We start by generating a sample database.

```sh
PIRGenerateDatabase \
--output-database /tmp/database-v1.txtpb \
--row-count 10000 \
--value-size '10...20' \
--value-type repeated
```

This will generate a database of 10000 rows, with keywords 0 to 9999, and each value repeating the keyword for 10 to 20 bytes.

We view a few rows from the database with
```sh
head /tmp/database-v1.txtpb
```
which shows
```json
rows {
keyword: "0"
value: "000000000000000"
}
rows {
keyword: "1"
value: "111111111111111111"
}
rows {
keyword: "2"
```

2. To simulate an updated database, we generate a database update with 1000 more rows and different value sizes.
```
PIRGenerateDatabase \
--output-database /tmp/database-update.txtpb \
--row-count 1000 \
--first-keyword 10000 \
--value-size '10..<25' \
--value-type repeated
```

We combine the initial database with the update to create `/tmp/database-v2.txtpb`.
```sh
cat /tmp/database-v1.txtpb /tmp/database-update.txtpb > /tmp/database-v2.txtpb
```

#### Processing
To ensure processing the update database yields the same configuration, we use the `.fixedSize` cuckoo table argument, specifying a bucket count.
A larger bucket count will leave more room for new entries, without changing the configuration.
However, a larger bucket count will also increase server runtime.
One way to choose the `bucketCount` is to start with `bucketCount : 1` and try larger `bucketCounts` until the processing works.
If the processing throws a `PirError.failedToConstructCuckooTable` or logs `Failed to construct Cuckoo table`, this is an indication the chosen bucket count was too small.

We create `/tmp/config-v1-fixed-size.json` with the following contents
```
{
"algorithm" : "mulPir",
"cuckooTableArguments" : {
"bucketCount" : {
"fixedSize" : {
"bucketCount" : 256,
}
},
"hashFunctionCount" : 2,
"maxEvictionCount" : 100,
"maxSerializedBucketSize" : 1024
},
"inputDatabase" : "/tmp/database-v1.txtpb",
"keyCompression" : "noCompression",
"outputDatabase" : "/tmp/database-v1-SHARD_ID.bin",
"outputEvaluationKeyConfig" : "/tmp/database-v1-evaluation-key-config.txtpb",
"outputPirParameters" : "/tmp/database-v1-pir-parameters-SHARD_ID.txtpb",
"rlweParameters" : "n_4096_logq_27_28_28_logt_5",
"sharding" : {
"shardCount" : 2
},
"trialsPerShard" : 1
}
```
and process the original database with
```sh
PIRProcessDatabase /tmp/config-v1-fixed-size.json
```

We can get a hash of the output parameters with `cat /tmp/database-v1-pir-parameters-*.txtpb | shasum`

We create `/tmp/config-v2-fixed-size.json` by replacing `v1` with `v2`:
```sh
sed 's/v1/v2/g' /tmp/config-v1-fixed-size.json > /tmp/config-v2-fixed-size.json
```
and process the updated database with
```sh
PIRProcessDatabase /tmp/config-v2-fixed-size.json
```
We can get a hash of the output parameters with `cat /tmp/database-v2-pir-parameters-*.txtpb | shasum`.
This should match the shasum from `cat /tmp/database-v1-pir-parameters-*.txtpb | shasum`.
2 changes: 1 addition & 1 deletion Sources/PrivateInformationRetrieval/CuckooTable.swift
Original file line number Diff line number Diff line change
@@ -347,7 +347,7 @@ struct CuckooTable {
"""
Unable to insert value with keyword \(keywordValuePair.keyword) \
into table with \(entryCount) entries. \
Consider enabling table expansion in config.
Consider setting `allowExpansion` or increasing `bucketCount`.
""")
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Parameter Tuning

Keyword PIR Parameter Tuning
Learn how to tune parameters for Keyword PIR.

## Overview

@@ -68,7 +68,7 @@ executables.
PIRGenerateDatabase \
--output-database thinDatabase.txtpb \
--row-count 100000 \
--value-size '2' \
--value-size 2 \
--value-type random
```

Original file line number Diff line number Diff line change
@@ -22,3 +22,4 @@ The PIR implementation in Swift Homomorphic Encryption uses homomorphic encrypti
### Articles
- <doc:EncodingPipeline>
- <doc:ParameterTuning>
- <doc:ReusingPirParameters>
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# Reusing PIR Parameters

Reusing PIR parameters across database updates.

## Overview
Database updates may lead to different PIR configurations, which need to be synced to the client.
We can configure the database processing to yield the same PIR configuration on small database updates.

### Requirements
This example assumes that you have the following binaries available on your `$PATH`.
The binaries are:
- `PIRGenerateDatabase`
- `PIRProcessDatabase`

The way to add these to your path is by first making sure that the `~/.swiftpm/bin` directory is on your `$PATH`. To do
so, add the following line to your `~/.zshrc` or appropriate shell configuration file.
```sh
export PATH="$HOME/.swiftpm/bin:$PATH"
```
Make sure to reload it (`source ~/.zshrc`) or by restarting your terminal emulator. Then we are going to use the
`experimental-install` feature of Swift Package Manager.

Change directory to a checkout of this repository and run the following command.
```sh
swift package experimental-install -c release --product PIRGenerateDatabase
swift package experimental-install -c release --product PIRProcessDatabase
```

### Example
#### Database Creation

1. We start by generating a sample database.

```sh
PIRGenerateDatabase \
--output-database /tmp/database-v1.txtpb \
--row-count 10000 \
--value-size '10...20' \
--value-type repeated
```

This will generate a database of 100 rows, with keywords 0 to 99, and each value repeating the keyword for 10 to 20 bytes.

We view a few rows from the database with
```sh
head /tmp/database-v1.txtpb
```
which shows
```json
rows {
keyword: "0"
value: "000000000000000"
}
rows {
keyword: "1"
value: "111111111111111111"
}
rows {
keyword: "2"
```

2. To simulate an updated database, we generate a database update with a few more rows and different value sizes
```
PIRGenerateDatabase \
--output-database /tmp/database-update.txtpb \
--row-count 1000 \
--first-keyword 10000 \
--value-size '10..<25' \
--value-type repeated
```

We combine the initial database with the update to create `/tmp/database-v2.txtpb`.
```sh
cat /tmp/database-v1.txtpb /tmp/database-update.txtpb > /tmp/database-v2.txtpb
```

#### Processing
To ensure processing the update database yields the same configuration, we use the `.fixedSize` cuckoo table argument, specifying a bucket count.
A larger bucket count will leave more room for new entries, without changing the configuration.
However, a larger bucket count will also increase server runtime.
One way to choose the `bucketCount` is to start with `bucketCount : 1` and try larger `bucketCounts` until the processing succeeds.
If the processing throws a `PirError.failedToConstructCuckooTable` or logs `Failed to construct Cuckoo table`, this is an indication the chosen bucket count was too small.

> Important: We also specify a fixed shard count, rather than using `entryCountPerShard`.
We create `/tmp/config-v1-fixed-size.json` with the following contents
```json
{
"algorithm" : "mulPir",
"cuckooTableArguments" : {
"bucketCount" : {
"fixedSize" : {
"bucketCount" : 256,
}
},
"hashFunctionCount" : 2,
"maxEvictionCount" : 100,
"maxSerializedBucketSize" : 1024
},
"inputDatabase" : "/tmp/database-v1.txtpb",
"keyCompression" : "noCompression",
"outputDatabase" : "/tmp/database-v1-SHARD_ID.bin",
"outputEvaluationKeyConfig" : "/tmp/database-v1-evaluation-key-config.txtpb",
"outputPirParameters" : "/tmp/database-v1-pir-parameters-SHARD_ID.txtpb",
"rlweParameters" : "n_4096_logq_27_28_28_logt_5",
"sharding" : {
"shardCount" : 2
},
"trialsPerShard" : 1
}
```
and process the original database with
```sh
PIRProcessDatabase /tmp/config-v1-fixed-size.json
```

We hash the output PIR parameters with
```sh
cat /tmp/database-v1-pir-parameters-*.txtpb | shasum
```

We create `/tmp/config-v2-fixed-size.json` by replacing `v1` with `v2`:
```sh
sed 's/v1/v2/g' /tmp/config-v1-fixed-size.json > /tmp/config-v2-fixed-size.json
```
and process the updated database with
```sh
PIRProcessDatabase /tmp/config-v2-fixed-size.json
```
We hash the output PIR parameters of the updated database with
```sh
cat /tmp/database-v2-pir-parameters-*.txtpb | shasum
```
This should match the hash of the original database's PIR parameters.
19 changes: 9 additions & 10 deletions Sources/PrivateNearestNeighborsSearch/Server.swift
Original file line number Diff line number Diff line change
@@ -16,12 +16,14 @@ import HomomorphicEncryption

/// Private nearest neighbors server.
public struct Server<Scheme: HeScheme> {
/// Configuration.
public let config: ServerConfig<Scheme>

/// The database.
public let database: ProcessedDatabase<Scheme>

/// Configuration.
public var config: ServerConfig<Scheme> {
database.serverConfig
}

/// Configuration needed for private nearest neighbors search..
public var evaluationKeyConfiguration: EvaluationKeyConfiguration {
config.clientConfig.evaluationKeyConfig
@@ -33,16 +35,13 @@ public struct Server<Scheme: HeScheme> {
}

/// Creates a new ``Server``.
/// - Parameters:
/// - database: Processed database.
/// - config: Server configuration.
/// - Parameter database: Processed database.
/// - Throws: Error upon failure to create the server.
@inlinable
public init(database: ProcessedDatabase<Scheme>, config: ServerConfig<Scheme>) throws {
guard config.distanceMetric == .cosineSimilarity else {
throw PnnsError.wrongDistanceMetric(got: config.distanceMetric, expected: .cosineSimilarity)
public init(database: ProcessedDatabase<Scheme>) throws {
guard database.serverConfig.distanceMetric == .cosineSimilarity else {
throw PnnsError.wrongDistanceMetric(got: database.serverConfig.distanceMetric, expected: .cosineSimilarity)
}
self.config = config
self.database = database
}

2 changes: 1 addition & 1 deletion Tests/PrivateNearestNeighborsSearchTests/ClientTests.swift
Original file line number Diff line number Diff line change
@@ -201,7 +201,7 @@ final class ClientTests: XCTestCase {
let processed = try database.process(config: serverConfig)

let client = try Client(config: clientConfig, contexts: processed.contexts)
let server = try Server(database: processed, config: serverConfig)
let server = try Server(database: processed)

// We query exact matches from rows in the database
let queryVectors = Array2d(data: database.rows.prefix(queryCount).map { row in row.vector })