├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── .license_header_template ├── .licenseignore ├── .pre-commit-config.yaml ├── .spi.yml ├── .swift-format ├── .swiftformat ├── .swiftlint.yml ├── .yamllint.yml ├── Benchmarks ├── PolyBenchmark │ └── PolyBenchmark.swift ├── PrivateInformationRetrievalBenchmark │ └── PrivateInformationRetrievalBenchmark.swift ├── PrivateNearestNeighborSearchBenchmark │ └── PrivateNearestNeighborSearchBenchmark.swift └── RlweBenchmark │ └── RlweBenchmark.swift ├── CITATION.cff ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── NOTICE.txt ├── Package.resolved ├── Package.swift ├── README.md ├── SECURITY.md ├── Snippets ├── .swiftformat ├── .swiftlint.yml ├── HomomorphicEncryption │ ├── BasicsSnippet.swift │ ├── EncryptionParametersSnippet.swift │ ├── EvaluationKeySnippet.swift │ ├── MultiplicationSnippet.swift │ ├── NoiseBudgetSnippet.swift │ └── SerializationSnippet.swift └── ModularArithmetic │ └── ModularArithmeticSnippet.swift ├── Sources ├── BenchmarkUtilities │ ├── BenchmarkMetricExtensions.swift │ ├── PirBenchmarkUtilities.swift │ └── PnnsBenchmarkUtilities.swift ├── CUtil │ ├── zeroize.c │ └── zeroize.h ├── HomomorphicEncryption │ ├── Array2d.swift │ ├── Bfv │ │ ├── Bfv+Decrypt.swift │ │ ├── Bfv+Encode.swift │ │ ├── Bfv+Encrypt.swift │ │ ├── Bfv+Keys.swift │ │ ├── Bfv+Multiply.swift │ │ └── Bfv.swift │ ├── Ciphertext.swift │ ├── CoefficientPacking.swift │ ├── Context.swift │ ├── CrtComposer.swift │ ├── DoubleWidthUInt.swift │ ├── Encoding.swift │ ├── EncryptionParameters.swift │ ├── Error.swift │ ├── HeScheme.swift │ ├── HeSchemeAsync.swift │ ├── HomomorphicEncryption.docc │ │ ├── DataFormats.md │ │ ├── Examples.md │ │ ├── HomomorphicEncryption.md │ │ └── UsingSwiftHomomorphicEncryption.md │ ├── Keys.swift │ ├── Modulus.swift │ ├── NoOpScheme.swift │ ├── Plaintext.swift │ ├── PolyRq │ │ ├── Galois.swift │ │ ├── PolyCollection.swift │ │ ├── PolyContext.swift │ │ ├── PolyRq+Ntt.swift │ │ ├── PolyRq+Randomize.swift │ │ ├── PolyRq+Serialize.swift │ │ └── PolyRq.swift │ ├── Random │ │ ├── BufferedRng.swift │ │ ├── NistAes128Ctr.swift │ │ ├── NistCtrDrbg.swift │ │ └── PseudoRandomNumberGenerator.swift │ ├── RnsBaseConverter.swift │ ├── RnsTool.swift │ ├── Scalar.swift │ ├── Serialize.swift │ ├── SerializedCiphertext.swift │ ├── SerializedKeys.swift │ ├── SerializedPlaintext.swift │ ├── Util.swift │ ├── Version.swift │ └── Zeroization.swift ├── HomomorphicEncryptionProtobuf │ ├── ConversionError.swift │ ├── ConversionHe.swift │ ├── HomomorphicEncryptionProtobuf.docc │ │ └── HomomorphicEncryptionProtobuf.md │ ├── MessageExtensions.swift │ └── generated │ │ ├── README.md │ │ ├── apple_swift_homomorphic_encryption_v1_error_stddev.pb.swift │ │ └── apple_swift_homomorphic_encryption_v1_he.pb.swift ├── ModularArithmetic │ ├── Modulus.swift │ └── Scalar.swift ├── PIRGenerateDatabase │ ├── PIRGenerateDatabase.docc │ │ └── PIRGenerateDatabase.md │ └── main.swift ├── PIRProcessDatabase │ ├── PIRProcessDatabase.docc │ │ ├── PIRProcessDatabase.md │ │ └── ReusingPirParameters.md │ └── main.swift ├── PIRShardDatabase │ ├── PIRShardDatabase.docc │ │ └── PIRShardDatabase.md │ └── ShardDatabase.swift ├── PNNSGenerateDatabase │ ├── GenerateDatabase.swift │ └── PNNSGenerateDatabase.docc │ │ └── PNNSGenerateDatabase.md ├── PNNSProcessDatabase │ ├── PNNSProcessDatabase.docc │ │ └── PNNSProcessDatabase.md │ └── ProcessDatabase.swift ├── PrivateInformationRetrieval │ ├── CuckooTable.swift │ ├── Error.swift │ ├── HashBucket.swift │ ├── IndexPirProtocol.swift │ ├── KeywordDatabase.swift │ ├── KeywordPirProtocol.swift │ ├── MulPir.swift │ ├── PirUtil.swift │ ├── PrivateInformationRetrieval.docc │ │ ├── EncodingPipeline.md │ │ ├── ParameterTuning.md │ │ ├── PrivateInformationRetrieval.md │ │ └── ReusingPirParameters.md │ ├── SymmetricPIRProtocol.swift │ ├── SymmetricPirDatabase.swift │ └── Util.swift ├── PrivateInformationRetrievalProtobuf │ ├── ConversionApi.swift │ ├── ConversionError.swift │ ├── ConversionPir.swift │ ├── PrivateInformationRetrievalProtobuf.docc │ │ └── PrivateInformationRetrievalProtobuf.md │ ├── generated │ │ ├── README.md │ │ ├── apple_swift_homomorphic_encryption_api_pir_v1_api.pb.swift │ │ ├── apple_swift_homomorphic_encryption_api_pir_v1_pir.pb.swift │ │ ├── apple_swift_homomorphic_encryption_api_shared_v1_api_shared.pb.swift │ │ ├── apple_swift_homomorphic_encryption_pir_v1_pir.pb.swift │ │ ├── apple_swift_homomorphic_encryption_pir_v1_pir_algorithm.pb.swift │ │ └── apple_swift_homomorphic_encryption_pir_v1_pir_database.pb.swift │ └── protobuf_module_mappings.txtpb ├── PrivateNearestNeighborSearch │ ├── CiphertextMatrix.swift │ ├── Client.swift │ ├── Config.swift │ ├── Database.swift │ ├── Error.swift │ ├── MatrixMultiplication.swift │ ├── PlaintextMatrix.swift │ ├── PnnsProtocol.swift │ ├── PrivateNearestNeighborSearch.docc │ │ └── PrivateNearestNeighborSearch.md │ ├── ProcessedDatabase.swift │ ├── SerializedCiphertextMatrix.swift │ ├── SerializedPlaintextMatrix.swift │ ├── Server.swift │ └── Util.swift ├── PrivateNearestNeighborSearchProtobuf │ ├── ConversionApi.swift │ ├── ConversionError.swift │ ├── ConversionPnns.swift │ ├── PrivateNearestNeighborSearchProtobuf.docc │ │ └── PrivateNearestNeighborSearch.md │ ├── generated │ │ ├── README.md │ │ ├── apple_swift_homomorphic_encryption_api_pnns_v1_api.pb.swift │ │ ├── apple_swift_homomorphic_encryption_api_pnns_v1_pnns.pb.swift │ │ ├── apple_swift_homomorphic_encryption_api_shared_v1_api_shared.pb.swift │ │ ├── apple_swift_homomorphic_encryption_pnns_v1_pnns.pb.swift │ │ ├── apple_swift_homomorphic_encryption_pnns_v1_pnns_client_config.pb.swift │ │ ├── apple_swift_homomorphic_encryption_pnns_v1_pnns_database.pb.swift │ │ ├── apple_swift_homomorphic_encryption_pnns_v1_pnns_distance_metric.pb.swift │ │ ├── apple_swift_homomorphic_encryption_pnns_v1_pnns_matrix_packing.pb.swift │ │ ├── apple_swift_homomorphic_encryption_pnns_v1_pnns_processed_database.pb.swift │ │ └── apple_swift_homomorphic_encryption_pnns_v1_pnns_server_config.pb.swift │ └── protobuf_module_mappings.txtpb └── TestUtilities │ ├── HeApiTestUtils.swift │ ├── PirUtilities │ ├── ExpansionTests.swift │ ├── IndexPirTests.swift │ ├── KeywordPirTests.swift │ ├── MulPirTests.swift │ ├── PirTestUtils.swift │ └── SymmetricPirTests.swift │ ├── PnnsUtilities │ ├── CiphertextMatrixTests.swift │ ├── ClientTests.swift │ ├── CosineSimilarityTests.swift │ ├── DatabaseTests.swift │ ├── MatrixMultiplicationTests.swift │ ├── PlaintextMatrixTests.swift │ └── PnnsUtils.swift │ └── TestUtilities.swift ├── Tests ├── HomomorphicEncryptionProtobufTests │ └── ConversionTests.swift ├── HomomorphicEncryptionTests │ ├── Array2dTests.swift │ ├── CoefficientPackingTests.swift │ ├── DoubleWidthUIntTests.swift │ ├── EncryptionParametersTests.swift │ ├── HeAPITests.swift │ ├── NttTests.swift │ ├── PolyRqTests │ │ ├── GaloisTests.swift │ │ ├── PolyContextTests.swift │ │ ├── PolyRq+RandomizeTests.swift │ │ ├── PolyRq+SerializeTests.swift │ │ └── PolyRqTests.swift │ ├── RandomTests │ │ ├── BufferedRngTests.swift │ │ ├── NistCtrDrbgTests.swift │ │ └── PseudoRandomNumberGeneratorTests.swift │ ├── RnsBaseConverterTests.swift │ ├── RnsToolTests.swift │ ├── ScalarTests.swift │ ├── SerializationTests.swift │ ├── UtilTests.swift │ └── ZeroizationTests.swift ├── PIRGenerateDatabaseTests │ └── PIRGenerateDatabaseTests.swift ├── PIRProcessDatabaseTests │ └── ProcessDatabaseTests.swift ├── PrivateInformationRetrievalProtobufTests │ └── ConversionTests.swift ├── PrivateInformationRetrievalTests │ ├── CuckooTableTests.swift │ ├── ExpansionTests.swift │ ├── HashBucketTests.swift │ ├── IndexPirTests.swift │ ├── KeywordDatabaseTests.swift │ ├── KeywordPirTests.swift │ ├── MulPirTests.swift │ ├── SymmetricPIRTests.swift │ └── UtilTests.swift ├── PrivateNearestNeighborSearchProtobufTests │ └── ConversionTests.swift └── PrivateNearestNeighborSearchTests │ ├── CiphertextMatrixTests.swift │ ├── ClientTests.swift │ ├── CosineSimilarityTests.swift │ ├── DatabaseTests.swift │ ├── MatrixMultiplicationTests.swift │ ├── PlaintextMatrixTests.swift │ └── UtilsTests.swift ├── Utilities └── generate-protobuf-files.sh ├── ci ├── install-lockwood-swiftformat.sh ├── install-swiftlint.sh └── run-apple-swift-format.sh └── copyright-header.txt /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | permissions: 3 | contents: read 4 | on: 5 | workflow_dispatch: 6 | push: 7 | branches: ["main", "release/**"] 8 | pull_request: 9 | branches: ["main", "release/**"] 10 | types: [opened, reopened, synchronize] 11 | # Pushing changes to PR stops currently-running CI 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.ref }} 14 | cancel-in-progress: true 15 | # Keep versions in sync with README 16 | env: 17 | SWIFTLINT_VERSION: 0.58.2 18 | SWIFTFORMAT_VERSION: 0.55.5 19 | SWIFT_HOMOMORPHIC_ENCRYPTION_ENABLE_BENCHMARKING: 1 20 | jobs: 21 | soundness: 22 | name: soundness 23 | uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main 24 | with: 25 | # https://github.com/swiftlang/swift-package-manager/issues/8103 26 | api_breakage_check_enabled: false 27 | format_check_enabled: false 28 | tests: 29 | name: swifttests 30 | uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main 31 | with: 32 | enable_windows_checks: false 33 | linux_exclude_swift_versions: "[{\"swift_version\": \"5.9\"}, {\"swift_version\": \"5.10\"}]" 34 | linux_pre_build_command: "apt-get update && apt-get install -y libjemalloc-dev" 35 | linux_build_command: > 36 | swift test --configuration release; 37 | for filename in $(find Snippets -name \*.swift); do 38 | basename=$(basename "$filename" .swift) 39 | swift run --configuration release ${basename} 40 | done 41 | pre-commit: 42 | timeout-minutes: 1 43 | runs-on: ubuntu-22.04 44 | steps: 45 | - name: Checkout repository 46 | uses: actions/checkout@v4 47 | - name: Install pre-commit 48 | run: pip install pre-commit 49 | - name: Pre-commit checks 50 | # CI will commit to `main` 51 | # swiftformat, swiftlint and license checks tested separately 52 | run: > 53 | SKIP=no-commit-to-branch,lockwood-swiftformat,swiftlint,check-doc-comments,insert-license 54 | pre-commit run --all-files 55 | insert-license: 56 | timeout-minutes: 1 57 | runs-on: ubuntu-22.04 58 | steps: 59 | - name: Checkout repository 60 | uses: actions/checkout@v4 61 | with: 62 | fetch-depth: 2 63 | - name: Install pre-commit 64 | run: pip install pre-commit 65 | - name: List changed files 66 | run: git diff --name-only HEAD~1 67 | - name: Run license check 68 | run: pre-commit run insert-license --files $(git diff --name-only HEAD~1) 69 | lint: 70 | timeout-minutes: 15 71 | runs-on: ubuntu-22.04 72 | steps: 73 | - name: Checkout repository 74 | uses: actions/checkout@v4 75 | - name: Cache SwiftLint 76 | id: cache-swiftlint 77 | uses: actions/cache@v4 78 | with: 79 | path: /tmp/swiftlint/SwiftLint/.build/release/swiftlint 80 | key: ${{ runner.os }}-swiftlint-${{ env.SWIFTLINT_VERSION }} 81 | - name: Install SwiftLint 82 | if: steps.cache-swiftlint.outputs.cache-hit != 'true' 83 | run: | 84 | ci/install-swiftlint.sh 85 | - name: Run SwiftLint 86 | run: /tmp/swiftlint/SwiftLint/.build/release/swiftlint lint --strict . 87 | lockwood-swiftformat: 88 | timeout-minutes: 5 89 | runs-on: ubuntu-22.04 90 | steps: 91 | - name: Checkout repository 92 | uses: actions/checkout@v4 93 | - name: Cache SwiftFormat 94 | id: cache-swiftformat 95 | uses: actions/cache@v4 96 | with: 97 | path: /tmp/swiftformat/SwiftFormat/.build/release/swiftformat 98 | key: ${{ runner.os }}-swiftformat-${{ env.SWIFTFORMAT_VERSION }} 99 | - name: Install Lockwood SwiftFormat 100 | if: steps.cache-swiftformat.outputs.cache-hit != 'true' 101 | run: | 102 | ci/install-lockwood-swiftformat.sh 103 | - name: Run SwiftFormat 104 | run: /tmp/swiftformat/SwiftFormat/.build/release/swiftformat --strict . 105 | check-doc-comments: 106 | timeout-minutes: 5 107 | runs-on: ubuntu-22.04 108 | steps: 109 | - name: Checkout repository 110 | uses: actions/checkout@v4 111 | - name: Check documentation comments 112 | run: ci/run-apple-swift-format.sh 113 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.xcuserdatad 2 | *.xcuserstate 3 | .DS_Store 4 | .benchmarkBaselines/ 5 | .build/ 6 | .docc-build/ 7 | .index-build/ 8 | .swiftpm 9 | .vscode/ 10 | xcuserdata/ 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "swift-homomorphic-encryption-protobuf"] 2 | path = swift-homomorphic-encryption-protobuf 3 | url = https://github.com/apple/swift-homomorphic-encryption-protobuf 4 | -------------------------------------------------------------------------------- /.license_header_template: -------------------------------------------------------------------------------- 1 | @@ Copyright YEARS 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 | -------------------------------------------------------------------------------- /.licenseignore: -------------------------------------------------------------------------------- 1 | **/.swiftformat 2 | *.pb.swift 3 | *.txtpb 4 | .gitignore 5 | .gitmodules 6 | .swiftformat 7 | CITATION.cff 8 | Package.resolved 9 | Package.swift 10 | Snippets/* 11 | swift-homomorphic-encryption-protobuf 12 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v5.0.0 4 | hooks: 5 | - id: check-case-conflict 6 | - id: check-merge-conflict 7 | - id: check-symlinks 8 | - id: fix-byte-order-marker 9 | - id: check-toml 10 | - id: check-yaml 11 | args: [--allow-multiple-documents] 12 | - id: end-of-file-fixer 13 | - id: mixed-line-ending 14 | - id: no-commit-to-branch 15 | args: [--branch, main] 16 | - id: trailing-whitespace 17 | - repo: https://github.com/crate-ci/typos 18 | rev: v1.30.3 19 | hooks: 20 | - id: typos 21 | - repo: https://github.com/Lucas-C/pre-commit-hooks 22 | rev: v1.5.5 23 | hooks: 24 | - id: insert-license 25 | name: insert-license 26 | types_or: [c, swift, proto] 27 | args: 28 | - --license-filepath 29 | - copyright-header.txt 30 | - --comment-style 31 | - // 32 | - --allow-past-years 33 | - --use-current-year 34 | - --detect-license-in-X-top-lines=11 35 | - id: insert-license 36 | name: insert-license-sh 37 | types_or: [shell] 38 | args: 39 | - --license-filepath 40 | - copyright-header.txt 41 | - --comment-style 42 | - "##" 43 | - --allow-past-years 44 | - --use-current-year 45 | - repo: local 46 | hooks: 47 | # https://github.com/nicklockwood/SwiftFormat 48 | - id: lockwood-swiftformat 49 | name: lockwood-swiftformat 50 | entry: swiftformat 51 | language: system 52 | types: [swift] 53 | # https://github.com/apple/swift-format 54 | - id: apple-swift-format 55 | name: apple-swift-format 56 | entry: ci/run-apple-swift-format.sh 57 | language: script 58 | pass_filenames: false 59 | # https://github.com/realm/SwiftLint 60 | - id: swiftlint 61 | name: swiftlint 62 | entry: swiftlint lint --strict 63 | language: system 64 | exclude: DoubleWidthUInt.swift|.pb.swift 65 | types: [swift] 66 | -------------------------------------------------------------------------------- /.spi.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | builder: 3 | configs: 4 | - documentation_targets: 5 | - HomomorphicEncryption 6 | - HomomorphicEncryptionProtobuf 7 | - PIRGenerateDatabase 8 | - PIRProcessDatabase 9 | - PIRShardDatabase 10 | - PNNSGenerateDatabase 11 | - PNNSProcessDatabase 12 | - PrivateInformationRetrieval 13 | - PrivateInformationRetrievalProtobuf 14 | - PrivateNearestNeighborSearch 15 | - PrivateNearestNeighborSearchProtobuf 16 | swift_version: 6.0 17 | -------------------------------------------------------------------------------- /.swift-format: -------------------------------------------------------------------------------- 1 | { 2 | "rules" : { 3 | "BeginDocumentationCommentWithOneLineSummary" : true, 4 | "UseTripleSlashForDocumentationComments" : true, 5 | "ValidateDocumentationComments" : true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.swiftformat: -------------------------------------------------------------------------------- 1 | --closingparen same-line 2 | --exclude **/*.pb.swift 3 | --extensionacl on-declarations 4 | --ifdef no-indent 5 | --indent 4 6 | --indentstrings true 7 | --maxwidth 120 8 | --nospaceoperators ..<, ... 9 | --ranges no-space 10 | --self init-only 11 | --swiftversion 6.0 12 | -------------------------------------------------------------------------------- /.yamllint.yml: -------------------------------------------------------------------------------- 1 | extends: default 2 | 3 | rules: 4 | line-length: false 5 | document-start: false 6 | truthy: 7 | check-keys: false # Otherwise we get a false positive on GitHub action's `on` key 8 | 9 | ignore: 10 | - .build 11 | - .index-build 12 | - swift-homomorphic-encryption-protobuf 13 | -------------------------------------------------------------------------------- /Benchmarks/PrivateInformationRetrievalBenchmark/PrivateInformationRetrievalBenchmark.swift: -------------------------------------------------------------------------------- 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 | // Benchmarks for Pir functions. 16 | // These benchmarks can be triggered with 17 | // SWIFT_HOMOMORPHIC_ENCRYPTION_ENABLE_BENCHMARKING=1 swift package benchmark --target PIRBenchmark 18 | 19 | import _BenchmarkUtilities 20 | import HomomorphicEncryption 21 | 22 | nonisolated(unsafe) let benchmarks: () -> Void = { 23 | pirProcessBenchmark(Bfv.self)() 24 | pirProcessBenchmark(Bfv.self)() 25 | 26 | indexPirBenchmark(Bfv.self)() 27 | indexPirBenchmark(Bfv.self)() 28 | 29 | keywordPirBenchmark(Bfv.self)() 30 | keywordPirBenchmark(Bfv.self)() 31 | } 32 | -------------------------------------------------------------------------------- /Benchmarks/PrivateNearestNeighborSearchBenchmark/PrivateNearestNeighborSearchBenchmark.swift: -------------------------------------------------------------------------------- 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 | // Benchmarks for Pnns functions. 16 | // These benchmarks can be triggered with 17 | // SWIFT_HOMOMORPHIC_ENCRYPTION_ENABLE_BENCHMARKING=1 swift package benchmark --target PNNSBenchmark 18 | 19 | import _BenchmarkUtilities 20 | import HomomorphicEncryption 21 | 22 | nonisolated(unsafe) let benchmarks: () -> Void = { 23 | pnnsProcessBenchmark(Bfv.self)() 24 | pnnsProcessBenchmark(Bfv.self)() 25 | 26 | cosineSimilarityBenchmark(Bfv.self)() 27 | cosineSimilarityBenchmark(Bfv.self)() 28 | } 29 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | authors: 3 | - name: Apple Inc. 4 | - name: Swift Homomorphic Encryption project authors 5 | title: Swift Homomorphic Encryption 6 | url: https://github.com/apple/swift-homomorphic-encryption 7 | license: Apache-2.0 8 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Default codeowner for all files 2 | * @fboemer @karulont @RuiyuZhu 3 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | 3 | ### Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our 7 | project and our community a harassment-free experience for everyone, 8 | regardless of age, body size, disability, ethnicity, sex 9 | characteristics, gender identity and expression, level of experience, 10 | education, socio-economic status, nationality, personal appearance, 11 | race, religion, or sexual identity and orientation. 12 | 13 | ### Our Standards 14 | 15 | Examples of behavior that contributes to creating a positive environment 16 | include: 17 | 18 | * Using welcoming and inclusive language 19 | * Being respectful of differing viewpoints and experiences 20 | * Gracefully accepting constructive criticism 21 | * Focusing on what is best for the community 22 | * Showing empathy towards other community members 23 | 24 | Examples of unacceptable behavior by participants include: 25 | 26 | * The use of sexualized language or imagery and unwelcome sexual 27 | attention or advances 28 | * Trolling, insulting/derogatory comments, and personal or political 29 | attacks 30 | * Public or private harassment 31 | * Publishing others’ private information, such as a physical or 32 | electronic address, without explicit permission 33 | * Other conduct which could reasonably be considered inappropriate in a 34 | professional setting 35 | 36 | ### Our Responsibilities 37 | 38 | Project maintainers are responsible for clarifying the standards of 39 | acceptable behavior and are expected to take appropriate and fair 40 | corrective action in response to any instances of unacceptable behavior. 41 | 42 | Project maintainers have the right and responsibility to remove, edit, 43 | or reject comments, commits, code, wiki edits, issues, and other 44 | contributions that are not aligned to this Code of Conduct, or to ban 45 | temporarily or permanently any contributor for other behaviors that they 46 | deem inappropriate, threatening, offensive, or harmful. 47 | 48 | ### Scope 49 | 50 | This Code of Conduct applies within all project spaces, and it also 51 | applies when an individual is representing the project or its community 52 | in public spaces. Examples of representing a project or community 53 | include using an official project e-mail address, posting via an 54 | official social media account, or acting as an appointed representative 55 | at an online or offline event. Representation of a project may be 56 | further defined and clarified by project maintainers. 57 | 58 | ### Enforcement 59 | 60 | Instances of abusive, harassing, or otherwise unacceptable behavior may 61 | be reported by contacting the open source team at 62 | opensource-conduct@group.apple.com. All complaints will be reviewed and 63 | investigated and will result in a response that is deemed necessary and 64 | appropriate to the circumstances. The project team is obligated to 65 | maintain confidentiality with regard to the reporter of an incident. 66 | Further details of specific enforcement policies may be posted 67 | separately. 68 | 69 | Project maintainers who do not follow or enforce the Code of Conduct in 70 | good faith may face temporary or permanent repercussions as determined 71 | by other members of the project’s leadership. 72 | 73 | ### Attribution 74 | 75 | This Code of Conduct is adapted from the 76 | [Contributor Covenant](https://www.contributor-covenant.org), version 1.4, 77 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 78 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing to Swift Homomorphic Encryption 2 | 3 | Welcome to the Swift Homomorphic Encryption community! Thanks for your interest in contributing. We would love contributions in the form of feedback via [GitHub Issues](https://github.com/apple/swift-homomorphic-encryption/issues), pull requests with new features, bug fixes, documentation (including fixing typos!), and your ideas. If it's a big change, please start with an Issue. 4 | 5 | ### Pull Requests: 6 | Before making a commit for a pull request, please run `pre-commit install`. 7 | Then on each commit some basic formatting checks will be run. 8 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2024 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 | ------------------------------------------------------------------------------- 16 | 17 | This product contains modifications of "DoubleWidth.swift.gyb" from https://github.com/swiftlang/swift/blob/fc23eef2d2b2e42116ac94a6cc0d0d2cc96688f5/test/Prototypes/DoubleWidth.swift.gyb in: 18 | - Sources/HomomorphicEncryption/DoubleWidthUInt.swift 19 | - Tests/HomomorphicEncryptionTests/DoubleWidthUIntTests.swift 20 | 21 | * LICENSE (Apache 2.0 with Runtime Library Exception) 22 | * https://www.swift.org/legal/license.html 23 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "61d8aacb16c7a03dacafd8211476f27983fcc16b9cacad985296a457df03603c", 3 | "pins" : [ 4 | { 5 | "identity" : "swift-algorithms", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/apple/swift-algorithms", 8 | "state" : { 9 | "revision" : "87e50f483c54e6efd60e885f7f5aa946cee68023", 10 | "version" : "1.2.1" 11 | } 12 | }, 13 | { 14 | "identity" : "swift-argument-parser", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/apple/swift-argument-parser.git", 17 | "state" : { 18 | "revision" : "41982a3656a71c768319979febd796c6fd111d5c", 19 | "version" : "1.5.0" 20 | } 21 | }, 22 | { 23 | "identity" : "swift-asn1", 24 | "kind" : "remoteSourceControl", 25 | "location" : "https://github.com/apple/swift-asn1.git", 26 | "state" : { 27 | "revision" : "ae33e5941bb88d88538d0a6b19ca0b01e6c76dcf", 28 | "version" : "1.3.1" 29 | } 30 | }, 31 | { 32 | "identity" : "swift-crypto", 33 | "kind" : "remoteSourceControl", 34 | "location" : "https://github.com/apple/swift-crypto.git", 35 | "state" : { 36 | "revision" : "a6ce32a18b81b04ce7e897d1d98df6eb2da04786", 37 | "version" : "3.12.2" 38 | } 39 | }, 40 | { 41 | "identity" : "swift-docc-plugin", 42 | "kind" : "remoteSourceControl", 43 | "location" : "https://github.com/swiftlang/swift-docc-plugin", 44 | "state" : { 45 | "revision" : "85e4bb4e1cd62cec64a4b8e769dcefdf0c5b9d64", 46 | "version" : "1.4.3" 47 | } 48 | }, 49 | { 50 | "identity" : "swift-docc-symbolkit", 51 | "kind" : "remoteSourceControl", 52 | "location" : "https://github.com/swiftlang/swift-docc-symbolkit", 53 | "state" : { 54 | "revision" : "b45d1f2ed151d057b54504d653e0da5552844e34", 55 | "version" : "1.0.0" 56 | } 57 | }, 58 | { 59 | "identity" : "swift-log", 60 | "kind" : "remoteSourceControl", 61 | "location" : "https://github.com/apple/swift-log.git", 62 | "state" : { 63 | "revision" : "3d8596ed08bd13520157f0355e35caed215ffbfa", 64 | "version" : "1.6.3" 65 | } 66 | }, 67 | { 68 | "identity" : "swift-numerics", 69 | "kind" : "remoteSourceControl", 70 | "location" : "https://github.com/apple/swift-numerics", 71 | "state" : { 72 | "revision" : "e0ec0f5f3af6f3e4d5e7a19d2af26b481acb6ba8", 73 | "version" : "1.0.3" 74 | } 75 | }, 76 | { 77 | "identity" : "swift-protobuf", 78 | "kind" : "remoteSourceControl", 79 | "location" : "https://github.com/apple/swift-protobuf", 80 | "state" : { 81 | "revision" : "d72aed98f8253ec1aa9ea1141e28150f408cf17f", 82 | "version" : "1.29.0" 83 | } 84 | } 85 | ], 86 | "version" : 3 87 | } 88 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | If you believe that you have discovered a security or privacy vulnerability in our open source software, please report it to us using the GitHub private vulnerability feature. Reports should include specific product and software version(s) that you believe are affected; a technical description of the behavior that you observed and the behavior that you expected; the steps required to reproduce the issue; and a proof of concept or exploit. 2 | 3 | Reports concerning known, publicly disclosed CVEs can be submitted as normal issues to this project. Output from automated security scans or fuzzers must include additional context demonstrating the vulnerability with a proof of concept or working exploit. Application crashes due to malformed inputs are typically not treated as security vulnerabilities, unless they are shown to also impact other processes on the system. 4 | 5 | While we welcome reports for open source software projects, they are not eligible for Apple Security Bounties. 6 | -------------------------------------------------------------------------------- /Snippets/.swiftformat: -------------------------------------------------------------------------------- 1 | --maxwidth 87 # Copyright header is 87 characters 2 | -------------------------------------------------------------------------------- /Snippets/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | line_length: 2 | # Copyright header is 87 characters 3 | warning: 87 4 | -------------------------------------------------------------------------------- /Snippets/HomomorphicEncryption/BasicsSnippet.swift: -------------------------------------------------------------------------------- 1 | // Example showing the basics. 2 | 3 | // snippet.hide 4 | // Copyright 2024-2025 Apple Inc. and the Swift Homomorphic Encryption project authors 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // snippet.show 18 | 19 | // snippet.encryption 20 | import HomomorphicEncryption 21 | 22 | // We start by choosing some encryption parameters for the Bfv scheme. 23 | // *These encryption parameters are insecure, suitable for testing only.* 24 | let encryptParams = 25 | try EncryptionParameters(from: .insecure_n_8_logq_5x18_logt_5) 26 | // Perform pre-computation for HE computation with these parameters. 27 | let context = try Context>(encryptionParameters: encryptParams) 28 | 29 | // We encode N values using coefficient encoding. 30 | let values: [UInt64] = [8, 5, 12, 12, 15, 0, 8, 5] 31 | let plaintext: Bfv.CoeffPlaintext = try context.encode( 32 | values: values, 33 | format: .coefficient) 34 | 35 | // We generate a secret key and use it to encrypt the plaintext. 36 | let secretKey = try context.generateSecretKey() 37 | var ciphertext = try plaintext.encrypt(using: secretKey) 38 | 39 | // Decrypting the plaintext yields the original values. 40 | var decrypted = try ciphertext.decrypt(using: secretKey) 41 | var decoded: [UInt64] = try decrypted.decode(format: .coefficient) 42 | precondition(decoded == values) 43 | 44 | // Mixing formats between encoding and decoding yields incorrect results. 45 | decoded = try decrypted.decode(format: .simd) 46 | precondition(decoded != values) 47 | 48 | // snippet.addition 49 | // We add the ciphertext with the plaintext, yielding another ciphertext. 50 | var sum = try ciphertext + plaintext 51 | // The ciphertext decrypts to the element-wise sum of the ciphertext's 52 | // and plaintext's values, mod 17, the plaintext modulus. 53 | precondition(encryptParams.plaintextModulus == 17) 54 | var plaintextSum = try sum.decrypt(using: secretKey) 55 | decoded = try plaintextSum.decode(format: .coefficient) 56 | precondition(decoded == [16, 10, 7, 7, 13, 0, 16, 10]) 57 | 58 | // We can also add ciphertexts. 59 | try sum += ciphertext 60 | plaintextSum = try sum.decrypt(using: secretKey) 61 | decoded = try plaintextSum.decode(format: .coefficient) 62 | precondition(decoded == [7, 15, 2, 2, 11, 0, 7, 15]) 63 | // snippet.end 64 | 65 | // snippet.subtraction 66 | // We can subtract a plaintext from a ciphertext. 67 | try sum -= plaintext 68 | plaintextSum = try sum.decrypt(using: secretKey) 69 | decoded = try plaintextSum.decode(format: .coefficient) 70 | precondition(decoded == [16, 10, 7, 7, 13, 0, 16, 10]) 71 | 72 | // We can also subtract a ciphertext from a ciphertext. 73 | try sum -= ciphertext 74 | plaintextSum = try sum.decrypt(using: secretKey) 75 | decoded = try plaintextSum.decode(format: .coefficient) 76 | precondition(decoded == [8, 5, 12, 12, 15, 0, 8, 5]) 77 | 78 | // One special case is when subtracting a ciphertext from itself. 79 | // This yields a "transparent ciphertext", which reveals the underlying 80 | // plaintext to any observer. The observed value in this case is zero. 81 | try sum -= sum 82 | precondition(sum.isTransparent()) 83 | -------------------------------------------------------------------------------- /Snippets/HomomorphicEncryption/MultiplicationSnippet.swift: -------------------------------------------------------------------------------- 1 | // Example showing HE multiplication. 2 | 3 | // snippet.hide 4 | // Copyright 2024-2025 Apple Inc. and the Swift Homomorphic Encryption project authors 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // snippet.show 18 | 19 | import HomomorphicEncryption 20 | 21 | // For this example, we make use of SIMD encoding. In SIMD format, addition and 22 | // multiplication are performed element-wise on the encoded values. By contrast, 23 | // in coefficient format, addition is element-wise, but multiplication is a 24 | // negacyclic convolution of coefficients. 25 | precondition(PredefinedRlweParameters.insecure_n_8_logq_5x18_logt_5 26 | .supportsSimdEncoding) 27 | 28 | // We start by choosing some encryption parameters for the Bfv scheme. 29 | // When all the coefficient moduli are small enough, using UInt32 can yield 30 | // significant speedups compared to UInt64. 31 | precondition(PredefinedRlweParameters.insecure_n_8_logq_5x18_logt_5 32 | .supportsScalar(UInt32.self)) 33 | let encryptParams = 34 | try EncryptionParameters(from: .insecure_n_8_logq_5x18_logt_5) 35 | precondition(encryptParams.plaintextModulus == 17) 36 | // Perform pre-computation for HE computation with these parameters. 37 | let context = try Context>(encryptionParameters: encryptParams) 38 | 39 | // We don't need to use all the slots in the encoding. 40 | // However, performing HE operations on ciphertexts with fewer slots doesn't give 41 | // any runtime savings. 42 | let valueCount = encryptParams.polyDegree / 2 43 | let values = (0...CoeffPlaintext = try context.encode( 45 | values: values, 46 | format: .simd) 47 | 48 | // We generate a secret key and encrypt the plaintext. 49 | let secretKey = try context.generateSecretKey() 50 | let ciphertext = try plaintext.encrypt(using: secretKey) 51 | 52 | // Multiplication requires the ciphertext and plaintext to be in Evaluation 53 | // format. 54 | let evalCiphertext = try ciphertext.convertToEvalFormat() 55 | let evalPlaintext = try plaintext.convertToEvalFormat() 56 | 57 | // The result decrypts to an element-wise product of the values, 58 | // mod the plaintext modulus, 17 in this case. 59 | let product = try evalCiphertext * evalPlaintext 60 | var plaintextProduct = try product.decrypt(using: secretKey) 61 | var decoded: [UInt32] = try plaintextProduct.decode(format: .simd) 62 | precondition(Array(decoded[0..( 18 | unsignedV1: T1, 19 | signedV2: T2) -> (T1, T2) 20 | { 21 | let reminder1 = unsignedV1.toRemainder(11, variableTime: true) 22 | let lessThan1 = reminder1.constantTimeLessThan(5) 23 | let reminder2 = signedV2.toRemainder(97, variableTime: true) 24 | let lessThan2 = reminder2.multiplyHigh(48) 25 | return (lessThan1, lessThan2) 26 | } 27 | 28 | let v1: UInt32 = 5 29 | let v2: Int32 = -33 30 | 31 | precondition(useProtocols(unsignedV1: v1, signedV2: v2) == (0, 0)) 32 | 33 | let modulus: UInt32 = 13 34 | let singleWordModulus = ReduceModulus( 35 | modulus: modulus, 36 | bound: ReduceModulus.InputBound.SingleWord, 37 | variableTime: true) 38 | let doubleWordModulus = ReduceModulus( 39 | modulus: modulus, 40 | bound: ReduceModulus.InputBound.DoubleWord, 41 | variableTime: true) 42 | let reduceProductModulus = ReduceModulus( 43 | modulus: modulus, 44 | bound: ReduceModulus.InputBound.ModulusSquared, 45 | variableTime: true) 46 | let divisionModulus = DivisionModulus( 47 | modulus: modulus, 48 | singleFactor: UInt32(991_146_300), 49 | doubleFactor: UInt64(4_256_940_940_086_819_604)) 50 | let m = Modulus(modulus: modulus, 51 | singleWordModulus: singleWordModulus, 52 | doubleWordModulus: doubleWordModulus, 53 | reduceProductModulus: reduceProductModulus, 54 | divisionModulus: divisionModulus) 55 | 56 | precondition(m.multiplyMod(4, 5) == 7) 57 | print("4 * 5 mod \(modulus) = \(m.multiplyMod(4, 5))") 58 | -------------------------------------------------------------------------------- /Sources/BenchmarkUtilities/BenchmarkMetricExtensions.swift: -------------------------------------------------------------------------------- 1 | // Copyright 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 Benchmark 16 | 17 | let noiseBudgetScale = 10 18 | 19 | extension BenchmarkMetric { 20 | static var querySize: Self { .custom("Query byte size") } 21 | static var queryCiphertextCount: Self { .custom("Query ciphertext count") } 22 | static var evaluationKeySize: Self { .custom("Evaluation key byte size") } 23 | static var evaluationKeyCount: Self { .custom("Evaluation key count") } 24 | static var responseSize: Self { .custom("Response byte size") } 25 | static var responseCiphertextCount: Self { .custom("Response ciphertext count") } 26 | static var noiseBudget: Self { .custom("Noise budget x \(noiseBudgetScale)") } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/CUtil/zeroize.c: -------------------------------------------------------------------------------- 1 | // Copyright 2024 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 | #include 16 | #include 17 | 18 | void c_zeroize(void *s, size_t n) 19 | { 20 | // We'd prefer to use memset_s if possible, since the compiler shouldn't optimize that away. 21 | // If it's not available, we use a memory barrier as a best effort to prevent optimization 22 | memset(s, 0, n); 23 | __asm__ __volatile__("" : : "r"(s) : "memory"); 24 | } 25 | -------------------------------------------------------------------------------- /Sources/CUtil/zeroize.h: -------------------------------------------------------------------------------- 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 | #ifndef ZEROIZE_H 16 | #define ZEROIZE_H 17 | 18 | #include 19 | 20 | #ifdef __cplusplus 21 | extern "C" { 22 | #endif 23 | void c_zeroize(void *s, size_t n); 24 | #ifdef __cplusplus 25 | } 26 | #endif 27 | 28 | #endif /* ZEROIZE_H */ 29 | -------------------------------------------------------------------------------- /Sources/HomomorphicEncryption/Bfv/Bfv+Encode.swift: -------------------------------------------------------------------------------- 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 | extension Bfv { 16 | @inlinable 17 | // swiftlint:disable:next missing_docs attributes 18 | public static func encodeSimdDimensions(for parameters: EncryptionParameters) -> SimdEncodingDimensions? { 19 | guard parameters.supportsSimdEncoding else { 20 | return nil 21 | } 22 | return SimdEncodingDimensions(rowCount: 2, columnCount: parameters.polyDegree / 2) 23 | } 24 | 25 | @inlinable 26 | // swiftlint:disable:next missing_docs attributes 27 | public static func encode(context: Context>, values: some Collection, 28 | format: EncodeFormat) throws -> CoeffPlaintext 29 | { 30 | try context.encode(values: values, format: format) 31 | } 32 | 33 | @inlinable 34 | // swiftlint:disable:next missing_docs attributes 35 | public static func encode(context: Context>, signedValues: some Collection, 36 | format: EncodeFormat) throws -> CoeffPlaintext 37 | { 38 | try context.encode(signedValues: signedValues, format: format) 39 | } 40 | 41 | @inlinable 42 | // swiftlint:disable:next missing_docs attributes 43 | public static func encode(context: Context>, values: some Collection, format: EncodeFormat, 44 | moduliCount: Int?) throws -> EvalPlaintext 45 | { 46 | let coeffPlaintext = try Self.encode(context: context, values: values, format: format) 47 | return try coeffPlaintext.convertToEvalFormat(moduliCount: moduliCount) 48 | } 49 | 50 | @inlinable 51 | // swiftlint:disable:next missing_docs attributes 52 | public static func encode( 53 | context: Context>, 54 | signedValues: some Collection, 55 | format: EncodeFormat, 56 | moduliCount: Int?) throws -> EvalPlaintext 57 | { 58 | let coeffPlaintext = try Self.encode(context: context, signedValues: signedValues, format: format) 59 | return try coeffPlaintext.convertToEvalFormat(moduliCount: moduliCount) 60 | } 61 | 62 | @inlinable 63 | // swiftlint:disable:next missing_docs attributes 64 | public static func decodeCoeff(plaintext: CoeffPlaintext, format: EncodeFormat) throws -> [Scalar] { 65 | try plaintext.context.decode(plaintext: plaintext, format: format) 66 | } 67 | 68 | @inlinable 69 | // swiftlint:disable:next missing_docs attributes 70 | public static func decodeCoeff(plaintext: CoeffPlaintext, format: EncodeFormat) throws -> [SignedScalar] { 71 | try plaintext.context.decode(plaintext: plaintext, format: format) 72 | } 73 | 74 | @inlinable 75 | // swiftlint:disable:next missing_docs attributes 76 | public static func decodeEval(plaintext: EvalPlaintext, format: EncodeFormat) throws -> [Scalar] { 77 | try plaintext.convertToCoeffFormat().decode(format: format) 78 | } 79 | 80 | @inlinable 81 | // swiftlint:disable:next missing_docs attributes 82 | public static func decodeEval(plaintext: EvalPlaintext, format: EncodeFormat) throws -> [SignedScalar] { 83 | try plaintext.convertToCoeffFormat().decode(format: format) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Sources/HomomorphicEncryption/Bfv/Bfv+Multiply.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2024 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 | extension Bfv { 16 | @inlinable 17 | // swiftlint:disable:next missing_docs attributes 18 | public static func mulAssign(_ lhs: inout CanonicalCiphertext, _ rhs: CanonicalCiphertext) throws { 19 | let evalCiphertext = try multiplyWithoutScaling(lhs, rhs) 20 | lhs = try dropExtendedBase(from: evalCiphertext) 21 | } 22 | 23 | /// Reduces a ciphertext from base `[Q, Bsk]` to base `Q`. 24 | /// 25 | /// The ciphertext may be an intermediate result from a call to ``Bfv/multiplyWithoutScaling(_:_:)``. 26 | /// 27 | /// - Parameter ciphertext: Ciphertext with base `[Q, Bsk]`. 28 | /// - Returns: The ciphertext with base `Q`. 29 | /// - Throws: Error upon failure to drop the base. 30 | @inlinable 31 | public static func dropExtendedBase(from ciphertext: EvalCiphertext) throws -> CoeffCiphertext { 32 | guard ciphertext.moduli.count % 2 == 1, ciphertext.moduli.count >= 3 else { 33 | throw HeError.invalidCiphertext(ciphertext, message: "Ciphertext must have modului count >= 3 and odd") 34 | } 35 | let scalingModuliCount = (ciphertext.moduli.count - 1) / 2 36 | let rnsTool = ciphertext.context.getRnsTool(moduliCount: scalingModuliCount) 37 | 38 | let tVec = Array(repeating: ciphertext.context.plaintextModulus, count: ciphertext.moduli.count) 39 | let polys = try ciphertext.polys.map { poly in 40 | let scaledPoly = poly * tVec 41 | let coeffPoly = try scaledPoly.inverseNtt() 42 | return try rnsTool.floorQBskToQ(poly: coeffPoly) 43 | } 44 | return CoeffCiphertext(context: ciphertext.context, polys: polys, correctionFactor: ciphertext.correctionFactor) 45 | } 46 | 47 | @inlinable 48 | static func computeBehzPolys(ciphertext: CanonicalCiphertext) throws -> [PolyRq] { 49 | let rnsTool = ciphertext.context.getRnsTool(moduliCount: ciphertext.moduli.count) 50 | return try ciphertext.polys.map { poly in 51 | let polyQBsk = try rnsTool.liftQToQBsk(poly: poly) 52 | return try polyQBsk.forwardNtt() 53 | } 54 | } 55 | 56 | /// Computes `lhs * rhs` in an extended base. 57 | /// 58 | /// - seealso: Use ``Bfv/dropExtendedBase(from:)`` to remove the extended base. 59 | @inlinable 60 | public static func multiplyWithoutScaling(_ lhs: CanonicalCiphertext, 61 | _ rhs: CanonicalCiphertext) throws -> EvalCiphertext 62 | { 63 | try validateEquality(of: lhs.context, and: rhs.context) 64 | guard lhs.polys.count == freshCiphertextPolyCount, lhs.correctionFactor == 1 else { 65 | throw HeError.invalidCiphertext(lhs) 66 | } 67 | guard rhs.polys.count == freshCiphertextPolyCount, rhs.correctionFactor == 1 else { 68 | throw HeError.invalidCiphertext(rhs) 69 | } 70 | guard lhs.polyContext() == rhs.polyContext() else { 71 | throw HeError.incompatibleCiphertexts(lhs, rhs) 72 | } 73 | 74 | let lhsPolys = try computeBehzPolys(ciphertext: lhs) 75 | let rhsPolys = try computeBehzPolys(ciphertext: rhs) 76 | 77 | let poly0 = lhsPolys[0] * rhsPolys[0] 78 | let poly1 = lhsPolys[0] * rhsPolys[1] + lhsPolys[1] * rhsPolys[0] 79 | let poly2 = lhsPolys[1] * rhsPolys[1] 80 | 81 | return EvalCiphertext(context: lhs.context, polys: [poly0, poly1, poly2], correctionFactor: 1) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Sources/HomomorphicEncryption/HomomorphicEncryption.docc/DataFormats.md: -------------------------------------------------------------------------------- 1 | # Data Formats 2 | 3 | Representation and serialization of polynomials and related objects. 4 | 5 | ## Overview 6 | 7 | Objects containing polynomials have multiple distinct representations. For instance, the structures ``Plaintext`` and ``SerializedPlaintext``, and the protobuf message `SerializedPlaintext` all represent plaintexts in different contexts. Same for ciphertexts and keys. 8 | 9 | The structures ``Plaintext``, ``Ciphertext``, ``SecretKey``, etc. are used at run-time to carry out computations. In addition to data, these structures have references to "contexts" (see below) that facilitate these computations. Serialization structures like ``SerializedPlaintext``, ``SerializedCiphertext``, ``SerializedSecretKey``, etc. serialize the run-time structures' data (not the context) into a byte array which serves as an intermediate serialization format, independent of the wire protocol. The protobuf objects like ``SerializedPlaintext`` implement the particular wire protocol. 10 | 11 | Plaintexts, ciphertexts, and keys need a serialization protocol, as they are stored on disk during processing, and sent over the wire. The crux of serializing these objects is serializing the polynomials they contain, so let's look at polynomial representation and serialization. 12 | 13 | ### Polynomial Representation 14 | 15 | The ``PolyRq`` struct represents a polynomial in `R_q = Z_q[X] / (X^N + 1)`, that is, degree `N` polynomials where each coefficient is an integer mod `q`. The polynomial is represented either as coefficients (``Coeff``) or as evaluations (``Eval``), called terms. Each of these individual terms (i.e., a coefficient or an evaluation) is represented as residues relative to a given list of moduli. 16 | 17 | During an execution, many polynomials contained in plaintexts, ciphertexts and keys share a lot of information. These polynomials have distinct coefficients, but they can have the same degree, moduli basis, etc. There are also common parameters used during computation. From a serialization perspective, it is wasteful to serialize this information for all polynomials, so redundant elements are separated out into a `context` as described next. 18 | 19 | Inside `PolyRq`, the polynomial coefficients themselves are stored in an ``Array2d`` named `data`, where columns correspond to the coefficient index, and rows correspond to RNS moduli. The number of columns is equal to the degree of the polynomial. Thus, entry `(i,j)` is the residue of the `j`th coefficient relative to the `i`th modulus. The moduli and other information about the polynomial (including its degree) are stored in a ``PolyContext`` named `context`. 20 | 21 | To serialize a polynomial, only the coefficients are serialized, and not the `context`. (This implies that during deserialization, we need to pass a `context` with correct list of moduli.) 22 | 23 | The ``Plaintext`` struct contains a ``PolyRq`` polynomial, which in turn contains the `context`. The ``SerializedPlaintext`` struct contains the serialization of the _coefficients_ of the polynomial as an array of bytes. Details of this serialization are describe in the section below. The protobuf message ``SerializedPlaintext`` is the protobuf serialization of ``SerializedPlaintext`` used as the wire protocol. Thus,``SerializedPlaintext`` is an intermediate serialization that makes it easy to change the wire protocol without changing low-level polynomial representation. 24 | 25 | Similar discussion applies to keys and ciphertexts. 26 | 27 | ### Serializing Polynomials 28 | 29 | Coefficients in `data` are serialized in a row-major manner. Residues for all coefficients relative to the first modulus are serialized first, then all residues relative to the second modulus, and so on. 30 | 31 | For a specific modulus, serialization packs the residues in an array of bytes. Each residue can be represented in `ceil(log(modulus)` bits, and thus the entire row requires `degree * ceil(log(modulus))` bits. The array of bytes is considered a contiguous sequence of bits, and the residues are packed in order, in fixed-width chunks of `ceil(log(modulus))` bits each. If the total number of bits is not divisible by 8, then the remaining bits in the last byte are set to 0. 32 | 33 | For decryption, some number of least significant bits of the coefficients can be ignored without affecting the result. Thus, we can ignore those bits from the serialization too, resulting in smaller message size. For ciphertexts meant for decryption only, calling ``Ciphertext/serialize(indices:forDecryption:)`` with `forDecryption: true` will calculate the number of bits to skip and serialize appropriately. Furthermore, for ciphertexts in ``Coeff`` format, if only certain coefficient indices are of interest, they can be specified for a more compressible ciphertext. 34 | 35 | Similar packing is done for all moduli. The final output is the single array obtained by concatenating the serializations of residues for each modulus as it appears in ``PolyContext``. 36 | -------------------------------------------------------------------------------- /Sources/HomomorphicEncryption/HomomorphicEncryption.docc/Examples.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | Examples for how to use ``HomomorphicEncryption`` 4 | 5 | ### Basics 6 | We start with the basics. 7 | @Snippet(path: "swift-homomorphic-encryption/Snippets/HomomorphicEncryption/BasicsSnippet", slice:"encryption") 8 | 9 | ### HE Addition and Subtraction 10 | Continuing from the previous example, we can also compute on the encrypted data. 11 | We can add a ciphertext with a ciphertext or a plaintext. 12 | @Snippet(path: "swift-homomorphic-encryption/Snippets/HomomorphicEncryption/BasicsSnippet", slice:"addition") 13 | 14 | Continuing from the previous example, we can also subtract a ciphertext by a ciphertext or a plaintext. 15 | @Snippet(path: "swift-homomorphic-encryption/Snippets/HomomorphicEncryption/BasicsSnippet", slice:"subtraction") 16 | 17 | ### HE Multiplication 18 | @Snippet(path: "swift-homomorphic-encryption/Snippets/HomomorphicEncryption/MultiplicationSnippet") 19 | 20 | ### Evaluation Key 21 | @Snippet(path: "swift-homomorphic-encryption/Snippets/HomomorphicEncryption/EvaluationKeySnippet") 22 | 23 | ### Noise budget 24 | @Snippet(path: "swift-homomorphic-encryption/Snippets/HomomorphicEncryption/NoiseBudgetSnippet") 25 | 26 | ### Serialization 27 | @Snippet(path: "swift-homomorphic-encryption/Snippets/HomomorphicEncryption/SerializationSnippet") 28 | 29 | ### Encryption Parameters 30 | @Snippet(path: "swift-homomorphic-encryption/Snippets/HomomorphicEncryption/EncryptionParametersSnippet") 31 | -------------------------------------------------------------------------------- /Sources/HomomorphicEncryption/HomomorphicEncryption.docc/HomomorphicEncryption.md: -------------------------------------------------------------------------------- 1 | # ``HomomorphicEncryption`` 2 | 3 | Homomorphic Encryption (HE) enables computation on encrypted data. 4 | 5 | ## Overview 6 | Swift Homomorphic Encryption is a collection of libraries and executables implementing homomorphic encryption (HE) and applications, such as private information retrieval (PIR). 7 | For more information, refer to documentation for the libraries: 8 | * [HomomorphicEncryptionProtobuf](https://swiftpackageindex.com/apple/swift-homomorphic-encryption/main/documentation/homomorphicencryptionprotobuf) 9 | * [HomomorphicEncryption](https://swiftpackageindex.com/apple/swift-homomorphic-encryption/main/documentation/homomorphicencryption) 10 | * [PrivateInformationRetrievalProtobuf](https://swiftpackageindex.com/apple/swift-homomorphic-encryption/main/documentation/privateinformationretrievalprotobuf) 11 | * [PrivateInformationRetrieval](https://swiftpackageindex.com/apple/swift-homomorphic-encryption/main/documentation/privateinformationretrieval) 12 | * [PrivateNearestNeighborSearchProtobuf](https://swiftpackageindex.com/apple/swift-homomorphic-encryption/main/documentation/privatenearestneighborsearchprotobuf) 13 | * [PrivateNearestNeighborSearch](https://swiftpackageindex.com/apple/swift-homomorphic-encryption/main/documentation/privatenearestneighborsearch) 14 | 15 | and executables: 16 | * [PIRGenerateDatabase](https://swiftpackageindex.com/apple/swift-homomorphic-encryption/main/documentation/pirgeneratedatabase) 17 | * [PIRProcessDatabase](https://swiftpackageindex.com/apple/swift-homomorphic-encryption/main/documentation/pirprocessdatabase) 18 | * [PIRShardDatabase](https://swiftpackageindex.com/apple/swift-homomorphic-encryption/main/documentation/pirsharddatabase) 19 | * [PNNSGenerateDatabase](https://swiftpackageindex.com/apple/swift-homomorphic-encryption/main/documentation/pnnsgeneratedatabase) 20 | * [PNNSProcessDatabase](https://swiftpackageindex.com/apple/swift-homomorphic-encryption/main/documentation/pnnsprocessdatabase) 21 | 22 | ### Background 23 | Swift Homomorphic Encryption implements a special form of cryptography called homomorphic encryption (HE). 24 | HE is a cryptosystem which enables computation on encrypted data. 25 | The computation is performed directly on the encrypted data, *without revealing the plaintext of that data to the operating process*. 26 | HE computations therefore happen without decryption or access to the decryption key. 27 | 28 | HE thereby allows a client to enable a server to perform operations on encrypted data, and therefore without revealing the data to server. 29 | A typical HE workflow might be: 30 | * The client encrypts its sensitive data and sends the resulting ciphertext to the server. 31 | * The server performs HE computation on the ciphertext (and perhaps its own plaintext inputs), without learning what any ciphertext decrypts to. 32 | * The server sends the resulting ciphertext response to the client. 33 | * The client decrypts to learn the response. 34 | 35 | Swift Homomorphic Encryption implements the Brakerski-Fan-Vercauteren (BFV) HE scheme, which is based on the ring learning with errors (RLWE) hardness problem. 36 | This scheme can be configured to support post-quantum 128-bit security. 37 | 38 | > Warning: BFV does not provide IND-CCA security, and should be used accordingly. 39 | > In particular, as little information as possible about each decrypted ciphertext should be sent back to the server. To protect against a malicious server, the client should also validate the decrypted content is in the expected format. 40 | > 41 | > Consult a cryptography expert when developing and deploying homomorphic encryption applications. 42 | 43 | ## Topics 44 | 45 | ### Articles 46 | - 47 | - 48 | - 49 | -------------------------------------------------------------------------------- /Sources/HomomorphicEncryption/HomomorphicEncryption.docc/UsingSwiftHomomorphicEncryption.md: -------------------------------------------------------------------------------- 1 | # Using Swift Homomorphic Encryption 2 | 3 | Get started using Swift Homomorphic Encryption. 4 | 5 | ## Overview 6 | Swift Homomorphic Encryption is available as a Swift Package Manager package. 7 | To use Swift Homomorphic Encryption, choose a [tag](https://github.com/apple/swift-homomorphic-encryption/tags). 8 | Then, add the following dependency in your `Package.swift` 9 | ```swift 10 | .package( 11 | url: "https://github.com/apple/swift-homomorphic-encryption", 12 | from: "tag"), 13 | ``` 14 | replacing `tag` with your chosen tag, e.g. `1.0.0`. 15 | 16 | To use the `HomomorphicEncryption` library, add 17 | ```swift 18 | .product(name: "HomomorphicEncryption", package: "swift-homomorphic-encryption"), 19 | ``` 20 | to your target's dependencies. 21 | 22 | > Important: 23 | > When linking your executable, make sure to set `-cross-module-optimization`. 24 | > Without this flag, performance of Swift Homomorphic Encryption degrades dramatically, 25 | > due to failure to specialize generics. For example, 26 | > ```swift 27 | > .executableTarget( 28 | > name: "YourTarget", 29 | > dependencies: [ 30 | > .product(name: "HomomorphicEncryption", package: "swift-homomorphic-encryption"), 31 | > ], 32 | > swiftSettings: [.unsafeFlags(["-cross-module-optimization"], 33 | > .when(configuration: .release))] 34 | > ) 35 | 36 | You can then add 37 | ```swift 38 | import HomomorphicEncryption 39 | ``` 40 | to your Swift code to access the functionality in the `HomomorphicEncryption` library. 41 | 42 | > Note: 43 | > If you are using Swift Homomorphic Encryption for research, please cite using the 44 | > [CITATION.cff](https://github.com/apple/swift-homomorphic-encryption/blob/main/CITATION.cff) file. 45 | 46 | ### Supported Platforms 47 | Swift Homomorphic Encryption aims to support all of the platforms where Swift is supported. 48 | 49 | > Note: Swift Homomorphic Encryption relies on [SystemRandomNumberGenerator](https://developer.apple.com/documentation/swift/systemrandomnumbergenerator) as a cryptographically secure random number generator, which may have platform-dependent behavior. 50 | 51 | ### Swift / Xcode versions 52 | The following table maps Swift Homomorphic Encryption packgae versions to required Swift and Xcode versions: 53 | 54 | Package version | Swift version | Xcode version 55 | ----------------|---------------|----------------------------------------- 56 | 1.0.x | >= Swift 5.10 | >= Xcode 15.3 57 | main | >= Swift 6.0 | >= Xcode 16.1 58 | 59 | ### Source Stability 60 | Swift Homomorphic Encryption follows [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html). Source breaking changes to the public API can only land in a new major version, with the following exception: 61 | 62 | * Adding a new `case` to a public `enum` type will require only a minor version bump. For instance, we may add a new `enum` to an [HeError](https://swiftpackageindex.com/apple/swift-homomorphic-encryption/documentation/homomorphicencryption/heerror). To avoid breaking source code, add a `default` case when adding a `switch` on the enum values. 63 | 64 | Future minor versions of the package may introduce changes to these rules as needed. 65 | 66 | We'd like this package to quickly embrace Swift language and toolchain improvements that are relevant to its mandate. Accordingly, from time to time, we expect that new versions of this package will require clients to upgrade to a more recent Swift toolchain release. Requiring a new Swift release will only require a minor version bump. 67 | 68 | > Warning: Any symbol beginning with an underscore, and any product beginning with an underscore, is not subject to semantic versioning: these APIs may change without warning. 69 | -------------------------------------------------------------------------------- /Sources/HomomorphicEncryption/PolyRq/PolyCollection.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2024 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 ModularArithmetic 16 | 17 | /// Protocol for a collection of ``PolyRq`` polynomials. 18 | public protocol PolyCollection { 19 | /// Coefficient type. 20 | associatedtype Scalar: ScalarType 21 | 22 | /// Returns the polynomial's context. 23 | @inlinable 24 | func polyContext() -> PolyContext 25 | } 26 | 27 | extension PolyCollection { 28 | /// The polynomial's degree. 29 | @inlinable public var degree: Int { polyContext().degree } 30 | 31 | /// The polynomial's scalar moduli. 32 | @inlinable public var moduli: [Scalar] { polyContext().moduli } 33 | 34 | /// The polynomial's moduli. 35 | @inlinable var reduceModuli: [Modulus] { polyContext().reduceModuli } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/HomomorphicEncryption/PolyRq/PolyRq+Serialize.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2024 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 Foundation 16 | import ModularArithmetic 17 | 18 | extension PolyRq { 19 | @inlinable 20 | init(deserialize buffer: C, context: PolyContext, skipLSBs: Int = 0) throws 21 | where C: Collection, 22 | C.Element == UInt8, 23 | C.Index == Int 24 | { 25 | self = Self.zero(context: context) 26 | try load(from: buffer, skipLSBs: skipLSBs) 27 | } 28 | 29 | @inlinable 30 | mutating func load(from buffer: C, skipLSBs: Int = 0) throws 31 | where C: Collection, C.Element == UInt8, C.Index == Int 32 | { 33 | var offset = buffer.startIndex 34 | for (rnsIndex, modulus) in moduli.enumerated() { 35 | let bitsPerCoeff = modulus.ceilLog2 36 | let byteCount = CoefficientPacking.coefficientsToBytesByteCount( 37 | coeffCount: degree, 38 | bitsPerCoeff: bitsPerCoeff, 39 | skipLSBs: skipLSBs) 40 | 41 | guard offset &+ byteCount <= buffer.endIndex else { 42 | let expected = moduli.reduce(0) { total, mod in 43 | total &+ CoefficientPacking.coefficientsToBytesByteCount( 44 | coeffCount: degree, 45 | bitsPerCoeff: mod.ceilLog2, 46 | skipLSBs: skipLSBs) 47 | } 48 | throw HeError.serializedBufferSizeMismatch( 49 | polyContext: context, 50 | actual: buffer.count, 51 | expected: expected) 52 | } 53 | 54 | let bytes = buffer[offset..<(offset &+ byteCount)] 55 | try CoefficientPacking.bytesToCoefficientsInplace( 56 | bytes: bytes, 57 | coeffs: &data.data[polyIndices(rnsIndex: rnsIndex)], 58 | bitsPerCoeff: bitsPerCoeff, 59 | skipLSBs: skipLSBs) 60 | offset &+= byteCount 61 | } 62 | } 63 | 64 | @inlinable 65 | func serializationByteCount(skipLSBs: Int = 0) -> Int { 66 | context.serializationByteCount(skipLSBs: skipLSBs) 67 | } 68 | 69 | @inlinable 70 | package func serialize(skipLSBs: Int = 0) -> [UInt8] { 71 | // safe because we initialize the buffer with correct count 72 | // swiftlint:disable:next force_try 73 | try! moduli.enumerated().flatMap { rnsIndex, modulus in 74 | let bitsPerCoeff = modulus.ceilLog2 75 | let bytesCount = CoefficientPacking.coefficientsToBytesByteCount( 76 | coeffCount: degree, 77 | bitsPerCoeff: bitsPerCoeff, 78 | skipLSBs: skipLSBs) 79 | var buffer: [UInt8] = .init(repeating: 0, count: bytesCount) 80 | try CoefficientPacking.coefficientsToBytesInplace( 81 | coeffs: data.data[polyIndices(rnsIndex: rnsIndex)], 82 | bytes: &buffer, 83 | bitsPerCoeff: bitsPerCoeff, 84 | skipLSBs: skipLSBs) 85 | return buffer 86 | } 87 | } 88 | } 89 | 90 | extension PolyContext { 91 | @inlinable 92 | package func serializationByteCount(skipLSBs: Int = 0) -> Int { 93 | moduli.map { modulus in 94 | CoefficientPacking.coefficientsToBytesByteCount( 95 | coeffCount: degree, 96 | bitsPerCoeff: modulus.ceilLog2, 97 | skipLSBs: skipLSBs) 98 | }.sum() 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Sources/HomomorphicEncryption/Random/BufferedRng.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2024 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 | /// Wrapper for random number generator that adds a buffer. 16 | @usableFromInline 17 | package final class BufferedRng { 18 | /// Random number generator. 19 | @usableFromInline var rng: R 20 | /// Offset into ``buffer``, pointing to the next unused random byte. 21 | @usableFromInline var offset: Int 22 | /// Buffer of random bytes. 23 | @usableFromInline let buffer: UnsafeMutableRawBufferPointer 24 | /// Number of random bytes that have been generated but not used. 25 | @usableFromInline var remaining: Int { 26 | buffer.count &- offset 27 | } 28 | 29 | /// Initializes a ``BufferedRng``. 30 | /// - Parameters: 31 | /// - rng: Random number generator. 32 | /// - bufferCount: Number of bytes in the buffer. 33 | @inlinable 34 | init(rng: R, bufferCount: Int) { 35 | self.rng = rng 36 | self.offset = bufferCount 37 | self.buffer = UnsafeMutableRawBufferPointer.allocate(byteCount: bufferCount, alignment: 16) 38 | } 39 | 40 | deinit { 41 | buffer.deallocate() 42 | } 43 | } 44 | 45 | extension BufferedRng: PseudoRandomNumberGenerator { 46 | /// Fills a buffer with random values. 47 | /// - Parameter bufferPointer: Buffer to fill. 48 | @inlinable 49 | package func fill(_ bufferPointer: UnsafeMutableRawBufferPointer) { 50 | var filled = 0 51 | while filled < bufferPointer.count { 52 | if remaining == 0 { 53 | rng.fill(buffer) 54 | offset = 0 55 | } 56 | 57 | let sliceEnd = min(filled &+ remaining, bufferPointer.endIndex) 58 | let sliceToBeFilled = UnsafeMutableRawBufferPointer(rebasing: bufferPointer[filled.. 18 | 19 | extension NistAes128Ctr { 20 | /// Number of bytes in the AES-128 seed. 21 | @usableFromInline static let SeedCount: Int = NistCtrDrbg.SeedCount 22 | 23 | /// Initializes a ``NistAes128Ctr``. 24 | @inlinable 25 | package convenience init() { 26 | do { 27 | try self.init(rng: NistCtrDrbg(), bufferCount: 4096) 28 | } catch { 29 | preconditionFailure("NistAes128Ctr.init failed: \(error)") 30 | } 31 | } 32 | 33 | /// Initializes a ``NistAes128Ctr`` with a seed. 34 | /// - Parameter seed: Seed for the random number generator. Must have ``SeedCount`` bytes. 35 | /// - Throws: Error upon failure to initialize the random number generator. 36 | @inlinable 37 | convenience init(seed: [UInt8]) throws { 38 | try self.init(rng: NistCtrDrbg(entropy: seed), bufferCount: 4096) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/HomomorphicEncryption/Random/NistCtrDrbg.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2024 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 _CryptoExtras 16 | import Crypto 17 | import Foundation 18 | 19 | /// Random number generator. 20 | /// 21 | /// Implements the NIST `CTR_DRBG` using AES without derivation function. 22 | /// Description is in NIST SP 800-90A: 23 | /// . 24 | @usableFromInline 25 | package struct NistCtrDrbg { 26 | @usableFromInline static let ReseedInterval: Int64 = 1 << 48 27 | 28 | @usableFromInline static let MaxByteCountPerRequest: Int = 1 << 16 29 | 30 | /// Size of AES block. 31 | @usableFromInline static let BlockCount: Int = 16 32 | 33 | /// Size of AES key. 34 | @usableFromInline static let KeyCount: Int = 16 35 | 36 | /// Size of the seed. 37 | @usableFromInline static let SeedCount: Int = KeyCount + BlockCount 38 | 39 | @usableFromInline var key: SymmetricKey 40 | 41 | /// This is called `V` in the NIST specification. 42 | @usableFromInline var nonce: UInt128 43 | 44 | @usableFromInline var reseedCounter: Int64 45 | 46 | @usableFromInline var nonceBytes: [UInt8] { 47 | // Because of a mismatch between pre-increment & post-increment we always implicitly add one to the nonce before 48 | // we call `AES._CTR.encrypt()` 49 | (nonce &+ 1).bigEndianBytes 50 | } 51 | 52 | @inlinable 53 | init(entropy: [UInt8] = [UInt8]( 54 | randomByteCount: Self.SeedCount)) throws 55 | { 56 | self.key = SymmetricKey(data: [UInt8](repeating: 0, count: Self.KeyCount)) 57 | self.nonce = 0 58 | self.reseedCounter = 1 59 | try ctrDrbgUpdate(providedData: entropy) 60 | } 61 | 62 | @inlinable 63 | mutating func ctrDrbgUpdate(providedData: [UInt8]) throws { 64 | precondition(providedData.count == Self.SeedCount) 65 | 66 | let xor = try AES._CTR.encrypt(providedData, using: key, nonce: .init(nonceBytes: 67 | nonceBytes)) 68 | key = SymmetricKey(data: xor.prefix(Self.KeyCount)) 69 | nonce = .init(bigEndianBytes: xor.suffix(Self.BlockCount)) 70 | } 71 | 72 | mutating func ctrDrbgGenerate(count: Int) throws -> [UInt8] { 73 | let requestedByteCount = count 74 | precondition(reseedCounter <= Self.ReseedInterval) 75 | precondition(requestedByteCount <= Self.MaxByteCountPerRequest) 76 | 77 | let zeroes = [UInt8](repeating: 0, count: requestedByteCount) 78 | let output = try AES._CTR.encrypt(zeroes, using: key, nonce: .init(nonceBytes: nonceBytes)) 79 | nonce &+= UInt128(requestedByteCount.dividingCeil(Self.BlockCount, variableTime: true)) 80 | 81 | let additionalInput = [UInt8](repeating: 0, count: Self.SeedCount) 82 | try ctrDrbgUpdate(providedData: additionalInput) 83 | 84 | reseedCounter &+= 1 85 | return Array(output) 86 | } 87 | } 88 | 89 | extension NistCtrDrbg: PseudoRandomNumberGenerator { 90 | /// Fills a buffer with random values. 91 | /// - Parameter bufferPointer: Buffer to fill. 92 | public mutating func fill(_ bufferPointer: UnsafeMutableRawBufferPointer) { 93 | do { 94 | let data = try ctrDrbgGenerate(count: bufferPointer.count) 95 | data.withUnsafeBytes { dataPointer in 96 | bufferPointer.copyMemory(from: dataPointer) 97 | } 98 | } catch { 99 | preconditionFailure("NistCtrDrbg failed: \(error)") 100 | } 101 | } 102 | } 103 | 104 | extension [UInt8] { 105 | @inlinable 106 | init(randomByteCount: Int) { 107 | self = .init(repeating: 0, count: randomByteCount) 108 | var rng = SystemRandomNumberGenerator() 109 | rng.fill(&self) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Sources/HomomorphicEncryption/Random/PseudoRandomNumberGenerator.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2024 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 | /// Protocol for a pseudo random number genearator (PRNG). 16 | public protocol PseudoRandomNumberGenerator { 17 | /// Fills a buffer with random values. 18 | /// - Parameter bufferPointer: Buffer to fill. 19 | @inlinable 20 | mutating func fill(_ bufferPointer: UnsafeMutableRawBufferPointer) 21 | } 22 | 23 | extension PseudoRandomNumberGenerator { 24 | /// Fills a buffer with random values. 25 | /// - Parameter buffer: Buffer to fill. 26 | @inlinable 27 | public mutating func fill(_ buffer: inout [UInt8]) { 28 | buffer.withUnsafeMutableBytes { bufferPointer in 29 | self.fill(bufferPointer) 30 | } 31 | } 32 | 33 | /// Computes the next generated random number. 34 | /// - Returns: The next generated random number. 35 | @inlinable 36 | @inline(__always) 37 | public mutating func next() -> T { 38 | var r = T.zero 39 | withUnsafeMutableBytes(of: &r) { bufferPointer in 40 | self.fill(bufferPointer) 41 | } 42 | return r 43 | } 44 | 45 | /// Fills an array with random values. 46 | /// - Parameter array: Array to fill. 47 | @inlinable 48 | public mutating func fill(_ array: inout [some FixedWidthInteger]) { 49 | array.withUnsafeMutableBytes { bufferPointer in 50 | self.fill(bufferPointer) 51 | } 52 | } 53 | 54 | /// Fills an `ArraySlice` with random values. 55 | /// - Parameter slice: Slice to fill 56 | @inlinable 57 | public mutating func fill(_ slice: inout ArraySlice) { 58 | slice.withUnsafeMutableBytes { bufferPointer in 59 | self.fill(bufferPointer) 60 | } 61 | } 62 | } 63 | 64 | extension RandomNumberGenerator { 65 | /// Fills a buffer with random values. 66 | /// - Parameter bufferPointer: Buffer to fill. 67 | @inlinable 68 | public mutating func fill(_ bufferPointer: UnsafeMutableRawBufferPointer) { 69 | let size = MemoryLayout.size 70 | for i in stride(from: bufferPointer.startIndex, through: bufferPointer.endIndex &- size, by: size) { 71 | var random = next() 72 | withUnsafeBytes(of: &random) { randomBufferPointer in 73 | let rebased = UnsafeMutableRawBufferPointer(rebasing: bufferPointer[i..<(i &+ size)]) 74 | rebased.copyMemory(from: randomBufferPointer) 75 | } 76 | } 77 | 78 | var remainingSlice = bufferPointer.suffix(from: (bufferPointer.count / size) * size) 79 | if !remainingSlice.isEmpty { 80 | var random = next() 81 | withUnsafeBytes(of: &random) { randomBufferPointer in 82 | for (sliceIndex, randomIndex) in zip(remainingSlice.indices, randomBufferPointer.indices) { 83 | remainingSlice[sliceIndex] = randomBufferPointer[randomIndex] 84 | } 85 | } 86 | } 87 | } 88 | } 89 | 90 | extension SystemRandomNumberGenerator: PseudoRandomNumberGenerator {} 91 | -------------------------------------------------------------------------------- /Sources/HomomorphicEncryption/Serialize.swift: -------------------------------------------------------------------------------- 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 Foundation 16 | 17 | @usableFromInline 18 | enum Serialize {} 19 | 20 | extension Serialize { 21 | @inlinable 22 | static func serializePolysBufferSize(polyCount: Int, context: PolyContext, 23 | skipLSBs: [Int] = []) -> Int 24 | { 25 | let skipLSBs = skipLSBs.isEmpty ? .init(repeating: 0, count: polyCount) : skipLSBs 26 | return skipLSBs.map { context.serializationByteCount(skipLSBs: $0) } 27 | .sum() + MemoryLayout.size 28 | } 29 | 30 | @inlinable 31 | static func serializePolys( 32 | _ polys: some Collection>, 33 | to buffer: inout C, 34 | context: PolyContext, 35 | skipLSBs: [Int] = []) throws 36 | where C: MutableCollection, 37 | C.Element == UInt8, 38 | C.Index == Int 39 | { 40 | let skipLSBs = skipLSBs.isEmpty ? .init(repeating: 0, count: polys.count) : skipLSBs 41 | guard buffer.count >= MemoryLayout.size else { 42 | let expectedBufferSize = serializePolysBufferSize( 43 | polyCount: polys.count, 44 | context: context, 45 | skipLSBs: skipLSBs) 46 | throw HeError.serializationBufferSizeMismatch( 47 | polyContext: context, 48 | actual: buffer.count, 49 | expected: expectedBufferSize) 50 | } 51 | var offset = buffer.startIndex 52 | let polyCount = UInt16(polys.count) 53 | buffer[offset] = UInt8(truncatingIfNeeded: polyCount) 54 | buffer.formIndex(after: &offset) 55 | buffer[offset] = UInt8(truncatingIfNeeded: polyCount >> UInt8.bitWidth) 56 | buffer.formIndex(after: &offset) 57 | 58 | let serialized = zip(polys, skipLSBs).flatMap { poly, skipLSBs in 59 | poly.serialize(skipLSBs: skipLSBs) 60 | } 61 | _ = serialized.withUnsafeBytes { srcBuffer in 62 | buffer[offset...].withContiguousMutableStorageIfAvailable { dstBuffer in 63 | // safe because we know the addresses are not nil 64 | // swiftlint:disable:next force_unwrapping 65 | memcpy(dstBuffer.baseAddress!, srcBuffer.baseAddress!, serialized.count) 66 | } 67 | } 68 | } 69 | 70 | @inlinable 71 | static func deserializePolys(from buffer: C, 72 | context: PolyContext, 73 | skipLSBs: [Int] = []) throws -> [PolyRq] 74 | where C: Collection, C.Element == UInt8, C.Index == Int 75 | { 76 | guard buffer.count >= MemoryLayout.size else { 77 | throw HeError.serializationBufferSizeMismatch(polyContext: context, actual: buffer.count, expected: 2) 78 | } 79 | var offset = buffer.startIndex 80 | var polyCountU16: UInt16 = 0 81 | 82 | polyCountU16 |= UInt16(buffer[offset]) 83 | buffer.formIndex(after: &offset) 84 | polyCountU16 |= (UInt16(buffer[offset]) << UInt8.bitWidth) 85 | buffer.formIndex(after: &offset) 86 | 87 | let polyCount = Int(polyCountU16) 88 | let skipLSBs = skipLSBs.isEmpty ? .init(repeating: 0, count: polyCount) : skipLSBs 89 | var polys: [PolyRq] = .init(repeating: PolyRq.zero(context: context), count: polyCount) 90 | for index in polys.indices { 91 | try polys[index].load(from: buffer[offset...], skipLSBs: skipLSBs[index]) 92 | offset += context.serializationByteCount(skipLSBs: skipLSBs[index]) 93 | } 94 | return polys 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Sources/HomomorphicEncryption/SerializedPlaintext.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2024 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 | /// Serialized ``Plaintext`` type. 16 | public struct SerializedPlaintext: Hashable, Codable, Sendable { 17 | /// The serialized polynomial. 18 | public let poly: [UInt8] 19 | 20 | /// Initializes a serialized plaintext. 21 | /// - Parameter poly: Serialized polynomial. 22 | public init(poly: [UInt8]) { 23 | self.poly = poly 24 | } 25 | } 26 | 27 | extension Plaintext { 28 | /// Serializes the plaintext. 29 | public func serialize() -> SerializedPlaintext { 30 | SerializedPlaintext(poly: poly.serialize()) 31 | } 32 | } 33 | 34 | extension Plaintext where Format == Coeff { 35 | /// Deserializes a plaintext. 36 | /// - Parameters: 37 | /// - serialized: Serialized plaintext. 38 | /// - context: Context to associate with the plaintext. 39 | /// - Throws: Error upon failure to deserialize. 40 | @inlinable 41 | public init(deserialize serialized: SerializedPlaintext, context: Context) throws { 42 | self.context = context 43 | self.poly = try PolyRq(deserialize: serialized.poly, context: context.plaintextContext) 44 | } 45 | } 46 | 47 | extension Plaintext where Format == Eval { 48 | /// Deserializes a plaintext. 49 | /// - Parameters: 50 | /// - serialized: Serialized plaintext. 51 | /// - context: Context to associate with the plaintext. 52 | /// - moduliCount: Optional number of moduli to associate with the plaintext. If not set, the plaintext will have 53 | /// the top-level ciphertext context with all the moduli. 54 | /// - Throws: Error upon failure to deserialize. 55 | @inlinable 56 | public init(deserialize serialized: SerializedPlaintext, context: Context, moduliCount: Int? = nil) throws { 57 | self.context = context 58 | let moduliCount = moduliCount ?? context.ciphertextContext.moduli.count 59 | let plaintextContext = try context.ciphertextContext.getContext(moduliCount: moduliCount) 60 | self.poly = try PolyRq(deserialize: serialized.poly, context: plaintextContext) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/HomomorphicEncryption/Util.swift: -------------------------------------------------------------------------------- 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 ModularArithmetic 16 | 17 | extension Sequence where Element: Hashable { 18 | @inlinable 19 | func allUnique() -> Bool { 20 | var seen = Set() 21 | for element in self { 22 | guard seen.insert(element).inserted else { 23 | return false 24 | } 25 | } 26 | return true 27 | } 28 | } 29 | 30 | extension FixedWidthInteger { 31 | // not a constant time operation 32 | @inlinable 33 | func toRemainder(_ mod: Self, variableTime: Bool) -> Self { 34 | precondition(variableTime) 35 | precondition(mod > 0) 36 | var result = self % mod 37 | if result < 0 { 38 | result += mod 39 | } 40 | return result 41 | } 42 | } 43 | 44 | extension [UInt8] { 45 | /// Initializes a byte array from a hexadecimal string. 46 | /// 47 | /// Initializes to nil upon invalid hexadecimal string. 48 | /// - Parameter hexString: A hexadecimal string, without leading "0x". 49 | public init?(hexEncoded hexString: String) { 50 | // Ensure the string has an even number of characters 51 | guard hexString.count.isMultiple(of: 2) else { 52 | return nil 53 | } 54 | 55 | var data = Array() 56 | data.reserveCapacity(hexString.count / 2) 57 | var index = hexString.startIndex 58 | 59 | while index < hexString.endIndex { 60 | let nextIndex = hexString.index(index, offsetBy: 2) 61 | if let byte = UInt8(hexString[index..() -> V { 80 | reduce(V(1)) { V($0) * V($1) } 81 | } 82 | 83 | /// Computes the sum of the elements in the array. 84 | /// 85 | /// The sum is 0 for an empty array. 86 | /// - Returns: the sum. 87 | @inlinable 88 | public func sum() -> V { 89 | reduce(V(0)) { V($0) + V($1) } 90 | } 91 | } 92 | 93 | extension Array where Element: ScalarType { 94 | @inlinable 95 | func product() -> Width32 { 96 | func wideningProduct(of elements: [some FixedWidthInteger]) -> [Prod] { 97 | stride(from: 0, to: elements.count, by: 2).map { index in 98 | var product = Prod(elements[index]) 99 | if index < elements.count - 1 { 100 | product &*= Prod(elements[index + 1]) 101 | } 102 | return product 103 | } 104 | } 105 | 106 | if count == 1 { 107 | return Width32(self[0]) 108 | } 109 | let doubleWidth: [Self.Element.DoubleWidth] = wideningProduct(of: self) 110 | if doubleWidth.count == 1 { 111 | return Width32(doubleWidth[0]) 112 | } 113 | let quadWidth: [QuadWidth] = wideningProduct(of: doubleWidth) 114 | if quadWidth.count == 1 { 115 | return Width32(quadWidth[0]) 116 | } 117 | let octoWidth: [OctoWidth] = wideningProduct(of: quadWidth) 118 | if octoWidth.count == 1 { 119 | return Width32(octoWidth[0]) 120 | } 121 | let width16: [Width16] = wideningProduct(of: octoWidth) 122 | if width16.count == 1 { 123 | return Width32(width16[0]) 124 | } 125 | let width32: [Width32] = wideningProduct(of: width16) 126 | 127 | return width32.reduce(Width32(1)) { $0 * $1 } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /Sources/HomomorphicEncryption/Version.swift: -------------------------------------------------------------------------------- 1 | // Copyright 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 | package enum Version { 16 | case development(branch: String) 17 | case release(major: Int, minor: Int, patch: Int) 18 | 19 | package static let current = Version.development(branch: "main") 20 | 21 | package var description: String { 22 | switch self { 23 | case let .development(branch): 24 | "\(branch)-development" 25 | case let .release(major, minor, patch): 26 | "\(major).\(minor).\(patch)" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/HomomorphicEncryption/Zeroization.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2024 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 | #if canImport(Darwin) 16 | import Darwin 17 | 18 | // swiftlint:disable:next implicitly_unwrapped_optional attributes 19 | @inlinable func zeroize(_ s: UnsafeMutableRawPointer!, _ n: Int) { 20 | let exitCode = memset_s(s, n, 0, n) 21 | precondition(exitCode == 0, "memset_s returned exit code \(exitCode)") 22 | } 23 | #else 24 | import CUtil 25 | 26 | // swiftlint:disable:next implicitly_unwrapped_optional attributes 27 | @inlinable func zeroize(_ s: UnsafeMutableRawPointer!, _ n: Int) { 28 | c_zeroize(s, n) 29 | } 30 | #endif 31 | -------------------------------------------------------------------------------- /Sources/HomomorphicEncryptionProtobuf/ConversionError.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2024 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 Foundation 16 | import SwiftProtobuf 17 | 18 | /// Error type when converting between protobuf and native objects. 19 | public enum ConversionError: Error { 20 | case invalidScheme 21 | case unimplementedScheme(scheme: String) 22 | case unrecognizedEnumValue(enum: any Enum.Type, value: Int) 23 | case unsetOneof(oneof: any Message.Type, field: String) 24 | } 25 | 26 | extension ConversionError { 27 | static func unsetOneof(oneof: any Message.Type, field: AnyKeyPath) -> Self { 28 | .unsetOneof(oneof: oneof, field: String(reflecting: field)) 29 | } 30 | } 31 | 32 | extension ConversionError: LocalizedError { 33 | public var errorDescription: String? { 34 | switch self { 35 | case .invalidScheme: 36 | "Invalid HE scheme" 37 | case let .unimplementedScheme(scheme: scheme): 38 | "Unimplemented encryption scheme: \(scheme)" 39 | case let .unrecognizedEnumValue(enum: enume, value: value): 40 | "Unrecognized value \(value) in enum \(enume)" 41 | case let .unsetOneof(oneof: oneof, field: field): 42 | "Unset oneof in message \(oneof) for field \(field)" 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Sources/HomomorphicEncryptionProtobuf/HomomorphicEncryptionProtobuf.docc/HomomorphicEncryptionProtobuf.md: -------------------------------------------------------------------------------- 1 | # ``HomomorphicEncryptionProtobuf`` 2 | 3 | Protocol buffer support for ``HomomorphicEncryption`` 4 | 5 | ## Overview 6 | `HomomorphicEncryptionProtobuf` contains supports for using [HomomorphicEncryption](https://swiftpackageindex.com/apple/swift-homomorphic-encryption/main/documentation/homomorphicencryption) with [protocol buffers](https://protobuf.dev/), commonly referred to as `protobuf`. 7 | This module contains: 8 | * the generated Swift code to use the protobuf types. 9 | * conversion between protobuf types and [HomomorphicEncryption](https://swiftpackageindex.com/apple/swift-homomorphic-encryption/main/documentation/homomorphicencryption) runtime types. 10 | 11 | The protobuf schemas are defined at [https://github.com/apple/swift-homomorphic-encryption-protobuf](https://github.com/apple/swift-homomorphic-encryption-protobuf). 12 | -------------------------------------------------------------------------------- /Sources/HomomorphicEncryptionProtobuf/MessageExtensions.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2024 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 Foundation 16 | import SwiftProtobuf 17 | 18 | extension Message { 19 | /// Initializes a `Message` from a file. 20 | /// 21 | /// The serialized message should be in protocol buffer text format or binary format. 22 | /// - Parameter path: Filepath with a serialized message. If the message is in text format, `path` should have 23 | /// `.txtpb` extension. 24 | /// - Throws: Error upon failure to initialize message. 25 | public init(from path: String) throws { 26 | if path.hasSuffix(".txtpb") { 27 | try self.init(textFormatString: String(contentsOfFile: path, encoding: .utf8)) 28 | } else { 29 | let serializedData = try Data(contentsOf: URL(fileURLWithPath: path)) 30 | try self.init(serializedBytes: serializedData) 31 | } 32 | } 33 | 34 | /// Saves a `Message` to a file. 35 | /// 36 | /// - Parameter path: Filepath to save the serialized message. The message will be serialized with protocol buffer 37 | /// text format if the path has `.txtpb` extension, and protocol buffer binary format otherwise. 38 | /// - Throws: Error upon failure to save the message. 39 | public func save(to path: String) throws { 40 | if path.hasSuffix(".txtpb") { 41 | let textFormat = textFormatString() 42 | try textFormat.write(toFile: path, atomically: true, encoding: .utf8) 43 | } else { 44 | let data = try serializedData() 45 | try data.write(to: URL(fileURLWithPath: path)) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/HomomorphicEncryptionProtobuf/generated/README.md: -------------------------------------------------------------------------------- 1 | # Generated files 2 | 3 | Files in this directory are auto-generated using swift-protobuf. 4 | See the [dependencies](../../../README.md#dependencies) for the swift-protobuf version. 5 | 6 | ## Requirements 7 | * [swift-protobuf](https://github.com/apple/swift-protobuf) 8 | 9 | ## Generate Swift files 10 | To generate the Swift files: 11 | 1. Change directory to `` 12 | 2. Run 13 | ```sh 14 | ./Utilities/generate-protobuf-files.sh 15 | ``` 16 | -------------------------------------------------------------------------------- /Sources/HomomorphicEncryptionProtobuf/generated/apple_swift_homomorphic_encryption_v1_error_stddev.pb.swift: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. 2 | // swift-format-ignore-file 3 | // swiftlint:disable all 4 | // 5 | // Generated by the Swift generator plugin for the protocol buffer compiler. 6 | // Source: apple/swift_homomorphic_encryption/v1/error_stddev.proto 7 | // 8 | // For information on using the generated types, please see the documentation: 9 | // https://github.com/apple/swift-protobuf/ 10 | 11 | // Copyright 2024 Apple Inc. and the Swift Homomorphic Encryption project authors 12 | // 13 | // Licensed under the Apache License, Version 2.0 (the "License"); 14 | // you may not use this file except in compliance with the License. 15 | // You may obtain a copy of the License at 16 | // 17 | // http://www.apache.org/licenses/LICENSE-2.0 18 | // 19 | // Unless required by applicable law or agreed to in writing, software 20 | // distributed under the License is distributed on an "AS IS" BASIS, 21 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 22 | // See the License for the specific language governing permissions and 23 | // limitations under the License. 24 | 25 | import SwiftProtobuf 26 | 27 | // If the compiler emits an error on this type, it is because this file 28 | // was generated by a version of the `protoc` Swift plug-in that is 29 | // incompatible with the version of SwiftProtobuf to which you are linking. 30 | // Please ensure that you are building against the same version of the API 31 | // that was used to generate this file. 32 | fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { 33 | struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} 34 | typealias Version = _2 35 | } 36 | 37 | /// Error standard deviation for RLWE samples. 38 | public enum Apple_SwiftHomomorphicEncryption_V1_ErrorStdDev: SwiftProtobuf.Enum, Swift.CaseIterable { 39 | public typealias RawValue = Int 40 | 41 | /// 8 / sqrt(2 pi) ~= 3.2. 42 | /// Note: this is the target standard deviation. 43 | /// In practice, since we sample using a centered binomial distribution, 44 | /// the sampled standard deviation may exceed the target standard deviation. 45 | case stddev32 // = 0 46 | case UNRECOGNIZED(Int) 47 | 48 | public init() { 49 | self = .stddev32 50 | } 51 | 52 | public init?(rawValue: Int) { 53 | switch rawValue { 54 | case 0: self = .stddev32 55 | default: self = .UNRECOGNIZED(rawValue) 56 | } 57 | } 58 | 59 | public var rawValue: Int { 60 | switch self { 61 | case .stddev32: return 0 62 | case .UNRECOGNIZED(let i): return i 63 | } 64 | } 65 | 66 | // The compiler won't synthesize support with the UNRECOGNIZED case. 67 | public static let allCases: [Apple_SwiftHomomorphicEncryption_V1_ErrorStdDev] = [ 68 | .stddev32, 69 | ] 70 | 71 | } 72 | 73 | // MARK: - Code below here is support for the SwiftProtobuf runtime. 74 | 75 | extension Apple_SwiftHomomorphicEncryption_V1_ErrorStdDev: SwiftProtobuf._ProtoNameProviding { 76 | public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 77 | 0: .same(proto: "ERROR_STD_DEV_STDDEV32"), 78 | ] 79 | } 80 | -------------------------------------------------------------------------------- /Sources/PIRGenerateDatabase/PIRGenerateDatabase.docc/PIRGenerateDatabase.md: -------------------------------------------------------------------------------- 1 | # ``PIRGenerateDatabase`` 2 | 3 | Keyword PIR database generation 4 | 5 | ## Overview 6 | 7 | `PIRGenerateDatabase` is an executable which generates a sample database for testing. 8 | The resulting database can be sharded with the [PIRShardDatabase](https://swiftpackageindex.com/apple/swift-homomorphic-encryption/main/documentation/pirsharddatabase) executable and processed with the [PIRProcessDatabase](https://swiftpackageindex.com/apple/swift-homomorphic-encryption/main/documentation/pirprocessdatabase) executable. 9 | 10 | ### Requirements 11 | To install the `PIRGenerateDatabase` executable, first make sure that the `~/.swiftpm/bin` directory is on your `$PATH`. To do 12 | so, add the following line to your `~/.zshrc` or appropriate shell configuration file. 13 | ```sh 14 | export PATH="$HOME/.swiftpm/bin:$PATH" 15 | ``` 16 | Make sure to reload it (`source ~/.zshrc`) or by restarting your terminal emulator. Then we are going to use the 17 | `experimental-install` feature of Swift Package Manager. 18 | 19 | Change directory to a checkout of this repository and run the following command. 20 | ```sh 21 | swift package experimental-install -c release --product PIRGenerateDatabase 22 | ``` 23 | 24 | ### Example 25 | 26 | 1. We start by generating a sample database. 27 | ```sh 28 | PIRGenerateDatabase \ 29 | --output-database database.txtpb \ 30 | --row-count 100 \ 31 | --value-size '10...20' \ 32 | --value-type repeated 33 | ``` 34 | 35 | This will generate a database of 100 rows, with keywords 0 to 99, and each value repeating the keyword for 10 to 20 bytes. 36 | 37 | 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). 38 | For readability, the `.txtpb` extension ensures the output database will be saved in protocol buffer text format. 39 | 40 | > Note: For a more compact format, use the `.binpb` extension to save the database in protocol buffer binary format. 41 | 42 | 2. We view a few rows from the database with 43 | ```sh 44 | head database.txtpb 45 | ``` 46 | which shows 47 | ```json 48 | rows { 49 | keyword: "0" 50 | value: "000000000000000" 51 | } 52 | rows { 53 | keyword: "1" 54 | value: "111111111111111111" 55 | } 56 | rows { 57 | keyword: "2" 58 | ``` 59 | -------------------------------------------------------------------------------- /Sources/PIRGenerateDatabase/main.swift: -------------------------------------------------------------------------------- 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 ArgumentParser 16 | import Foundation 17 | import HomomorphicEncryption 18 | import PrivateInformationRetrieval 19 | import PrivateInformationRetrievalProtobuf 20 | 21 | enum ValueTypeArguments: String, CaseIterable, ExpressibleByArgument { 22 | case random 23 | /// Repeats the keyword 24 | case repeated 25 | } 26 | 27 | struct ValueSizeArguments: ExpressibleByArgument { 28 | let range: Range 29 | 30 | init?(argument: String) { 31 | let parsedOpen = argument.split(separator: "..<") 32 | if parsedOpen.count == 2, let lower = Int(parsedOpen[0]), let upper = Int(parsedOpen[1]), lower < upper, 33 | lower > 0, upper > 0 34 | { 35 | self.range = lower.. 0, upper > 0 40 | { 41 | self.range = lower..<(upper + 1) 42 | } else if parsedClosed.count == 1, let size = Int(parsedClosed[0]), size > 0 { 43 | self.range = size..<(size + 1) 44 | } else { 45 | return nil 46 | } 47 | } 48 | } 49 | } 50 | 51 | extension [UInt8] { 52 | @inlinable 53 | init(randomByteCount: Int) { 54 | self = .init(repeating: 0, count: randomByteCount) 55 | var rng = SystemRandomNumberGenerator() 56 | rng.fill(&self) 57 | } 58 | } 59 | 60 | // This executable is used in tests, which breaks `swift test -c release` when used with `@main`. 61 | // So we avoid using `@main` here. 62 | struct GenerateDatabaseCommand: ParsableCommand { 63 | static let configuration: CommandConfiguration = .init( 64 | commandName: "PIRGenerateDatabase", version: Version.current.description) 65 | 66 | @Option(help: "Path to output database. Must end in '.txtpb' or '.binpb'") 67 | var outputDatabase: String 68 | 69 | @Option(help: "Number of rows in the database") 70 | var rowCount: Int 71 | 72 | @Option(help: "Number of bytes in each row. Must be of the form 'x', 'x.. Note: For a more compact format, use the `.binpb` extension to load the input database, and save the sharded databases in protocol buffer binary format. 110 | -------------------------------------------------------------------------------- /Sources/PNNSGenerateDatabase/GenerateDatabase.swift: -------------------------------------------------------------------------------- 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 ArgumentParser 16 | import Foundation 17 | import HomomorphicEncryption 18 | import PrivateNearestNeighborSearch 19 | import PrivateNearestNeighborSearchProtobuf 20 | 21 | enum VectorTypeArguments: String, CaseIterable, ExpressibleByArgument { 22 | /// Each vector's entry is uniform random from `[-1.0, 1.0]` 23 | case random 24 | /// The vector of the i'th row is all 0s except a 1 at index `i % vectorDimension`. 25 | case unit 26 | } 27 | 28 | @main 29 | struct GenerateDatabaseCommand: ParsableCommand { 30 | static let configuration: CommandConfiguration = .init( 31 | commandName: "PNNSGenerateDatabase", version: Version.current.description) 32 | 33 | @Option(help: "Path to output database. Must end in '.txtpb' or '.binpb'.") 34 | var outputDatabase: String 35 | 36 | @Option(help: "Number of rows in the database.") 37 | var rowCount: Int 38 | 39 | @Option(help: "Number of entries in each row's vector.") 40 | var vectorDimension: Int 41 | 42 | @Option(help: "Number of bytes of metadata for each row.") 43 | var metadataSize: Int 44 | 45 | @Option var vectorType: VectorTypeArguments 46 | 47 | mutating func run() throws { 48 | let rows: [DatabaseRow] = (0.. Note: For a more compact format, use the `.binpb` extension to save the database in protocol buffer binary format. 41 | 42 | 2. We view a few rows from the database with 43 | ```sh 44 | head database.txtpb 45 | ``` 46 | which shows 47 | ```json 48 | rows { 49 | entry_metadata: "000" 50 | vector: [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] 51 | } 52 | rows { 53 | entry_id: 1 54 | entry_metadata: "111" 55 | vector: [0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] 56 | } 57 | rows { 58 | ``` 59 | 60 | You can use `PNNSProcessDatabase` to prepare the database for hosting PNNS queries. 61 | -------------------------------------------------------------------------------- /Sources/PrivateInformationRetrieval/PrivateInformationRetrieval.docc/PrivateInformationRetrieval.md: -------------------------------------------------------------------------------- 1 | # ``PrivateInformationRetrieval`` 2 | 3 | Private information retrieval (PIR) enables private database lookup. 4 | 5 | ## Overview 6 | 7 | Private information retrieval (PIR) enables a client to perform a database lookup from a server hosting a keyword-value database, *without the server learning the keyword in the client's query.*. 8 | Each row in the database is a *keyword* with an associated *value*. 9 | During the PIR protocol, the client issues a query using its private keyword, and learns the value associated with the keyword. 10 | 11 | A trivial implementation of PIR is to have the client issue a generic "fetch database" request, independent of its private keyword. 12 | Then the server sends the entire database to the client. 13 | While this *trivial PIR* protocol satisfies the privacy and correctness requirements of PIR, it is only feasible for small databases. 14 | 15 | The PIR implementation in Swift Homomorphic Encryption uses homomorphic encryption to improve upon the trivial PIR protocol. 16 | 17 | > Warning: PIR is asymmetric, meaning the client may learn keyword-value pairs not requested, as happens in trivial PIR for instance. 18 | > A variant of PIR, known as *symmetric PIR*, would be required to ensure the client does not learn anything about values it did not request. 19 | 20 | ## Topics 21 | 22 | ### Articles 23 | - 24 | - 25 | - 26 | -------------------------------------------------------------------------------- /Sources/PrivateInformationRetrieval/Util.swift: -------------------------------------------------------------------------------- 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 Foundation 16 | 17 | extension [UInt8] { 18 | func utf8OrBase64() -> String { 19 | if let utf8 = String(validating: self, as: UTF8.self) { 20 | "\(utf8) (utf8)" 21 | } else { 22 | "\(Data(self).base64EncodedString()) (base64)" 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/PrivateInformationRetrievalProtobuf/ConversionApi.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2024 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 | import HomomorphicEncryptionProtobuf 17 | import PrivateInformationRetrieval 18 | 19 | extension Apple_SwiftHomomorphicEncryption_Api_Pir_V1_PIRResponse { 20 | /// Converts the protobuf object to a native type. 21 | /// - Parameter context: Context to associate with the native type. 22 | /// - Returns: The converted native type. 23 | /// - Throws: Error upon invalid protobuf object. 24 | public func native(context: Context) throws -> Response { 25 | let ciphertexts: [[Scheme.CoeffCiphertext]] = try replies.map { reply in 26 | let serializedCiphertexts: [SerializedCiphertext] = try reply.native() 27 | return try serializedCiphertexts.map { serialized in 28 | try Scheme.CoeffCiphertext(deserialize: serialized, context: context, moduliCount: 1) 29 | } 30 | } 31 | return Response(ciphertexts: ciphertexts) 32 | } 33 | } 34 | 35 | extension Response { 36 | /// Converts the native object into a protobuf object. 37 | /// - Returns: The converted protobuf object. 38 | /// - Throws: Error upon unsupported object. 39 | public func proto() throws -> Apple_SwiftHomomorphicEncryption_Api_Pir_V1_PIRResponse { 40 | try Apple_SwiftHomomorphicEncryption_Api_Pir_V1_PIRResponse.with { pirResponse in 41 | pirResponse.replies = try ciphertexts.map { reply in 42 | try reply.map { try $0.serialize(forDecryption: true) }.proto() 43 | } 44 | } 45 | } 46 | } 47 | 48 | extension Apple_SwiftHomomorphicEncryption_Api_Pir_V1_PIRShardConfig { 49 | /// Converts the protobuf object to a native type. 50 | /// - Parameters: 51 | /// - batchSize: Number of queries in a batch. 52 | /// - evaluationKeyConfig: Evaluation key configuration 53 | /// - Returns: The converted native type. 54 | public func native(batchSize: Int, evaluationKeyConfig: EvaluationKeyConfig) -> IndexPirParameter { 55 | IndexPirParameter( 56 | entryCount: Int(numEntries), 57 | entrySizeInBytes: Int(entrySize), 58 | dimensions: dimensions.map(Int.init), 59 | batchSize: batchSize, 60 | evaluationKeyConfig: evaluationKeyConfig) 61 | } 62 | } 63 | 64 | extension IndexPirParameter { 65 | /// Converts the native object into a protobuf object. 66 | /// - Parameter shardID: Optional identifier to associate with the shard 67 | /// - Returns: The converted protobuf object. 68 | public func proto(shardID: String = "") -> Apple_SwiftHomomorphicEncryption_Api_Pir_V1_PIRShardConfig { 69 | Apple_SwiftHomomorphicEncryption_Api_Pir_V1_PIRShardConfig.with { shardConfig in 70 | shardConfig.numEntries = UInt64(entryCount) 71 | shardConfig.entrySize = UInt64(entrySizeInBytes) 72 | shardConfig.dimensions = dimensions.map(UInt64.init) 73 | shardConfig.shardID = shardID 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Sources/PrivateInformationRetrievalProtobuf/ConversionError.swift: -------------------------------------------------------------------------------- 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 Foundation 16 | import SwiftProtobuf 17 | 18 | enum ConversionError: Error { 19 | case unrecognizedEnumValue(enum: any Enum.Type, value: Int) 20 | case unspecifiedEnumValue(enum: any Enum.Type) 21 | } 22 | 23 | extension ConversionError: LocalizedError { 24 | public var errorDescription: String? { 25 | switch self { 26 | case let .unrecognizedEnumValue(enum: enume, value: value): 27 | "Unrecognized value \(value) in enum \(enume)" 28 | case let .unspecifiedEnumValue(enum: enume): 29 | "Unspecified value for enum \(enume)" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/PrivateInformationRetrievalProtobuf/PrivateInformationRetrievalProtobuf.docc/PrivateInformationRetrievalProtobuf.md: -------------------------------------------------------------------------------- 1 | # ``PrivateInformationRetrievalProtobuf`` 2 | 3 | Protocol buffer support for ``PrivateInformationRetrieval`` 4 | 5 | ## Overview 6 | `PrivateInformationRetrievalProtobuf` contains supports for using [PrivateInformationRetrieval](https://swiftpackageindex.com/apple/swift-homomorphic-encryption/main/documentation/privateinformationretrieval) with [protocol buffers](https://protobuf.dev/), commonly referred to as `protobuf`. 7 | This module contains: 8 | * the generated Swift code to use the protobuf types. 9 | * conversion between protobuf types and [PrivateInformationRetrieval](https://swiftpackageindex.com/apple/swift-homomorphic-encryption/main/documentation/privateinformationretrieval) runtime types. 10 | 11 | The protobuf schemas are defined at [https://github.com/apple/swift-homomorphic-encryption-protobuf](https://github.com/apple/swift-homomorphic-encryption-protobuf). 12 | -------------------------------------------------------------------------------- /Sources/PrivateInformationRetrievalProtobuf/generated/README.md: -------------------------------------------------------------------------------- 1 | # Generated files 2 | 3 | Files in this directory are auto-generated using swift-protobuf. 4 | See the [dependencies](../../../README.md#dependencies) for the swift-protobuf version. 5 | 6 | ## Requirements 7 | * [swift-protobuf](https://github.com/apple/swift-protobuf) 8 | 9 | ## Generate Swift files 10 | To generate the Swift files: 11 | 12 | 1. Change directory to `` 13 | 2. Run 14 | ```sh 15 | ./Utilities/generate-protobuf-files.sh 16 | ``` 17 | -------------------------------------------------------------------------------- /Sources/PrivateInformationRetrievalProtobuf/generated/apple_swift_homomorphic_encryption_pir_v1_pir_algorithm.pb.swift: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. 2 | // swift-format-ignore-file 3 | // swiftlint:disable all 4 | // 5 | // Generated by the Swift generator plugin for the protocol buffer compiler. 6 | // Source: apple/swift_homomorphic_encryption/pir/v1/pir_algorithm.proto 7 | // 8 | // For information on using the generated types, please see the documentation: 9 | // https://github.com/apple/swift-protobuf/ 10 | 11 | // Copyright 2024 Apple Inc. and the Swift Homomorphic Encryption project authors 12 | // 13 | // Licensed under the Apache License, Version 2.0 (the "License"); 14 | // you may not use this file except in compliance with the License. 15 | // You may obtain a copy of the License at 16 | // 17 | // http://www.apache.org/licenses/LICENSE-2.0 18 | // 19 | // Unless required by applicable law or agreed to in writing, software 20 | // distributed under the License is distributed on an "AS IS" BASIS, 21 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 22 | // See the License for the specific language governing permissions and 23 | // limitations under the License. 24 | 25 | import SwiftProtobuf 26 | 27 | // If the compiler emits an error on this type, it is because this file 28 | // was generated by a version of the `protoc` Swift plug-in that is 29 | // incompatible with the version of SwiftProtobuf to which you are linking. 30 | // Please ensure that you are building against the same version of the API 31 | // that was used to generate this file. 32 | fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { 33 | struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} 34 | typealias Version = _2 35 | } 36 | 37 | /// Different algorithms for PIR. 38 | public enum Apple_SwiftHomomorphicEncryption_Pir_V1_PirAlgorithm: SwiftProtobuf.Enum, Swift.CaseIterable { 39 | public typealias RawValue = Int 40 | 41 | /// AclsPir. 42 | /// 43 | /// 44 | case aclsPir // = 0 45 | 46 | /// MulPir. 47 | /// 48 | /// 49 | case mulPir // = 1 50 | case UNRECOGNIZED(Int) 51 | 52 | public init() { 53 | self = .aclsPir 54 | } 55 | 56 | public init?(rawValue: Int) { 57 | switch rawValue { 58 | case 0: self = .aclsPir 59 | case 1: self = .mulPir 60 | default: self = .UNRECOGNIZED(rawValue) 61 | } 62 | } 63 | 64 | public var rawValue: Int { 65 | switch self { 66 | case .aclsPir: return 0 67 | case .mulPir: return 1 68 | case .UNRECOGNIZED(let i): return i 69 | } 70 | } 71 | 72 | // The compiler won't synthesize support with the UNRECOGNIZED case. 73 | public static let allCases: [Apple_SwiftHomomorphicEncryption_Pir_V1_PirAlgorithm] = [ 74 | .aclsPir, 75 | .mulPir, 76 | ] 77 | 78 | } 79 | 80 | // MARK: - Code below here is support for the SwiftProtobuf runtime. 81 | 82 | extension Apple_SwiftHomomorphicEncryption_Pir_V1_PirAlgorithm: SwiftProtobuf._ProtoNameProviding { 83 | public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 84 | 0: .same(proto: "PIR_ALGORITHM_ACLS_PIR"), 85 | 1: .same(proto: "PIR_ALGORITHM_MUL_PIR"), 86 | ] 87 | } 88 | -------------------------------------------------------------------------------- /Sources/PrivateInformationRetrievalProtobuf/protobuf_module_mappings.txtpb: -------------------------------------------------------------------------------- 1 | mapping { 2 | module_name: "HomomorphicEncryptionProtobuf" 3 | proto_file_path: "apple/swift_homomorphic_encryption/v1/he.proto" 4 | } 5 | -------------------------------------------------------------------------------- /Sources/PrivateNearestNeighborSearch/Database.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2024 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 | /// One row in a nearest-neighbor search database. 16 | public struct DatabaseRow: Codable, Equatable, Hashable, Sendable { 17 | /// Unique identifier for the database entry. 18 | public let entryId: UInt64 19 | 20 | /// Metadata associated with the entry. 21 | public let entryMetadata: [UInt8] 22 | 23 | /// Vector for use in nearest neighbor search. 24 | public let vector: [Float] 25 | 26 | /// Creates a new ``DatabaseRow``. 27 | /// - Parameters: 28 | /// - entryId: Unique identifier for the database entry. 29 | /// - entryMetadata: Metadata associated with the entry. 30 | /// - vector: Vector for use in nearest neighbor search. 31 | public init(entryId: UInt64, entryMetadata: [UInt8], vector: [Float]) { 32 | self.entryId = entryId 33 | self.entryMetadata = entryMetadata 34 | self.vector = vector 35 | } 36 | } 37 | 38 | /// Database for nearest neighbor search. 39 | public struct Database: Codable, Equatable, Hashable, Sendable { 40 | /// Rows in the database. 41 | public let rows: [DatabaseRow] 42 | 43 | /// Creates a new ``Database``. 44 | /// - Parameter rows: Rows in the database. 45 | public init(rows: [DatabaseRow]) { 46 | self.rows = rows 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/PrivateNearestNeighborSearch/PnnsProtocol.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2024 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 | /// A nearest neighbor search query. 18 | public struct Query: Equatable, Sendable { 19 | /// Encrypted query; one matrix per plaintext CRT modulus. 20 | public let ciphertextMatrices: [CiphertextMatrix] 21 | 22 | /// Creates a ``Query``. 23 | /// - Parameter ciphertextMatrices: Encrypted query. 24 | public init(ciphertextMatrices: [CiphertextMatrix]) { 25 | self.ciphertextMatrices = ciphertextMatrices 26 | } 27 | } 28 | 29 | /// A nearest neighbor search response. 30 | public struct Response: Sendable { 31 | /// Encrypted distances; one matrix per plaintext CRT modulus. 32 | public let ciphertextMatrices: [CiphertextMatrix] 33 | /// The entry identifiers the server computed distances for. 34 | public let entryIds: [UInt64] 35 | /// Metadata for each entry the server computed distances for. 36 | public let entryMetadatas: [[UInt8]] 37 | 38 | /// Creates a new ``Response``. 39 | /// - Parameters: 40 | /// - ciphertextMatrices: Encrypted distances; one matrix per plaintext CRT modulus. 41 | /// - entryIds: An identifiers the server computed distances for. 42 | /// - entryMetadatas: Metadata for each entry the server computed distances for. 43 | public init( 44 | ciphertextMatrices: [CiphertextMatrix] = [], 45 | entryIds: [UInt64] = [], 46 | entryMetadatas: [[UInt8]] = []) 47 | { 48 | self.ciphertextMatrices = ciphertextMatrices 49 | self.entryIds = entryIds 50 | self.entryMetadatas = entryMetadatas 51 | } 52 | } 53 | 54 | /// Distances from one or more query vector to the database rows. 55 | public struct DatabaseDistances: Sendable { 56 | /// Each row contains the distances from a database entry to each query vector. 57 | public let distances: Array2d 58 | /// Identifier for each entry in the database. 59 | public let entryIds: [UInt64] 60 | /// Metadata for each entry in the database. 61 | public let entryMetadatas: [[UInt8]] 62 | 63 | /// Creates a new ``DatabaseDistances``. 64 | /// - Parameters: 65 | /// - distances: Each row contains the distances from a database entry to each query vector. 66 | /// - entryIds: Identifier for each entry in the database 67 | /// - entryMetadatas: Metadata for each entry in the database 68 | public init( 69 | distances: Array2d = Array2d(), 70 | entryIds: [UInt64] = [], 71 | entryMetadatas: [[UInt8]] = []) 72 | { 73 | self.distances = distances 74 | self.entryIds = entryIds 75 | self.entryMetadatas = entryMetadatas 76 | } 77 | } 78 | 79 | extension Response { 80 | /// Computes the noise budget of the response. 81 | /// 82 | /// The *noise budget* of the each ciphertext in the response decreases throughout HE operations. Once a 83 | /// ciphertext's noise budget is 84 | /// below 85 | /// `HeScheme/minNoiseBudget`, decryption may yield inaccurate plaintexts. 86 | /// - Parameters: 87 | /// - secretKey: Secret key. 88 | /// - variableTime: If `true`, indicates the secret key coefficients may be leaked through timing. 89 | /// - Returns: The noise budget. 90 | /// - Throws: Error upon failure to compute the noise budget. 91 | /// - Warning: Leaks `secretKey` through timing. Should be used for testing only. 92 | /// - Warning: The noise budget depends on the encrypted message, which is impractical to know apriori. So this 93 | /// function should be treated only as a rough proxy for correct decryption, rather than a source of truth. 94 | /// See Section 2 of for more details. 95 | @inlinable 96 | public func noiseBudget(using secretKey: Scheme.SecretKey, variableTime: Bool) throws -> Double { 97 | try ciphertextMatrices.map { ciphertextMatrix in 98 | try ciphertextMatrix.noiseBudget(using: secretKey, variableTime: variableTime) 99 | }.min() ?? -Double.infinity 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Sources/PrivateNearestNeighborSearch/PrivateNearestNeighborSearch.docc/PrivateNearestNeighborSearch.md: -------------------------------------------------------------------------------- 1 | # ``PrivateNearestNeighborSearch`` 2 | 3 | Private nearest neighbor search (PNNS) enables search on vector databases. 4 | 5 | ## Overview 6 | 7 | Private nearest neighbor search (PNNS) enables a client with a private vector to search for the nearest vectors in a database hosted by a server, *without the server learning the client's vector.*. 8 | Each row in the database is a *vector* with an associated *entry identifier* and *entry metadata*. 9 | During the PNNS protocol, the client issues a query using its private vector, and learns the nearest neighbors according to a ``DistanceMetric``. 10 | Specifically, the client learns the distances between the client's query vector to the nearest neighbors, as well as the entry identifier and entry metadata of the nearest neighbors. 11 | 12 | A trivial implementation of PNNS is to have the client issue a generic "fetch database" request, independent of its private vector. 13 | Then the server sends the entire database to the client, who computes the distances locally. 14 | While this *trivial PNNS* protocol satisfies the privacy and correctness requirements of PNNS, it is only feasible for small databases. 15 | 16 | The PNNS implementation in Swift Homomorphic Encryption uses homomorphic encryption to improve upon the trivial PNNS protocol. 17 | 18 | See documentation for [PNNSGenerateDatabase](https://swiftpackageindex.com/apple/swift-homomorphic-encryption/main/documentation/pnnsgeneratedatabase) and [PNNSProcessDatabase](https://swiftpackageindex.com/apple/swift-homomorphic-encryption/main/documentation/pnnsprocessdatabase) for more information. 19 | -------------------------------------------------------------------------------- /Sources/PrivateNearestNeighborSearch/SerializedCiphertextMatrix.swift: -------------------------------------------------------------------------------- 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 | /// Stores a matrix of scalars as ciphertexts. 18 | public struct SerializedCiphertextMatrix: Equatable, Sendable { 19 | /// Dimensions of the matrix. 20 | public let dimensions: MatrixDimensions 21 | 22 | /// Packing with which the data is stored. 23 | public let packing: MatrixPacking 24 | 25 | /// Ciphertexts encrypting the scalars. 26 | public let ciphertexts: [SerializedCiphertext] 27 | 28 | /// Creates a new ``SerializedCiphertextMatrix``. 29 | /// - Parameters: 30 | /// - dimensions: Ciphertext matrix dimensions. 31 | /// - packing: The packing with which the data is stored. 32 | /// - ciphertexts: Ciphertexts encrypting the data. 33 | /// - Throws: Error upon failure to initialize the serialized ciphertext matrix. 34 | @inlinable 35 | public init( 36 | dimensions: MatrixDimensions, 37 | packing: MatrixPacking, 38 | ciphertexts: [SerializedCiphertext]) throws 39 | { 40 | self.dimensions = dimensions 41 | self.packing = packing 42 | self.ciphertexts = ciphertexts 43 | } 44 | } 45 | 46 | extension CiphertextMatrix { 47 | /// Deserializes a serialized ciphertext matrix. 48 | /// - Parameters: 49 | /// - serialized: Serialized ciphertext matrix. 50 | /// - context: Context to associate with the ciphertext matrix. 51 | /// - moduliCount: Number of moduli in each serialized ciphertext. If not set, deserialization will use the 52 | /// top-level ciphertext with all the moduli. 53 | /// - Throws: Error upon failure to deserialize the ciphertext matrix. 54 | @inlinable 55 | public init( 56 | deserialize serialized: SerializedCiphertextMatrix, 57 | context: Context, 58 | moduliCount: Int? = nil) throws 59 | { 60 | let ciphertexts: [Ciphertext] = try serialized.ciphertexts.map { serializedCiphertext in 61 | try Ciphertext(deserialize: serializedCiphertext, context: context, moduliCount: moduliCount) 62 | } 63 | try self.init(dimensions: serialized.dimensions, packing: serialized.packing, ciphertexts: ciphertexts) 64 | } 65 | 66 | /// Serializes the ciphertext matrix. 67 | /// - Parameter forDecryption: If true, serialization may use a more concise format, yielding ciphertexts which, 68 | /// once deserialized, are only compatible with decryption, and not any other HE operations. 69 | /// - Returns: The serialized ciphertext matrix. 70 | /// - Throws: Error upon failure to serialize. 71 | @inlinable 72 | public func serialize(forDecryption: Bool = false) throws -> SerializedCiphertextMatrix { 73 | try SerializedCiphertextMatrix( 74 | dimensions: dimensions, 75 | packing: packing, 76 | ciphertexts: ciphertexts.map { ciphertext in try ciphertext.serialize(forDecryption: forDecryption) }) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Sources/PrivateNearestNeighborSearch/SerializedPlaintextMatrix.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2024 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 | /// Stores a matrix of scalars as plaintexts. 18 | public struct SerializedPlaintextMatrix: Equatable, Sendable { 19 | /// Dimensions of the matrix. 20 | public let dimensions: MatrixDimensions 21 | 22 | /// Packing with which the data is stored. 23 | public let packing: MatrixPacking 24 | 25 | /// Plaintexts encoding the scalars. 26 | public let plaintexts: [SerializedPlaintext] 27 | 28 | /// Creates a new ``SerializedPlaintextMatrix``. 29 | /// - Parameters: 30 | /// - dimensions: Plaintext matrix dimensions. 31 | /// - packing: The packing with which the data is stored. 32 | /// - plaintexts: Plaintexts encoding the data. 33 | /// - Throws: Error upon failure to initialize the serialized plaintext matrix. 34 | @inlinable 35 | public init( 36 | dimensions: MatrixDimensions, 37 | packing: MatrixPacking, 38 | plaintexts: [SerializedPlaintext]) throws 39 | { 40 | self.dimensions = dimensions 41 | self.packing = packing 42 | self.plaintexts = plaintexts 43 | } 44 | } 45 | 46 | extension PlaintextMatrix { 47 | /// Serializes the plaintext matrix. 48 | @inlinable 49 | public func serialize() throws -> SerializedPlaintextMatrix { 50 | try SerializedPlaintextMatrix( 51 | dimensions: dimensions, 52 | packing: packing, 53 | plaintexts: plaintexts.map { plaintext in plaintext.serialize() }) 54 | } 55 | } 56 | 57 | extension PlaintextMatrix where Format == Coeff { 58 | /// Deserializes a plaintext matrix. 59 | /// - Parameters: 60 | /// - serialized: Serialized plaintext matrix. 61 | /// - context: Context to associate with the plaintext matrix. 62 | /// - Throws: Error upon failure to deserialize. 63 | init(deserialize serialized: SerializedPlaintextMatrix, context: Context) throws { 64 | let plaintexts = try serialized.plaintexts.map { serializedPlaintext in 65 | try Plaintext(deserialize: serializedPlaintext, context: context) 66 | } 67 | try self.init(dimensions: serialized.dimensions, packing: serialized.packing, plaintexts: plaintexts) 68 | } 69 | } 70 | 71 | extension PlaintextMatrix where Format == Eval { 72 | /// Deserializes a plaintext matrix. 73 | /// - Parameters: 74 | /// - serialized: Serialized plaintext matrix. 75 | /// - context: Context to associate with the plaintext matrix. 76 | /// - moduliCount: Optional number of moduli to associate with each plaintext. If not set, each plaintext will 77 | /// have the top-level ciphertext context with all the moduli. 78 | /// - Throws: Error upon failure to deserialize. 79 | init( 80 | deserialize serialized: SerializedPlaintextMatrix, 81 | context: Context, 82 | moduliCount: Int? = nil) throws 83 | { 84 | let plaintexts = try serialized.plaintexts.map { serializedPlaintext in 85 | try Plaintext(deserialize: serializedPlaintext, context: context, moduliCount: moduliCount) 86 | } 87 | try self.init(dimensions: serialized.dimensions, packing: serialized.packing, plaintexts: plaintexts) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Sources/PrivateNearestNeighborSearch/Server.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2024 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 | /// Private nearest neighbor server. 18 | public struct Server: Sendable { 19 | /// The database. 20 | public let database: ProcessedDatabase 21 | 22 | /// Configuration. 23 | public var config: ServerConfig { 24 | database.serverConfig 25 | } 26 | 27 | /// Client configuration. 28 | public var clientConfig: ClientConfig { 29 | config.clientConfig 30 | } 31 | 32 | /// Configuration needed for private nearest neighbor search. 33 | public var evaluationKeyConfig: EvaluationKeyConfig { 34 | config.clientConfig.evaluationKeyConfig 35 | } 36 | 37 | /// One context per plaintext modulus. 38 | public var contexts: [Context] { 39 | database.contexts 40 | } 41 | 42 | /// Creates a new ``Server``. 43 | /// - Parameter database: Processed database. 44 | /// - Throws: Error upon failure to create the server. 45 | @inlinable 46 | public init(database: ProcessedDatabase) throws { 47 | guard database.serverConfig.distanceMetric == .cosineSimilarity else { 48 | throw PnnsError.wrongDistanceMetric(got: database.serverConfig.distanceMetric, expected: .cosineSimilarity) 49 | } 50 | self.database = database 51 | } 52 | 53 | /// Compute the encrypted response to a query. 54 | /// - Parameters: 55 | /// - query: Query. 56 | /// - evaluationKey: Evaluation key to aid in the server computation. 57 | /// - Returns: The response. 58 | /// - Throws: Error upon failure to compute a response. 59 | @inlinable 60 | public func computeResponse(to query: Query, 61 | using evaluationKey: EvaluationKey) throws -> Response 62 | { 63 | guard query.ciphertextMatrices.count == database.plaintextMatrices.count else { 64 | throw PnnsError.invalidQuery(reason: InvalidQueryReason.wrongCiphertextMatrixCount( 65 | got: query.ciphertextMatrices.count, 66 | expected: database.plaintextMatrices.count)) 67 | } 68 | 69 | let responseMatrices = try zip(query.ciphertextMatrices, database.plaintextMatrices) 70 | .map { ciphertextMatrix, plaintextMatrix in 71 | var responseMatrix = try plaintextMatrix.mulTranspose( 72 | matrix: ciphertextMatrix.convertToCanonicalFormat(), 73 | using: evaluationKey) 74 | // Reduce response size by mod-switching to a single modulus. 75 | try responseMatrix.modSwitchDownToSingle() 76 | return try responseMatrix.convertToCoeffFormat() 77 | } 78 | 79 | return Response( 80 | ciphertextMatrices: responseMatrices, 81 | entryIds: database.entryIds, 82 | entryMetadatas: database.entryMetadatas) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Sources/PrivateNearestNeighborSearchProtobuf/ConversionApi.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2024 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 Foundation 16 | 17 | import HomomorphicEncryption 18 | import HomomorphicEncryptionProtobuf 19 | import PrivateNearestNeighborSearch 20 | 21 | extension Apple_SwiftHomomorphicEncryption_Api_Pnns_V1_PNNSShardResponse { 22 | /// Converts the protobuf object to a native type. 23 | /// - Parameter contexts: Contexts to associate with the native type; one context per plaintext modulus. 24 | /// - Returns: The converted native type. 25 | /// - Throws: Error upon invalid protobuf object. 26 | public func native(contexts: [Context]) throws -> Response { 27 | precondition(contexts.count == reply.count) 28 | let matrices: [CiphertextMatrix] = try zip(reply, contexts).map { matrix, context in 29 | let serialized: SerializedCiphertextMatrix = try matrix.native() 30 | return try CiphertextMatrix(deserialize: serialized, context: context, moduliCount: 1) 31 | } 32 | return Response( 33 | ciphertextMatrices: matrices, 34 | entryIds: entryIds, 35 | entryMetadatas: entryMetadatas.map { metadata in Array(metadata) }) 36 | } 37 | } 38 | 39 | extension Response { 40 | /// Converts the native object into a protobuf object. 41 | /// - Returns: The converted protobuf object. 42 | /// - Throws: Error upon unsupported object. 43 | public func proto() throws -> Apple_SwiftHomomorphicEncryption_Api_Pnns_V1_PNNSShardResponse { 44 | try Apple_SwiftHomomorphicEncryption_Api_Pnns_V1_PNNSShardResponse.with { pnnsResponse in 45 | pnnsResponse.reply = try ciphertextMatrices.map { matrix in 46 | try matrix.serialize(forDecryption: true).proto() 47 | } 48 | pnnsResponse.entryIds = entryIds 49 | pnnsResponse.entryMetadatas = entryMetadatas.map { bytes in Data(bytes) } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/PrivateNearestNeighborSearchProtobuf/ConversionError.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2024 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 Foundation 16 | import SwiftProtobuf 17 | 18 | /// Error type when converting between protobuf and native objects. 19 | public enum ConversionError: Error { 20 | case unrecognizedEnumValue(enum: any Enum.Type, value: Int) 21 | case unsetField(field: String, message: any Message.Type) 22 | case unsetOneof(oneof: any Message.Type, field: String) 23 | } 24 | 25 | extension ConversionError { 26 | static func unsetOneof(oneof: any Message.Type, field: AnyKeyPath) -> Self { 27 | .unsetOneof(oneof: oneof, field: String(reflecting: field)) 28 | } 29 | 30 | static func unsetField(_ field: AnyKeyPath, in message: any Message.Type) -> Self { 31 | .unsetField(field: String(reflecting: field), message: message) 32 | } 33 | } 34 | 35 | extension ConversionError: LocalizedError { 36 | public var errorDescription: String? { 37 | switch self { 38 | case let .unrecognizedEnumValue(enum: enume, value): 39 | "Unrecognized value \(value) in enum \(enume)" 40 | case let .unsetField(field, message): 41 | "Unset field \(field) in message \(message)" 42 | case let .unsetOneof(oneof, field): 43 | "Unset oneof in message \(oneof) for field \(field)" 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/PrivateNearestNeighborSearchProtobuf/PrivateNearestNeighborSearchProtobuf.docc/PrivateNearestNeighborSearch.md: -------------------------------------------------------------------------------- 1 | # ``PrivateNearestNeighborSearchProtobuf`` 2 | 3 | Protocol buffer support for ``PrivateNearestNeighborSearch`` 4 | 5 | ## Overview 6 | `PrivateNearestNeighborProtobuf` contains supports for using [PrivateNearestNeighborSearch](https://swiftpackageindex.com/apple/swift-homomorphic-encryption/main/documentation/privatenearestneighborsearch) with [protocol buffers](https://protobuf.dev/), commonly referred to as `protobuf`. 7 | This module contains: 8 | * the generated Swift code to use the protobuf types. 9 | * conversion between protobuf types and [PrivateNearestNeighborSearch](https://swiftpackageindex.com/apple/swift-homomorphic-encryption/main/documentation/privatenearestneighborsearch) runtime types. 10 | 11 | The protobuf schemas are defined at [https://github.com/apple/swift-homomorphic-encryption-protobuf](https://github.com/apple/swift-homomorphic-encryption-protobuf). 12 | -------------------------------------------------------------------------------- /Sources/PrivateNearestNeighborSearchProtobuf/generated/README.md: -------------------------------------------------------------------------------- 1 | # Generated files 2 | 3 | Files in this directory are auto-generated using swift-protobuf. 4 | See the [dependencies](../../../README.md#dependencies) for the swift-protobuf version. 5 | 6 | ## Requirements 7 | * [swift-protobuf](https://github.com/apple/swift-protobuf) 8 | 9 | ## Generate Swift files 10 | To generate the Swift files: 11 | 12 | 1. Change directory to `` 13 | 2. Run 14 | ```sh 15 | ./Utilities/generate-protobuf-files.sh``` 16 | -------------------------------------------------------------------------------- /Sources/PrivateNearestNeighborSearchProtobuf/generated/apple_swift_homomorphic_encryption_pnns_v1_pnns_distance_metric.pb.swift: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. 2 | // swift-format-ignore-file 3 | // swiftlint:disable all 4 | // 5 | // Generated by the Swift generator plugin for the protocol buffer compiler. 6 | // Source: apple/swift_homomorphic_encryption/pnns/v1/pnns_distance_metric.proto 7 | // 8 | // For information on using the generated types, please see the documentation: 9 | // https://github.com/apple/swift-protobuf/ 10 | 11 | // Copyright 2024 Apple Inc. and the Swift Homomorphic Encryption project authors 12 | // 13 | // Licensed under the Apache License, Version 2.0 (the "License"); 14 | // you may not use this file except in compliance with the License. 15 | // You may obtain a copy of the License at 16 | // 17 | // http://www.apache.org/licenses/LICENSE-2.0 18 | // 19 | // Unless required by applicable law or agreed to in writing, software 20 | // distributed under the License is distributed on an "AS IS" BASIS, 21 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 22 | // See the License for the specific language governing permissions and 23 | // limitations under the License. 24 | 25 | import SwiftProtobuf 26 | 27 | // If the compiler emits an error on this type, it is because this file 28 | // was generated by a version of the `protoc` Swift plug-in that is 29 | // incompatible with the version of SwiftProtobuf to which you are linking. 30 | // Please ensure that you are building against the same version of the API 31 | // that was used to generate this file. 32 | fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { 33 | struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} 34 | typealias Version = _2 35 | } 36 | 37 | /// Metric for distances between vectors. 38 | public enum Apple_SwiftHomomorphicEncryption_Pnns_V1_DistanceMetric: SwiftProtobuf.Enum, Swift.CaseIterable { 39 | public typealias RawValue = Int 40 | 41 | /// Cosine similarity. 42 | case cosineSimilarity // = 0 43 | case UNRECOGNIZED(Int) 44 | 45 | public init() { 46 | self = .cosineSimilarity 47 | } 48 | 49 | public init?(rawValue: Int) { 50 | switch rawValue { 51 | case 0: self = .cosineSimilarity 52 | default: self = .UNRECOGNIZED(rawValue) 53 | } 54 | } 55 | 56 | public var rawValue: Int { 57 | switch self { 58 | case .cosineSimilarity: return 0 59 | case .UNRECOGNIZED(let i): return i 60 | } 61 | } 62 | 63 | // The compiler won't synthesize support with the UNRECOGNIZED case. 64 | public static let allCases: [Apple_SwiftHomomorphicEncryption_Pnns_V1_DistanceMetric] = [ 65 | .cosineSimilarity, 66 | ] 67 | 68 | } 69 | 70 | // MARK: - Code below here is support for the SwiftProtobuf runtime. 71 | 72 | extension Apple_SwiftHomomorphicEncryption_Pnns_V1_DistanceMetric: SwiftProtobuf._ProtoNameProviding { 73 | public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 74 | 0: .same(proto: "DISTANCE_METRIC_COSINE_SIMILARITY"), 75 | ] 76 | } 77 | -------------------------------------------------------------------------------- /Sources/PrivateNearestNeighborSearchProtobuf/protobuf_module_mappings.txtpb: -------------------------------------------------------------------------------- 1 | mapping { 2 | module_name: "HomomorphicEncryptionProtobuf" 3 | proto_file_path: "apple/swift_homomorphic_encryption/v1/he.proto" 4 | } 5 | -------------------------------------------------------------------------------- /Sources/TestUtilities/PnnsUtilities/CosineSimilarityTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright 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 | import PrivateNearestNeighborSearch 17 | import Testing 18 | 19 | extension PrivateNearestNeighborSearchUtil { 20 | /// Tests cosine similarity computation. 21 | public enum CosineSimilarityTests { 22 | /// Tests normalization and scaling. 23 | public static func normalizeRowsAndScale() throws { 24 | // swiftlint:disable:next nesting 25 | struct TestCase { 26 | let scalingFactor: Float 27 | let norm: Array2d.Norm 28 | let input: [[Float]] 29 | let normalized: [[Float]] 30 | let scaled: [[Float]] 31 | let rounded: [[T]] 32 | } 33 | 34 | func runTestCase(testCase: TestCase) throws { 35 | let floatMatrix = Array2d(data: testCase.input) 36 | let normalized = floatMatrix.normalizedRows(norm: testCase.norm) 37 | for (normalized, expected) in zip(normalized.data, testCase.normalized.flatMap(\.self)) { 38 | #expect(normalized.isClose(to: expected)) 39 | } 40 | 41 | let scaled = normalized.scaled(by: testCase.scalingFactor) 42 | for (scaled, expected) in zip(scaled.data, testCase.scaled.flatMap(\.self)) { 43 | #expect(scaled.isClose(to: expected)) 44 | } 45 | let rounded: Array2d = scaled.rounded() 46 | #expect(rounded.data == testCase.rounded.flatMap(\.self)) 47 | } 48 | 49 | let testCases: [TestCase] = [ 50 | TestCase(scalingFactor: 10.0, 51 | norm: Array2d.Norm.Lp(p: 1.0), 52 | input: [[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]], 53 | normalized: [[1.0 / 3.0, 2.0 / 3.0], [3.0 / 7.0, 4.0 / 7.0], [5.0 / 11.0, 6.0 / 11.0]], 54 | scaled: [[10.0 / 3.0, 20.0 / 3.0], [30.0 / 7.0, 40.0 / 7.0], [50.0 / 11.0, 60.0 / 11.0]], 55 | rounded: [[3, 7], [4, 6], [5, 5]]), 56 | TestCase(scalingFactor: 100.0, 57 | norm: Array2d.Norm.Lp(p: 2.0), 58 | input: [[3.0, 4.0], [-5.0, 12.0]], 59 | normalized: [[3.0 / 5.0, 4.0 / 5.0], [-5.0 / 13.0, 12.0 / 13.0]], 60 | scaled: [[300.0 / 5.0, 400.0 / 5.0], [-500.0 / 13.0, 1200.0 / 13.0]], 61 | rounded: [[60, 80], [-38, 92]]), 62 | ] 63 | for testCase in testCases { 64 | try runTestCase(testCase: testCase) 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Sources/TestUtilities/PnnsUtilities/DatabaseTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright 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 | import PrivateNearestNeighborSearch 17 | import Testing 18 | 19 | extension PrivateNearestNeighborSearchUtil { 20 | /// Database tests. 21 | public enum DatabaseTests { 22 | /// Test serialization. 23 | @inlinable 24 | public static func serializedProcessedDatabase(for _: Scheme.Type) throws { 25 | let encryptionParameters = try EncryptionParameters(from: .insecure_n_8_logq_5x18_logt_5) 26 | let vectorDimension = 4 27 | 28 | let rows = (0...10).map { rowIndex in 29 | DatabaseRow( 30 | entryId: rowIndex, 31 | entryMetadata: rowIndex.littleEndianBytes, 32 | vector: Array(repeating: Float(rowIndex), count: vectorDimension)) 33 | } 34 | let database = Database(rows: rows) 35 | 36 | let extraPlaintextModuli = try Scheme.Scalar.generatePrimes( 37 | significantBitCounts: [7], 38 | preferringSmall: true, 39 | nttDegree: encryptionParameters.polyDegree) 40 | let clientConfig = try ClientConfig( 41 | encryptionParameters: encryptionParameters, 42 | scalingFactor: 123, 43 | queryPacking: .denseRow, 44 | vectorDimension: vectorDimension, 45 | evaluationKeyConfig: EvaluationKeyConfig(galoisElements: [3]), 46 | distanceMetric: .cosineSimilarity, 47 | extraPlaintextModuli: extraPlaintextModuli) 48 | let databasePacking = MatrixPacking 49 | .diagonal(babyStepGiantStep: BabyStepGiantStep(vectorDimension: vectorDimension)) 50 | let serverConfig = ServerConfig(clientConfig: clientConfig, databasePacking: databasePacking) 51 | 52 | let processed: ProcessedDatabase = try database.process(config: serverConfig) 53 | let serialized = try processed.serialize() 54 | let deserialized = try ProcessedDatabase(from: serialized, contexts: processed.contexts) 55 | #expect(deserialized == processed) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/TestUtilities/PnnsUtilities/PnnsUtils.swift: -------------------------------------------------------------------------------- 1 | // Copyright 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 | import PrivateNearestNeighborSearch 17 | 18 | /// Testing utilities for PrivateNearestNeighborSearch. 19 | public enum PrivateNearestNeighborSearchUtil {} 20 | 21 | extension PrivateNearestNeighborSearchUtil { 22 | @usableFromInline 23 | package struct DatabaseConfig { 24 | @usableFromInline let rowCount: Int 25 | @usableFromInline let vectorDimension: Int 26 | @usableFromInline let metadataCount: Int 27 | 28 | @inlinable 29 | init(rowCount: Int, vectorDimension: Int, metadataCount: Int = 0) { 30 | self.rowCount = rowCount 31 | self.vectorDimension = vectorDimension 32 | self.metadataCount = metadataCount 33 | } 34 | } 35 | 36 | @inlinable 37 | package static func getDatabaseForTesting(config: DatabaseConfig) -> Database { 38 | let rows = (0..(1) << sumOfPowers) 65 | } 66 | } 67 | 68 | @Test 69 | func sum() { 70 | #expect([UInt8]().sum() == 0) 71 | #expect([7].sum() == 7) 72 | #expect([1, 2, 3].sum() == 6) 73 | #expect([UInt8(255), UInt8(2)].sum() == UInt16(257)) 74 | } 75 | 76 | @Test 77 | func hexString() { 78 | #expect(Array(base64Encoded: "AAAA") == Array(hexEncoded: "000000")) 79 | #expect(Array(base64Encoded: "AAAB") == Array(hexEncoded: "000001")) 80 | let data = Array(randomByteCount: 64) 81 | let hexString = data.hexEncodedString() 82 | #expect(Array(hexEncoded: hexString) == data) 83 | } 84 | 85 | @Test 86 | func base64EncodedString() throws { 87 | #expect(try #require(Array(base64Encoded: "AAAA")).base64EncodedString() == "AAAA") 88 | #expect(try #require(Array(base64Encoded: "AAAB")).base64EncodedString() == "AAAB") 89 | let data = Array(randomByteCount: 64) 90 | let base64String = data.base64EncodedString() 91 | #expect(Array(base64Encoded: base64String) == data) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Tests/HomomorphicEncryptionTests/ZeroizationTests.swift: -------------------------------------------------------------------------------- 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 | @testable import HomomorphicEncryption 16 | import Testing 17 | 18 | @Suite 19 | struct ZeroizationTests { 20 | @Test 21 | func zeroize() { 22 | var buffer = [UInt32](1...10) 23 | let size = 5 * MemoryLayout.size 24 | buffer.withUnsafeMutableBytes { dataPointer in 25 | // swiftlint:disable:next force_unwrapping 26 | HomomorphicEncryption.zeroize(dataPointer.baseAddress!, size) 27 | } 28 | #expect(buffer == [0, 0, 0, 0, 0, 6, 7, 8, 9, 10]) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Tests/PIRGenerateDatabaseTests/PIRGenerateDatabaseTests.swift: -------------------------------------------------------------------------------- 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 | @testable import PIRGenerateDatabase 16 | import Testing 17 | 18 | @Suite 19 | struct PIRGenerateDatabaseTests { 20 | @Test 21 | func valueSizeArguments() throws { 22 | #expect(try #require(ValueSizeArguments(argument: "1")?.range) == 1..<2) 23 | #expect(try #require(ValueSizeArguments(argument: "1..<10")?.range) == 1..<10) 24 | #expect(try #require(ValueSizeArguments(argument: "1...10")?.range) == 1..<11) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tests/PIRProcessDatabaseTests/ProcessDatabaseTests.swift: -------------------------------------------------------------------------------- 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 Foundation 16 | import HomomorphicEncryption 17 | @testable import PIRProcessDatabase 18 | import PrivateInformationRetrieval 19 | import Testing 20 | 21 | @Suite 22 | struct ProcessDatabaseTests { 23 | @Test 24 | func argumentsJsonParsing() throws { 25 | do { 26 | let configString = """ 27 | { 28 | "inputDatabase": "input-database.txtpb", 29 | "outputDatabase": "output-database.txtpb", 30 | "outputEvaluationKeyConfig": "output-evaluation-key-config.txtpb", 31 | "outputPirParameters": "output-pir-params.txtpb", 32 | "rlweParameters": "insecure_n_8_logq_5x18_logt_5", 33 | "databaseEncryptionKeyPath": "teststring", 34 | "trialsPerShard": 1, 35 | "sharding": { 36 | "shardCount": 10 37 | } 38 | } 39 | """ 40 | let configData = try #require(configString.data(using: .utf8)) 41 | let parsedConfig = try JSONDecoder().decode(PIRProcessDatabase.Arguments.self, from: configData) 42 | 43 | let config = PIRProcessDatabase.Arguments( 44 | inputDatabase: "input-database.txtpb", 45 | outputDatabase: "output-database.txtpb", 46 | outputPirParameters: "output-pir-params.txtpb", 47 | rlweParameters: PredefinedRlweParameters.insecure_n_8_logq_5x18_logt_5, 48 | outputEvaluationKeyConfig: "output-evaluation-key-config.txtpb", 49 | sharding: Sharding.shardCount(10), 50 | trialsPerShard: 1) 51 | #expect(parsedConfig == config) 52 | } 53 | 54 | // Can parse default JSON string 55 | do { 56 | let configString = PIRProcessDatabase.Arguments.defaultJsonString() 57 | let configData = try #require(configString.data(using: .utf8)) 58 | #expect(throws: Never.self) { 59 | try JSONDecoder().decode(PIRProcessDatabase.Arguments.self, from: configData) 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Tests/PrivateInformationRetrievalTests/ExpansionTests.swift: -------------------------------------------------------------------------------- 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 _TestUtilities 16 | import HomomorphicEncryption 17 | import PrivateInformationRetrieval 18 | import Testing 19 | 20 | @Suite 21 | struct ExpansionTests { 22 | @Test(arguments: PirKeyCompressionStrategy.allCases) 23 | func expandCiphertextForOneStepTest(keyCompression: PirKeyCompressionStrategy) throws { 24 | try PirTestUtils.ExpansionTests.expandCiphertextForOneStep(scheme: NoOpScheme.self, keyCompression) 25 | try PirTestUtils.ExpansionTests.expandCiphertextForOneStep(scheme: Bfv.self, keyCompression) 26 | try PirTestUtils.ExpansionTests.expandCiphertextForOneStep(scheme: Bfv.self, keyCompression) 27 | } 28 | 29 | @Test 30 | func oneCiphertextRoundtrip() throws { 31 | try PirTestUtils.ExpansionTests.oneCiphertextRoundtrip(scheme: NoOpScheme.self) 32 | try PirTestUtils.ExpansionTests.oneCiphertextRoundtrip(scheme: Bfv.self) 33 | try PirTestUtils.ExpansionTests.oneCiphertextRoundtrip(scheme: Bfv.self) 34 | } 35 | 36 | @Test 37 | func multipleCiphertextsRoundtrip() throws { 38 | try PirTestUtils.ExpansionTests.multipleCiphertextsRoundtrip(scheme: NoOpScheme.self) 39 | try PirTestUtils.ExpansionTests.multipleCiphertextsRoundtrip(scheme: Bfv.self) 40 | try PirTestUtils.ExpansionTests.multipleCiphertextsRoundtrip(scheme: Bfv.self) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Tests/PrivateInformationRetrievalTests/HashBucketTests.swift: -------------------------------------------------------------------------------- 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 _TestUtilities 16 | @testable import PrivateInformationRetrieval 17 | import Testing 18 | 19 | @Suite 20 | struct HashBucketTests { 21 | private let rawBucket: [UInt8] = [3, 24, 95, 141, 179, 34, 113, 254, 37, 5, 22 | 0, 87, 111, 114, 108, 100, 82, 117, 17, 222, 23 | 175, 220, 211, 74, 6, 0, 77, 97, 97, 105, 108, 24 | 109, 192, 21, 173, 109, 218, 248, 187, 80, 8, 25 | 0, 68, 97, 114, 107, 110, 101, 115, 115] 26 | 27 | private func getTestEntry() -> HashBucket.HashBucketEntry { 28 | let size = Int.random(in: 1...100) 29 | let randomData = PirTestUtils.generateRandomBytes(size: size) 30 | 31 | return HashBucket.HashBucketEntry(keywordHash: UInt64.random(in: UInt64.min...UInt64.max), value: randomData) 32 | } 33 | 34 | private func getTestBucket() -> HashBucket { 35 | let count = Int.random(in: 1...10) 36 | return HashBucket(slots: (0...self) 26 | try PirTestUtils.MulPirTests.evaluationKeyConfig(scheme: Bfv.self) 27 | } 28 | 29 | @Test(arguments: PirKeyCompressionStrategy.allCases) 30 | func queryGeneration(keyCompression: PirKeyCompressionStrategy) throws { 31 | try PirTestUtils.MulPirTests.queryGenerationTest(scheme: NoOpScheme.self, keyCompression) 32 | try PirTestUtils.MulPirTests.queryGenerationTest(scheme: Bfv.self, keyCompression) 33 | try PirTestUtils.MulPirTests.queryGenerationTest(scheme: Bfv.self, keyCompression) 34 | } 35 | 36 | @Test 37 | func computeCoordinates() throws { 38 | try PirTestUtils.MulPirTests.computeCoordinates(scheme: NoOpScheme.self) 39 | try PirTestUtils.MulPirTests.computeCoordinates(scheme: Bfv.self) 40 | try PirTestUtils.MulPirTests.computeCoordinates(scheme: Bfv.self) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Tests/PrivateInformationRetrievalTests/SymmetricPIRTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright 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 _CryptoExtras 16 | import _TestUtilities 17 | import Crypto 18 | import Foundation 19 | import HomomorphicEncryption 20 | @testable import PrivateInformationRetrieval 21 | import Testing 22 | 23 | @Suite 24 | struct SymmetricPirTests { 25 | @Test 26 | func oprfRoundtrip() throws { 27 | func roundTrip(keyword: [UInt8], server: OprfServer, 28 | client: OprfClient) throws -> (OprfQuery, OprfClient.ParsedOprfOutput) 29 | { 30 | // Client 31 | let queryContext = try client.queryContext(at: keyword) 32 | // Server 33 | let response = try server.computeResponse(query: queryContext.query) 34 | // Client 35 | let output = try client.parse(oprfResponse: response, with: queryContext) 36 | 37 | return (queryContext.query, output) 38 | } 39 | let config = try PirTestUtils.SymmetricPirTests.generateSymmetricPirConfig() 40 | let server = try OprfServer(symmetricPirConfig: config) 41 | let client = try OprfClient(symmetricPirClientConfig: config.clientConfig()) 42 | 43 | let keyword: [UInt8] = [1, 2, 3, 4, 5] 44 | let (query1, output1) = try roundTrip(keyword: keyword, server: server, client: client) 45 | let (query2, output2) = try roundTrip(keyword: keyword, server: server, client: client) 46 | 47 | #expect(query1.oprfRepresentation != query2.oprfRepresentation) 48 | #expect(output1 == output2) 49 | } 50 | 51 | @Test 52 | func database() throws { 53 | let shardCount = 1 54 | let rowCount = 10 55 | let valueSize = 3 56 | let testDatabase = PirTestUtils.randomKeywordPirDatabase(rowCount: rowCount, valueSize: valueSize) 57 | 58 | let config = try PirTestUtils.SymmetricPirTests.generateSymmetricPirConfig() 59 | let encryptedDatabase = try KeywordDatabase( 60 | rows: testDatabase, 61 | sharding: .shardCount(shardCount), 62 | symmetricPirConfig: config) 63 | 64 | let oprfSecretKey = try OprfPrivateKey(rawRepresentation: config.oprfSecretKey) 65 | 66 | let testIndex = Int.random(in: 0...self) 102 | try PirTestUtils.SymmetricPirTests.roundTrip(Bfv.self) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Tests/PrivateInformationRetrievalTests/UtilTests.swift: -------------------------------------------------------------------------------- 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 Foundation 16 | @testable import PrivateInformationRetrieval 17 | import Testing 18 | 19 | @Suite 20 | struct UtilTests { 21 | @Test 22 | func utf8OrBase64() throws { 23 | let utf8 = Array(Data("abc123".utf8)) 24 | #expect(utf8.utf8OrBase64() == "abc123 (utf8)") 25 | 26 | let nonUtf8: [UInt8] = [128] 27 | #expect(nonUtf8.utf8OrBase64() == Data(nonUtf8).base64EncodedString() + " (base64)") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Tests/PrivateNearestNeighborSearchTests/CiphertextMatrixTests.swift: -------------------------------------------------------------------------------- 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 _TestUtilities 16 | import HomomorphicEncryption 17 | import Testing 18 | 19 | @Suite 20 | struct CiphertextMatrixTests { 21 | @Test 22 | func encryptDecryptRoundTrip() throws { 23 | try PrivateNearestNeighborSearchUtil.CiphertextMatrixTests.encryptDecryptRoundTrip(for: NoOpScheme.self) 24 | try PrivateNearestNeighborSearchUtil.CiphertextMatrixTests.encryptDecryptRoundTrip(for: Bfv.self) 25 | try PrivateNearestNeighborSearchUtil.CiphertextMatrixTests.encryptDecryptRoundTrip(for: Bfv.self) 26 | } 27 | 28 | @Test 29 | func convertFormatRoundTrip() throws { 30 | try PrivateNearestNeighborSearchUtil.CiphertextMatrixTests.convertFormatRoundTrip(for: NoOpScheme.self) 31 | try PrivateNearestNeighborSearchUtil.CiphertextMatrixTests.convertFormatRoundTrip(for: Bfv.self) 32 | try PrivateNearestNeighborSearchUtil.CiphertextMatrixTests.convertFormatRoundTrip(for: Bfv.self) 33 | } 34 | 35 | @Test 36 | func extractDenseRow() throws { 37 | try PrivateNearestNeighborSearchUtil.CiphertextMatrixTests.extractDenseRow(for: NoOpScheme.self) 38 | try PrivateNearestNeighborSearchUtil.CiphertextMatrixTests.extractDenseRow(for: Bfv.self) 39 | try PrivateNearestNeighborSearchUtil.CiphertextMatrixTests.extractDenseRow(for: Bfv.self) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Tests/PrivateNearestNeighborSearchTests/ClientTests.swift: -------------------------------------------------------------------------------- 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 _TestUtilities 16 | import HomomorphicEncryption 17 | import Testing 18 | 19 | @Suite 20 | struct ClientTests { 21 | @Test 22 | func clientConfig() throws { 23 | try PrivateNearestNeighborSearchUtil.ClientTests.clientConfig(for: NoOpScheme.self) 24 | try PrivateNearestNeighborSearchUtil.ClientTests.clientConfig(for: Bfv.self) 25 | try PrivateNearestNeighborSearchUtil.ClientTests.clientConfig(for: Bfv.self) 26 | } 27 | 28 | @Test 29 | func normalizeRowsAndScale() throws { 30 | try PrivateNearestNeighborSearchUtil.ClientTests.normalizeRowsAndScale() 31 | } 32 | 33 | @Test 34 | func queryAsResponse() throws { 35 | try PrivateNearestNeighborSearchUtil.ClientTests.queryAsResponse(for: NoOpScheme.self) 36 | try PrivateNearestNeighborSearchUtil.ClientTests.queryAsResponse(for: Bfv.self) 37 | try PrivateNearestNeighborSearchUtil.ClientTests.queryAsResponse(for: Bfv.self) 38 | } 39 | 40 | @Test 41 | func clientServer() throws { 42 | try PrivateNearestNeighborSearchUtil.ClientTests.clientServer(for: Bfv.self) 43 | try PrivateNearestNeighborSearchUtil.ClientTests.clientServer(for: Bfv.self) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Tests/PrivateNearestNeighborSearchTests/CosineSimilarityTests.swift: -------------------------------------------------------------------------------- 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 _TestUtilities 16 | import Testing 17 | 18 | @Suite 19 | struct CosineSimilarityTests { 20 | @Test 21 | func normalizeRowsAndScale() throws { 22 | try PrivateNearestNeighborSearchUtil.CosineSimilarityTests.normalizeRowsAndScale() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Tests/PrivateNearestNeighborSearchTests/DatabaseTests.swift: -------------------------------------------------------------------------------- 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 _TestUtilities 16 | import HomomorphicEncryption 17 | import Testing 18 | 19 | @Suite 20 | struct DatabaseTests { 21 | @Test 22 | func serializedProcessedDatabase() throws { 23 | try PrivateNearestNeighborSearchUtil.DatabaseTests.serializedProcessedDatabase(for: NoOpScheme.self) 24 | try PrivateNearestNeighborSearchUtil.DatabaseTests.serializedProcessedDatabase(for: Bfv.self) 25 | try PrivateNearestNeighborSearchUtil.DatabaseTests.serializedProcessedDatabase(for: Bfv.self) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Tests/PrivateNearestNeighborSearchTests/MatrixMultiplicationTests.swift: -------------------------------------------------------------------------------- 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 _TestUtilities 16 | import HomomorphicEncryption 17 | import Testing 18 | 19 | @Suite 20 | struct MatrixMultiplicationTests { 21 | @Test 22 | func mulVector() throws { 23 | try PrivateNearestNeighborSearchUtil.MatrixMultiplicationTests.mulVector(for: Bfv.self) 24 | try PrivateNearestNeighborSearchUtil.MatrixMultiplicationTests.mulVector(for: Bfv.self) 25 | } 26 | 27 | @Test 28 | func matrixMulSmallDimensions() throws { 29 | try PrivateNearestNeighborSearchUtil.MatrixMultiplicationTests.matrixMulSmallDimensions(for: Bfv.self) 30 | try PrivateNearestNeighborSearchUtil.MatrixMultiplicationTests.matrixMulSmallDimensions(for: Bfv.self) 31 | } 32 | 33 | @Test 34 | func matrixMulLargeDimensions() throws { 35 | try PrivateNearestNeighborSearchUtil.MatrixMultiplicationTests.matrixMulLargeDimensions(for: Bfv.self) 36 | try PrivateNearestNeighborSearchUtil.MatrixMultiplicationTests.matrixMulLargeDimensions(for: Bfv.self) 37 | } 38 | 39 | @Test 40 | func evaluationKeyContainment() throws { 41 | try PrivateNearestNeighborSearchUtil.MatrixMultiplicationTests.evaluationKeyContainment(for: NoOpScheme.self) 42 | try PrivateNearestNeighborSearchUtil.MatrixMultiplicationTests.evaluationKeyContainment(for: Bfv.self) 43 | try PrivateNearestNeighborSearchUtil.MatrixMultiplicationTests.evaluationKeyContainment(for: Bfv.self) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Tests/PrivateNearestNeighborSearchTests/PlaintextMatrixTests.swift: -------------------------------------------------------------------------------- 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 _TestUtilities 16 | import HomomorphicEncryption 17 | import Testing 18 | 19 | @Suite 20 | struct PlaintextMatrixTests { 21 | @Test 22 | func matrixDimensions() throws { 23 | try PrivateNearestNeighborSearchUtil.PlaintextMatrixTests.matrixDimensions() 24 | } 25 | 26 | @Test 27 | func plaintextMatrixError() throws { 28 | try PrivateNearestNeighborSearchUtil.PlaintextMatrixTests.plaintextMatrixError(for: Bfv.self) 29 | try PrivateNearestNeighborSearchUtil.PlaintextMatrixTests.plaintextMatrixError(for: Bfv.self) 30 | try PrivateNearestNeighborSearchUtil.PlaintextMatrixTests.plaintextMatrixError(for: NoOpScheme.self) 31 | } 32 | 33 | @Test 34 | func plaintextMatrixDenseRowError() throws { 35 | try PrivateNearestNeighborSearchUtil.PlaintextMatrixTests.plaintextMatrixDenseRowError(for: Bfv.self) 36 | try PrivateNearestNeighborSearchUtil.PlaintextMatrixTests.plaintextMatrixDenseRowError(for: Bfv.self) 37 | try PrivateNearestNeighborSearchUtil.PlaintextMatrixTests.plaintextMatrixDenseRowError(for: NoOpScheme.self) 38 | } 39 | 40 | @Test 41 | func plaintextMatrixDenseColumn() throws { 42 | try PrivateNearestNeighborSearchUtil.PlaintextMatrixTests.plaintextMatrixDenseColumn(for: Bfv.self) 43 | try PrivateNearestNeighborSearchUtil.PlaintextMatrixTests.plaintextMatrixDenseColumn(for: Bfv.self) 44 | try PrivateNearestNeighborSearchUtil.PlaintextMatrixTests.plaintextMatrixDenseColumn(for: NoOpScheme.self) 45 | } 46 | 47 | @Test 48 | func plaintextMatrixDenseRow() throws { 49 | try PrivateNearestNeighborSearchUtil.PlaintextMatrixTests.plaintextMatrixDenseRow(for: Bfv.self) 50 | try PrivateNearestNeighborSearchUtil.PlaintextMatrixTests.plaintextMatrixDenseRow(for: Bfv.self) 51 | try PrivateNearestNeighborSearchUtil.PlaintextMatrixTests.plaintextMatrixDenseRow(for: NoOpScheme.self) 52 | } 53 | 54 | @Test 55 | func plaintextMatrixDiagonal() throws { 56 | try PrivateNearestNeighborSearchUtil.PlaintextMatrixTests.plaintextMatrixDiagonal(for: Bfv.self) 57 | try PrivateNearestNeighborSearchUtil.PlaintextMatrixTests.plaintextMatrixDiagonal(for: Bfv.self) 58 | try PrivateNearestNeighborSearchUtil.PlaintextMatrixTests.plaintextMatrixDiagonal(for: NoOpScheme.self) 59 | } 60 | 61 | @Test 62 | func diagonalRotation() throws { 63 | try PrivateNearestNeighborSearchUtil.PlaintextMatrixTests.diagonalRotation(for: Bfv.self) 64 | try PrivateNearestNeighborSearchUtil.PlaintextMatrixTests.diagonalRotation(for: Bfv.self) 65 | try PrivateNearestNeighborSearchUtil.PlaintextMatrixTests.diagonalRotation(for: NoOpScheme.self) 66 | } 67 | 68 | @Test 69 | func plaintextMatrixConversion() throws { 70 | try PrivateNearestNeighborSearchUtil.PlaintextMatrixTests.plaintextMatrixConversion(for: Bfv.self) 71 | try PrivateNearestNeighborSearchUtil.PlaintextMatrixTests.plaintextMatrixConversion(for: Bfv.self) 72 | try PrivateNearestNeighborSearchUtil.PlaintextMatrixTests.plaintextMatrixConversion(for: NoOpScheme.self) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Tests/PrivateNearestNeighborSearchTests/UtilsTests.swift: -------------------------------------------------------------------------------- 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 _TestUtilities 16 | import HomomorphicEncryption 17 | @testable import PrivateNearestNeighborSearch 18 | import Testing 19 | 20 | @Suite 21 | struct UtilsTests { 22 | @Test 23 | func matrixMultiplication() throws { 24 | // Int64 25 | do { 26 | let x = Array2d(data: Array(-3..<3), rowCount: 2, columnCount: 3) 27 | let y = Array2d(data: Array(-6..<6), rowCount: 3, columnCount: 4) 28 | #expect(x.mul(y, modulus: 100) == Array2d(data: [[20, 14, 8, 2], [2, 5, 8, 11]])) 29 | // Values in [-floor(modulus/2), floor(modulus-1)/2] 30 | #expect(x.mul(y, modulus: 10) == Array2d(data: [[0, 4, -2, 2], [2, -5, -2, 1]])) 31 | } 32 | // Float 33 | do { 34 | let x = Array2d(data: Array(-3..<3).map { Float($0) }, rowCount: 2, columnCount: 3) 35 | let y = Array2d(data: Array(-6..<6).map { Float($0) }, rowCount: 3, columnCount: 4) 36 | #expect(x.mul(y) == Array2d(data: [[20.0, 14.0, 8.0, 2.0], [2.0, 5.0, 8.0, 11.0]])) 37 | } 38 | } 39 | 40 | @Test 41 | func fixedPointCosineSimilarity() throws { 42 | let innerDimension = 3 43 | let x = Array2d(data: Array(-3..<3).map { Float($0) }, rowCount: 2, columnCount: innerDimension) 44 | let y = Array2d(data: Array(-6..<6).map { Float($0) }, rowCount: innerDimension, columnCount: 4) 45 | 46 | let norm = Array2d.Norm.Lp(p: 2.0) 47 | let xNormalized = x.normalizedRows(norm: norm) 48 | let yNormalized = y.transposed().normalizedRows(norm: norm).transposed() 49 | let expected = xNormalized.mul(yNormalized) 50 | 51 | let scalingFactor = 100 52 | let modulus = UInt32(scalingFactor * scalingFactor * innerDimension + 1) 53 | let z = try x.fixedPointCosineSimilarity(y, modulus: modulus, scalingFactor: Float(scalingFactor)) 54 | 55 | #expect(fixedPointCosineSimilarityError(innerDimension: 3, scalingFactor: 100).isClose(to: 0.010025)) 56 | let absoluteError = fixedPointCosineSimilarityError( 57 | innerDimension: innerDimension, 58 | scalingFactor: scalingFactor) 59 | for (got, expected) in zip(z.data, expected.data) { 60 | #expect(got.isClose(to: expected, absoluteTolerance: absoluteError)) 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Utilities/generate-protobuf-files.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ## Copyright 2024-2025 Apple Inc. and the Swift Homomorphic Encryption project authors 3 | ## 4 | ## Licensed under the Apache License, Version 2.0 (the "License"); 5 | ## you may not use this file except in compliance with the License. 6 | ## You may obtain a copy of the License at 7 | ## 8 | ## http://www.apache.org/licenses/LICENSE-2.0 9 | ## 10 | ## Unless required by applicable law or agreed to in writing, software 11 | ## distributed under the License is distributed on an "AS IS" BASIS, 12 | ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | ## See the License for the specific language governing permissions and 14 | ## limitations under the License. 15 | 16 | # Updates swift protobuf files 17 | 18 | set -e 19 | 20 | echo "Updating protobuf files" 21 | cd swift-homomorphic-encryption-protobuf 22 | 23 | echo "Removing HomomorphicEncryptionProtobuf swift protobuf files" 24 | rm ../Sources/HomomorphicEncryptionProtobuf/generated/*.pb.swift 25 | echo "Regenerating HomomorphicEncryptionProtobuf swift protobuf files" 26 | find apple/swift_homomorphic_encryption/v1/ -name "*.proto" -exec protoc --swift_opt=Visibility=Public --swift_opt=FileNaming=PathToUnderscores --swift_out ../Sources/HomomorphicEncryptionProtobuf/generated {} \; 27 | 28 | echo "Removing PrivateInformationRetrievalProtobuf swift protobuf files" 29 | rm ../Sources/PrivateInformationRetrievalProtobuf/generated/*.pb.swift 30 | echo "Regenerating PrivateInformationRetrievalProtobuf swift protobuf files" 31 | find apple/swift_homomorphic_encryption/pir/ \ 32 | apple/swift_homomorphic_encryption/api/shared/v1/ \ 33 | apple/swift_homomorphic_encryption/api/pir/v1/ \ 34 | -name "*.proto" -exec protoc \ 35 | --swift_opt=ProtoPathModuleMappings=../Sources/PrivateInformationRetrievalProtobuf/protobuf_module_mappings.txtpb \ 36 | --swift_opt=Visibility=Public \ 37 | --swift_opt=FileNaming=PathToUnderscores \ 38 | --swift_out ../Sources/PrivateInformationRetrievalProtobuf/generated {} \; 39 | 40 | echo "Removing PrivateNearestNeighborSearchProtobuf swift protobuf files" 41 | rm ../Sources/PrivateNearestNeighborSearchProtobuf/generated/*.pb.swift 42 | echo "Regenerating PrivateNearestNeighborSearchProtobuf swift protobuf files" 43 | find apple/swift_homomorphic_encryption/pnns/ \ 44 | apple/swift_homomorphic_encryption/api/shared/v1/ \ 45 | apple/swift_homomorphic_encryption/api/pnns/v1/ \ 46 | -name "*.proto" -exec protoc \ 47 | --swift_opt=ProtoPathModuleMappings=../Sources/PrivateNearestNeighborSearchProtobuf/protobuf_module_mappings.txtpb \ 48 | --swift_opt=Visibility=Public \ 49 | --swift_opt=FileNaming=PathToUnderscores \ 50 | --swift_out ../Sources/PrivateNearestNeighborSearchProtobuf/generated {} \; 51 | 52 | cd - 53 | echo "Done updating protobuf files" 54 | -------------------------------------------------------------------------------- /ci/install-lockwood-swiftformat.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ## Copyright 2024-2025 Apple Inc. and the Swift Homomorphic Encryption project authors 3 | ## 4 | ## Licensed under the Apache License, Version 2.0 (the "License"); 5 | ## you may not use this file except in compliance with the License. 6 | ## You may obtain a copy of the License at 7 | ## 8 | ## http://www.apache.org/licenses/LICENSE-2.0 9 | ## 10 | ## Unless required by applicable law or agreed to in writing, software 11 | ## distributed under the License is distributed on an "AS IS" BASIS, 12 | ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | ## See the License for the specific language governing permissions and 14 | ## limitations under the License. 15 | 16 | set -e 17 | 18 | echo "installing Lockwood swiftformat" 19 | DIR=$PWD 20 | mkdir -p /tmp/swiftformat 21 | cd /tmp/swiftformat || exit 1 22 | git clone --depth 1 --branch "$SWIFTFORMAT_VERSION" https://github.com/nicklockwood/SwiftFormat 23 | cd SwiftFormat || exit 1 24 | swift build -c release 25 | export PATH=$PATH:$PWD/.build/release/ 26 | cd "$DIR" || exit 1 27 | which swiftformat 28 | -------------------------------------------------------------------------------- /ci/install-swiftlint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ## Copyright 2024-2025 Apple Inc. and the Swift Homomorphic Encryption project authors 3 | ## 4 | ## Licensed under the Apache License, Version 2.0 (the "License"); 5 | ## you may not use this file except in compliance with the License. 6 | ## You may obtain a copy of the License at 7 | ## 8 | ## http://www.apache.org/licenses/LICENSE-2.0 9 | ## 10 | ## Unless required by applicable law or agreed to in writing, software 11 | ## distributed under the License is distributed on an "AS IS" BASIS, 12 | ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | ## See the License for the specific language governing permissions and 14 | ## limitations under the License. 15 | 16 | set -e 17 | 18 | echo "installing swiftlint" 19 | DIR=$PWD 20 | mkdir -p /tmp/swiftlint 21 | cd /tmp/swiftlint || exit 1 22 | git clone --depth 1 --branch "$SWIFTLINT_VERSION" https://github.com/realm/SwiftLint 23 | cd SwiftLint || exit 24 | swift build -c release 25 | export PATH=$PATH:$PWD/.build/release/ 26 | cd "$DIR" || exit 1 27 | which swiftlint 28 | -------------------------------------------------------------------------------- /ci/run-apple-swift-format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ## Copyright 2024-2025 Apple Inc. and the Swift Homomorphic Encryption project authors 3 | ## 4 | ## Licensed under the Apache License, Version 2.0 (the "License"); 5 | ## you may not use this file except in compliance with the License. 6 | ## You may obtain a copy of the License at 7 | ## 8 | ## http://www.apache.org/licenses/LICENSE-2.0 9 | ## 10 | ## Unless required by applicable law or agreed to in writing, software 11 | ## distributed under the License is distributed on an "AS IS" BASIS, 12 | ## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | ## See the License for the specific language governing permissions and 14 | ## limitations under the License. 15 | 16 | set -e 17 | 18 | lint_folders="\ 19 | Benchmarks \ 20 | Snippets \ 21 | Sources \ 22 | Tests \ 23 | " 24 | # There's no way to disable whitespace lints from swift-format, so we manually filter the output 25 | # https://github.com/swiftlang/swift-format/issues/764 26 | # shellcheck disable=SC1035 27 | !(swift-format lint --recursive "$lint_folders" 2>&1 | grep Documentation) 28 | -------------------------------------------------------------------------------- /copyright-header.txt: -------------------------------------------------------------------------------- 1 | Copyright 2024 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 | --------------------------------------------------------------------------------