From 3ddff97046eb8ca04cdae3a198f78bcb9287e42f Mon Sep 17 00:00:00 2001 From: Michael Schlottke-Lakemper Date: Wed, 3 Jan 2024 16:47:57 +0100 Subject: [PATCH 01/13] Initial infrastructure & development --- .github/dependabot.yml | 7 +++ .github/workflows/CompatHelper.yml | 45 ++++++++++++++++++ .github/workflows/Documenter.yml | 41 +++++++++++++++++ .github/workflows/SpellCheck.yml | 13 ++++++ .github/workflows/TagBot.yml | 33 ++++++++++++++ .github/workflows/ci.yml | 62 +++++++++++++++++++++++++ .gitignore | 2 + docs/Project.toml | 5 ++ docs/make.jl | 73 ++++++++++++++++++++++++++++++ docs/src/.gitignore | 2 + docs/src/reference.md | 10 ++++ test/Project.toml | 5 ++ test/runtests.jl | 6 +++ test/test_ckks.jl | 10 ++++ 14 files changed, 314 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/CompatHelper.yml create mode 100644 .github/workflows/Documenter.yml create mode 100644 .github/workflows/SpellCheck.yml create mode 100644 .github/workflows/TagBot.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 docs/Project.toml create mode 100644 docs/make.jl create mode 100644 docs/src/.gitignore create mode 100644 docs/src/reference.md create mode 100644 test/Project.toml create mode 100644 test/runtests.jl create mode 100644 test/test_ckks.jl diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..d60f070 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" # Location of package manifests + schedule: + interval: "monthly" diff --git a/.github/workflows/CompatHelper.yml b/.github/workflows/CompatHelper.yml new file mode 100644 index 0000000..98e1e3b --- /dev/null +++ b/.github/workflows/CompatHelper.yml @@ -0,0 +1,45 @@ +name: CompatHelper +on: + schedule: + - cron: 0 0 * * * + workflow_dispatch: +permissions: + contents: write + pull-requests: write +jobs: + CompatHelper: + runs-on: ubuntu-latest + steps: + - name: Check if Julia is already available in the PATH + id: julia_in_path + run: which julia + continue-on-error: true + - name: Install Julia, but only if it is not already available in the PATH + uses: julia-actions/setup-julia@v1 + with: + version: '1' + arch: ${{ runner.arch }} + if: steps.julia_in_path.outcome != 'success' + - name: "Add the General registry via Git" + run: | + import Pkg + ENV["JULIA_PKG_SERVER"] = "" + Pkg.Registry.add("General") + shell: julia --color=yes {0} + - name: "Install CompatHelper" + run: | + import Pkg + name = "CompatHelper" + uuid = "aa819f21-2bde-4658-8897-bab36330d9b7" + version = "3" + Pkg.add(; name, uuid, version) + shell: julia --color=yes {0} + - name: "Run CompatHelper" + run: | + import CompatHelper + CompatHelper.main(; subdirs=["", "test", "docs"]) + shell: julia --color=yes {0} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }} + # COMPATHELPER_PRIV: ${{ secrets.COMPATHELPER_PRIV }} diff --git a/.github/workflows/Documenter.yml b/.github/workflows/Documenter.yml new file mode 100644 index 0000000..3cfbcdb --- /dev/null +++ b/.github/workflows/Documenter.yml @@ -0,0 +1,41 @@ +name: Documentation + +on: + push: + branches: + - 'main' + tags: '*' + paths-ignore: + - '.github/workflows/ci.yml' + - '.github/workflows/CompatHelper.yml' + - '.github/workflows/TagBot.yml' + pull_request: + paths-ignore: + - '.github/workflows/ci.yml' + - '.github/workflows/CompatHelper.yml' + - '.github/workflows/TagBot.yml' + workflow_dispatch: + +concurrency: + # Skip intermediate builds: always. + # Cancel intermediate builds: only if it is a pull request build. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} + +jobs: + build-docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v1 + with: + version: '1' + show-versioninfo: true + - uses: julia-actions/julia-buildpkg@v1 + - name: Install dependencies + run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' + - name: Build and deploy + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} # For authentication with SSH deploy key + run: julia --project=docs --color=yes docs/make.jl diff --git a/.github/workflows/SpellCheck.yml b/.github/workflows/SpellCheck.yml new file mode 100644 index 0000000..a780e97 --- /dev/null +++ b/.github/workflows/SpellCheck.yml @@ -0,0 +1,13 @@ +name: Spell Check + +on: [pull_request, workflow_dispatch] + +jobs: + typos-check: + name: Spell Check with Typos + runs-on: ubuntu-latest + steps: + - name: Checkout Actions Repository + uses: actions/checkout@v4 + - name: Check spelling + uses: crate-ci/typos@v1.16.26 diff --git a/.github/workflows/TagBot.yml b/.github/workflows/TagBot.yml new file mode 100644 index 0000000..90dc100 --- /dev/null +++ b/.github/workflows/TagBot.yml @@ -0,0 +1,33 @@ +name: TagBot +on: + issue_comment: + types: + - created + workflow_dispatch: + inputs: + lookback: + default: 3 +permissions: + actions: read + checks: read + contents: write + deployments: read + issues: read + discussions: read + packages: read + pages: read + pull-requests: read + repository-projects: read + security-events: read + statuses: read +jobs: + TagBot: + if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' + runs-on: ubuntu-latest + steps: + - uses: JuliaRegistries/TagBot@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + # Edit the following line to reflect the actual name of the GitHub Secret containing your private key + ssh: ${{ secrets.DOCUMENTER_KEY }} + # ssh: ${{ secrets.NAME_OF_MY_SSH_PRIVATE_KEY_SECRET }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..2ed7482 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,62 @@ +name: CI + +on: + push: + branches: + - main + tags: ['*'] + paths-ignore: + - 'LICENSE.md' + - 'README.md' + - '.zenodo.json' + - '.github/workflows/CompatHelper.yml' + - '.github/workflows/TagBot.yml' + - 'docs/**' + pull_request: + paths-ignore: + - 'LICENSE.md' + - 'README.md' + - '.zenodo.json' + - '.github/workflows/CompatHelper.yml' + - '.github/workflows/TagBot.yml' + - 'docs/**' + workflow_dispatch: + +concurrency: + # Skip intermediate builds: always. + # Cancel intermediate builds: only if it is a pull request build. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} + +jobs: + test: + name: ${{ matrix.os }} - Julia ${{ matrix.version }} - ${{ github.event_name }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + version: + - '1.8' + - '1.9' + - '1.10' + os: + - ubuntu-latest + - macOS-latest + - windows-latest + steps: + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v1 + with: + version: ${{ matrix.version }} + - run: julia -e 'using InteractiveUtils; versioninfo(verbose=true)' + - uses: julia-actions/cache@v1 + - uses: julia-actions/julia-buildpkg@v1 + - uses: julia-actions/julia-runtest@v1 + - uses: julia-actions/julia-processcoverage@v1 + - uses: codecov/codecov-action@v3 + with: + files: lcov.info + - uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + path-to-lcov: ./lcov.info diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..359e63b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +Manifest.toml +run/ diff --git a/docs/Project.toml b/docs/Project.toml new file mode 100644 index 0000000..1814eb3 --- /dev/null +++ b/docs/Project.toml @@ -0,0 +1,5 @@ +[deps] +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" + +[compat] +Documenter = "1" diff --git a/docs/make.jl b/docs/make.jl new file mode 100644 index 0000000..66e09b3 --- /dev/null +++ b/docs/make.jl @@ -0,0 +1,73 @@ +using Documenter + +# Get SecureArithmetic.jl root directory +securearithmetic_root_dir = dirname(@__DIR__) + +# Fix for https://github.com/trixi-framework/Trixi.jl/issues/668 +if (get(ENV, "CI", nothing) != "true") && (get(ENV, "SECUREARITHMETIC_DOC_DEFAULT_ENVIRONMENT", nothing) != "true") + push!(LOAD_PATH, securearithmetic_root_dir) +end + +using SecureArithmetic + +# Define module-wide setups such that the respective modules are available in doctests +DocMeta.setdocmeta!(SecureArithmetic, :DocTestSetup, :(using SecureArithmetic); recursive=true) + +# Copy some files from the top level directory to the docs and modify them +# as necessary +open(joinpath(@__DIR__, "src", "index.md"), "w") do io + # Point to source file + println(io, """ + ```@meta + EditURL = "https://github.com/sloede/SecureArithmetic.jl/blob/main/README.md" + ``` + """) + # Write the modified contents + for line in eachline(joinpath(securearithmetic_root_dir, "README.md")) + line = replace(line, "[LICENSE.md](LICENSE.md)" => "[License](@ref)") + println(io, line) + end +end + +open(joinpath(@__DIR__, "src", "license.md"), "w") do io + # Point to source file + println(io, """ + ```@meta + EditURL = "https://github.com/sloede/SecureArithmetic/blob/main/LICENSE.md" + ``` + """) + # Write the modified contents + println(io, "# License") + println(io, "") + for line in eachline(joinpath(securearithmetic_root_dir, "LICENSE.md")) + println(io, "> ", line) + end +end + +# Make documentation +makedocs( + # Specify modules for which docstrings should be shown + modules = [SecureArithmetic], + # Set sitename to Trixi.jl + sitename="SecureArithmetic.jl", + # Provide additional formatting options + format = Documenter.HTML( + # Disable pretty URLs during manual testing + prettyurls = get(ENV, "CI", nothing) == "true", + # Set canonical URL to GitHub pages URL + canonical = "https://securearithmetic-jl.lakemper.eu/stable" + ), + # Explicitly specify documentation structure + pages = [ + "Home" => "index.md", + "API Reference" => "reference.md", + "License" => "license.md" + ], +) + + +deploydocs(; + repo = "github.com/sloede/SecureArithmetic.jl", + devbranch = "main", + push_preview = true +) diff --git a/docs/src/.gitignore b/docs/src/.gitignore new file mode 100644 index 0000000..312f831 --- /dev/null +++ b/docs/src/.gitignore @@ -0,0 +1,2 @@ +index.md +license.md diff --git a/docs/src/reference.md b/docs/src/reference.md new file mode 100644 index 0000000..9bab422 --- /dev/null +++ b/docs/src/reference.md @@ -0,0 +1,10 @@ +# API reference + +```@meta +CurrentModule = SecureArithmetic +``` + +```@autodocs +Modules = [SecureArithmetic] +Private = false +``` diff --git a/test/Project.toml b/test/Project.toml new file mode 100644 index 0000000..ec84819 --- /dev/null +++ b/test/Project.toml @@ -0,0 +1,5 @@ +[deps] +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[compat] +Test = "1" diff --git a/test/runtests.jl b/test/runtests.jl new file mode 100644 index 0000000..2e6a467 --- /dev/null +++ b/test/runtests.jl @@ -0,0 +1,6 @@ +using Test + +@time @testset verbose=true showtiming=true "SecureArithmetic.jl tests" begin + include("test_ckks.jl") +end + diff --git a/test/test_ckks.jl b/test/test_ckks.jl new file mode 100644 index 0000000..bffdbdf --- /dev/null +++ b/test/test_ckks.jl @@ -0,0 +1,10 @@ +module TestCKKS + +using Test +using SecureArithmetic + +@testset verbose=true showtiming=true "dummy" begin + @test_nowarn greet() +end + +end # module From 9a4a6046c2d0beb9b5fdf1abcd3399a13763c0a6 Mon Sep 17 00:00:00 2001 From: Michael Schlottke-Lakemper Date: Wed, 3 Jan 2024 16:49:53 +0100 Subject: [PATCH 02/13] Add compat bound for Julia --- Project.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Project.toml b/Project.toml index 6c5df7b..1b91427 100644 --- a/Project.toml +++ b/Project.toml @@ -2,3 +2,6 @@ name = "SecureArithmetic" uuid = "38cee09b-6d7e-4d21-866e-807a7f642fe9" authors = ["Michael Schlottke-Lakemper "] version = "0.1.0" + +[compat] +julia = "1.8" From 7b9ecc1d8c6dae529121e473e1cf0c16aa5afb7e Mon Sep 17 00:00:00 2001 From: Michael Schlottke-Lakemper Date: Wed, 3 Jan 2024 17:08:09 +0100 Subject: [PATCH 03/13] Add OpenFHE as dependency --- Project.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Project.toml b/Project.toml index 1b91427..afacf78 100644 --- a/Project.toml +++ b/Project.toml @@ -3,5 +3,8 @@ uuid = "38cee09b-6d7e-4d21-866e-807a7f642fe9" authors = ["Michael Schlottke-Lakemper "] version = "0.1.0" +[deps] +OpenFHE = "77ce9b8e-ecf5-45d1-bd8a-d31f384f2f95" + [compat] julia = "1.8" From c14771730745dceb65e35c7b4496d1ff64ee10ba Mon Sep 17 00:00:00 2001 From: Michael Schlottke-Lakemper Date: Wed, 3 Jan 2024 17:20:17 +0100 Subject: [PATCH 04/13] Make dummy test work --- test/test_ckks.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_ckks.jl b/test/test_ckks.jl index bffdbdf..159a4aa 100644 --- a/test/test_ckks.jl +++ b/test/test_ckks.jl @@ -4,7 +4,7 @@ using Test using SecureArithmetic @testset verbose=true showtiming=true "dummy" begin - @test_nowarn greet() + @test_nowarn SecureArithmetic.greet() end end # module From 1cfb66f836553ad5169fae415bad4ff336f0b749 Mon Sep 17 00:00:00 2001 From: Michael Schlottke-Lakemper Date: Thu, 18 Jan 2024 09:28:56 +0100 Subject: [PATCH 05/13] Initial implementation of the OpenFHE and unencrypted backends --- examples/simple_real_numbers.jl | 101 ++++++++++++++++++++++++++ src/SecureArithmetic.jl | 16 ++++- src/arithmetic.jl | 15 ++++ src/openfhe.jl | 124 ++++++++++++++++++++++++++++++++ src/types.jl | 43 +++++++++++ src/unencrypted.jl | 58 +++++++++++++++ 6 files changed, 356 insertions(+), 1 deletion(-) create mode 100644 examples/simple_real_numbers.jl create mode 100644 src/arithmetic.jl create mode 100644 src/openfhe.jl create mode 100644 src/types.jl create mode 100644 src/unencrypted.jl diff --git a/examples/simple_real_numbers.jl b/examples/simple_real_numbers.jl new file mode 100644 index 0000000..ba37a1b --- /dev/null +++ b/examples/simple_real_numbers.jl @@ -0,0 +1,101 @@ +using SecureArithmetic +using OpenFHE + +function simple_real_numbers(context) + public_key, private_key = generate_keys(context) + + init_multiplication(context, private_key) + init_rotation(context, private_key, [1, -2]) + + + x1 = [0.25, 0.5, 0.75, 1.0, 2.0, 3.0, 4.0, 5.0] + x2 = [5.0, 4.0, 3.0, 2.0, 1.0, 0.75, 0.5, 0.25] + + pv1 = PlainVector(context, x1) + pv2 = PlainVector(context, x2) + + println("Input x1: ", pv1) + println("Input x2: ", pv2) + + sv1 = encrypt(context, public_key, pv1) + sv2 = encrypt(context, public_key, pv2) + + sv_add = sv1 + sv2 + + sv_sub = sv1 - sv2 + + sv_scalar = sv1 * 4.0 + + sv_mult = sv1 * sv2 + + sv_shift1 = circshift(sv1, -1) + sv_shift2 = circshift(sv1, 2) + + + println() + println("Results of homomorphic computations: ") + + result_sv1 = decrypt(context, private_key, sv1) + println("x1 = ", result_sv1) + + result_sv_add = decrypt(context, private_key, sv_add) + println("x1 + x2 = ", result_sv_add) + + result_sv_sub = decrypt(context, private_key, sv_sub) + println("x1 - x2 = ", result_sv_sub) + + result_sv_scalar = decrypt(context, private_key, sv_scalar) + println("4 * x1 = ", result_sv_scalar) + + result_sv_mult = decrypt(context, private_key, sv_mult) + println("x1 * x2 = ", result_sv_mult) + + result_sv_shift1 = decrypt(context, private_key, sv_shift1) + println("x1 shifted circularly by -1 = ", result_sv_shift1) + + result_sv_shift2 = decrypt(context, private_key, sv_shift2) + println("x1 shifted circularly by 2 = ", result_sv_shift2) +end + + +################################################################################ +println("="^80) +println("Creating OpenFHE context...") + +multiplicative_depth = 1 +scaling_modulus = 50 +batch_size = 8 + +parameters = CCParams{CryptoContextCKKSRNS}() +SetMultiplicativeDepth(parameters, multiplicative_depth) +SetScalingModSize(parameters, scaling_modulus) +SetBatchSize(parameters, batch_size) + +cc = GenCryptoContext(parameters) +Enable(cc, PKE) +Enable(cc, KEYSWITCH) +Enable(cc, LEVELEDSHE) +println("CKKS scheme is using ring dimension ", GetRingDimension(cc)) +println() + +context_openfhe = SecureContext(OpenFHEBackend(cc)) + + +################################################################################ +println("="^80) +println("Creating unencrypted context...") + +context_unencrypted = SecureContext(Unencrypted()) + + +################################################################################ +println("="^80) +println("simple_real_numbers with an OpenFHE context") +simple_real_numbers(context_openfhe) + + +################################################################################ +println("="^80) +println("simple_real_numbers with an Unencrypted context") +simple_real_numbers(context_unencrypted) + diff --git a/src/SecureArithmetic.jl b/src/SecureArithmetic.jl index 54ff8fc..dc8ca8c 100644 --- a/src/SecureArithmetic.jl +++ b/src/SecureArithmetic.jl @@ -1,5 +1,19 @@ module SecureArithmetic -greet() = print("Hello World!") +using OpenFHE: OpenFHE + +# Basic types +export SecureContext, SecureVector, PlainVector + +# Backends +export Unencrypted, OpenFHEBackend + +# Crypto operations +export generate_keys, init_multiplication, init_rotation, encrypt, decrypt + +include("types.jl") +include("openfhe.jl") +include("unencrypted.jl") +include("arithmetic.jl") end # module SecureArithmetic diff --git a/src/arithmetic.jl b/src/arithmetic.jl new file mode 100644 index 0000000..0662f4c --- /dev/null +++ b/src/arithmetic.jl @@ -0,0 +1,15 @@ +Base.:+(sv1::SecureVector, sv2::SecureVector) = add(sv1, sv2) +Base.:+(sv::SecureVector, pv::PlainVector) = add(sv, pv) +Base.:+(pv::PlainVector, sv::SecureVector) = add(sv, pv) + +Base.:-(sv1::SecureVector, sv2::SecureVector) = subtract(sv1, sv2) +Base.:-(sv::SecureVector, pv::PlainVector) = subtract(sv, pv) +Base.:-(pv::PlainVector, sv::SecureVector) = subtract(pv, sv) + +Base.:*(sv1::SecureVector, sv2::SecureVector) = multiply(sv1, sv2) +Base.:*(sv::SecureVector, pv::PlainVector) = multiply(sv, pv) +Base.:*(pv::PlainVector, sv::SecureVector) = multiply(sv, pv) +Base.:*(sv::SecureVector, scalar::Real) = multiply(sv, scalar) +Base.:*(scalar::Real, sv::SecureVector) = multiply(sv, scalar) + +Base.circshift(sv::SecureVector, shift::Integer) = rotate(sv, shift) diff --git a/src/openfhe.jl b/src/openfhe.jl new file mode 100644 index 0000000..ed09a09 --- /dev/null +++ b/src/openfhe.jl @@ -0,0 +1,124 @@ +struct OpenFHEBackend{CryptoContextT} <: AbstractCryptoBackend + crypto_context::CryptoContextT +end + +function get_crypto_context(context::SecureContext{<:OpenFHEBackend}) + context.backend.crypto_context +end +function get_crypto_context(secure_vector::SecureVector{<:OpenFHEBackend}) + get_crypto_context(secure_vector.context) +end +function get_crypto_context(plain_vector::PlainVector{<:OpenFHEBackend}) + get_crypto_context(plain_vector.context) +end + +function generate_keys(context::SecureContext{<:OpenFHEBackend}) + cc = get_crypto_context(context) + keys = OpenFHE.KeyGen(cc) + public_key = PublicKey(context, OpenFHE.public_key(keys)) + private_key = PrivateKey(context, OpenFHE.private_key(keys)) + + public_key, private_key +end + +function init_multiplication(context::SecureContext{<:OpenFHEBackend}, private_key) + cc = get_crypto_context(context) + OpenFHE.EvalMultKeyGen(cc, private_key.private_key) + + nothing +end + +function init_rotation(context::SecureContext{<:OpenFHEBackend}, private_key, shifts) + cc = get_crypto_context(context) + OpenFHE.EvalRotateKeyGen(cc, private_key.private_key, shifts) + + nothing +end + +function PlainVector(context::SecureContext{<:OpenFHEBackend}, data::Vector{<:Real}) + cc = get_crypto_context(context) + plaintext = OpenFHE.MakeCKKSPackedPlaintext(cc, data) + plain_vector = PlainVector(plaintext, context) + + plain_vector +end + +function encrypt(context::SecureContext{<:OpenFHEBackend}, public_key, data::Vector{<:Real}) + plain_vector = PlainVector(context, data) + secure_vector = encrypt(context, public_key, plain_vector) + + secure_vector +end + +function encrypt(context::SecureContext{<:OpenFHEBackend}, public_key, + plain_vector::PlainVector) + cc = get_crypto_context(context) + ciphertext = OpenFHE.Encrypt(cc, public_key.public_key, plain_vector.plaintext) + secure_vector = SecureVector(ciphertext, context) + + secure_vector +end + +function decrypt!(plain_vector, context::SecureContext{<:OpenFHEBackend}, private_key, + secure_vector) + cc = get_crypto_context(context) + OpenFHE.Decrypt(cc, private_key.private_key, secure_vector.ciphertext, + plain_vector.plaintext) + + plain_vector +end + +function decrypt(context::SecureContext{<:OpenFHEBackend}, private_key, secure_vector) + plain_vector = PlainVector(OpenFHE.Plaintext(), context) + + decrypt!(plain_vector, context, private_key, secure_vector) +end + +function add(sv1::SecureVector{<:OpenFHEBackend}, sv2::SecureVector{<:OpenFHEBackend}) + cc = get_crypto_context(sv1) + ciphertext = OpenFHE.EvalAdd(cc, sv1.ciphertext, sv2.ciphertext) + secure_vector = SecureVector(ciphertext, sv1.context) + + secure_vector +end + +function subtract(sv1::SecureVector{<:OpenFHEBackend}, sv2::SecureVector{<:OpenFHEBackend}) + cc = get_crypto_context(sv1) + ciphertext = OpenFHE.EvalSub(cc, sv1.ciphertext, sv2.ciphertext) + secure_vector = SecureVector(ciphertext, sv1.context) + + secure_vector +end + +function multiply(sv1::SecureVector{<:OpenFHEBackend}, sv2::SecureVector{<:OpenFHEBackend}) + cc = get_crypto_context(sv1) + ciphertext = OpenFHE.EvalMult(cc, sv1.ciphertext, sv2.ciphertext) + secure_vector = SecureVector(ciphertext, sv1.context) + + secure_vector +end + +function multiply(sv::SecureVector{<:OpenFHEBackend}, pv::PlainVector{<:OpenFHEBackend}) + cc = get_crypto_context(sv) + ciphertext = OpenFHE.EvalMult(cc, sv.ciphertext, pv.plaintext) + secure_vector = SecureVector(ciphertext, sv.context) + + secure_vector +end + +function multiply(sv::SecureVector{<:OpenFHEBackend}, scalar::Real) + cc = get_crypto_context(sv) + ciphertext = OpenFHE.EvalMult(cc, sv.ciphertext, scalar) + secure_vector = SecureVector(ciphertext, sv.context) + + secure_vector +end + +function rotate(sv::SecureVector{<:OpenFHEBackend}, shift) + cc = get_crypto_context(sv) + # We use `-shift` to match Julia's usual `circshift` direction + ciphertext = OpenFHE.EvalRotate(cc, sv.ciphertext, -shift) + secure_vector = SecureVector(ciphertext, sv.context) + + secure_vector +end diff --git a/src/types.jl b/src/types.jl new file mode 100644 index 0000000..01e536a --- /dev/null +++ b/src/types.jl @@ -0,0 +1,43 @@ +abstract type AbstractCryptoBackend end + +struct SecureContext{CryptoBackendT <: AbstractCryptoBackend} + backend::CryptoBackendT +end + +struct SecureVector{CryptoBackendT <: AbstractCryptoBackend, CiphertextT} + ciphertext::CiphertextT + context::SecureContext{CryptoBackendT} + + function SecureVector(ciphertext, context::SecureContext{CryptoBackendT}) where CryptoBackendT + new{CryptoBackendT, typeof(ciphertext)}(ciphertext, context) + end +end + +struct PlainVector{CryptoBackendT <: AbstractCryptoBackend, PlaintextT} + plaintext::PlaintextT + context::SecureContext{CryptoBackendT} + + function PlainVector(plaintext, context::SecureContext{CryptoBackendT}) where CryptoBackendT + new{CryptoBackendT, typeof(plaintext)}(plaintext, context) + end +end + +Base.print(io::IO, plain_vector::PlainVector) = print(io, plain_vector.plaintext) + +struct PrivateKey{CryptoBackendT <: AbstractCryptoBackend, KeyT} + private_key::KeyT + context::SecureContext{CryptoBackendT} + + function PrivateKey(context::SecureContext{CryptoBackendT}, key) where CryptoBackendT + new{CryptoBackendT, typeof(key)}(key, context) + end +end + +struct PublicKey{CryptoBackendT <: AbstractCryptoBackend, KeyT} + public_key::KeyT + context::SecureContext{CryptoBackendT} + + function PublicKey(context::SecureContext{CryptoBackendT}, key) where CryptoBackendT + new{CryptoBackendT, typeof(key)}(key, context) + end +end diff --git a/src/unencrypted.jl b/src/unencrypted.jl new file mode 100644 index 0000000..1ff1f1d --- /dev/null +++ b/src/unencrypted.jl @@ -0,0 +1,58 @@ +struct Unencrypted <: AbstractCryptoBackend + # No data fields required +end + +function generate_keys(context::SecureContext{<:Unencrypted}) + PublicKey(context, nothing), PrivateKey(context, nothing) +end + +init_multiplication(context::SecureContext{<:Unencrypted}, private_key) = nothing +init_rotation(context::SecureContext{<:Unencrypted}, private_key, shifts) = nothing + +function PlainVector(context::SecureContext{<:Unencrypted}, data::Vector{<:Real}) + plain_vector = PlainVector(data, context) +end + +function encrypt(context::SecureContext{<:Unencrypted}, public_key, data::Vector{<:Real}) + SecureVector(data, context) +end + +function encrypt(context::SecureContext{<:Unencrypted}, public_key, + plain_vector::PlainVector) + SecureVector(plain_vector.plaintext, context) +end + +function decrypt!(plain_vector, context::SecureContext{<:Unencrypted}, private_key, + secure_vector) + plain_vector.plaintext .= secure_vector.ciphertext +end + +function decrypt(context::SecureContext{<:Unencrypted}, private_key, secure_vector) + plain_vector = PlainVector(similar(secure_vector.ciphertext), context) + + decrypt!(plain_vector, context, private_key, secure_vector) +end + +function add(sv1::SecureVector{<:Unencrypted}, sv2::SecureVector{<:Unencrypted}) + SecureVector(sv1.ciphertext .+ sv2.ciphertext, sv1.context) +end + +function subtract(sv1::SecureVector{<:Unencrypted}, sv2::SecureVector{<:Unencrypted}) + SecureVector(sv1.ciphertext .- sv2.ciphertext, sv1.context) +end + +function multiply(sv1::SecureVector{<:Unencrypted}, sv2::SecureVector{<:Unencrypted}) + SecureVector(sv1.ciphertext .* sv2.ciphertext, sv1.context) +end + +function multiply(sv::SecureVector{<:Unencrypted}, pv::PlainVector{<:Unencrypted}) + SecureVector(sv.ciphertext .* pv.plaintext, sv.context) +end + +function multiply(sv::SecureVector{<:Unencrypted}, scalar::Real) + SecureVector(sv.ciphertext .* scalar, sv.context) +end + +function rotate(sv::SecureVector{<:Unencrypted}, shift) + SecureVector(circshift(sv.ciphertext, shift), sv.context) +end From e54060771a40c7013c79f94fc2524648592084e3 Mon Sep 17 00:00:00 2001 From: Michael Schlottke-Lakemper Date: Thu, 18 Jan 2024 09:35:17 +0100 Subject: [PATCH 06/13] Update tests --- test/runtests.jl | 2 +- test/test_ckks.jl | 10 ---------- test/test_examples.jl | 15 +++++++++++++++ 3 files changed, 16 insertions(+), 11 deletions(-) delete mode 100644 test/test_ckks.jl create mode 100644 test/test_examples.jl diff --git a/test/runtests.jl b/test/runtests.jl index 2e6a467..c8217d2 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,6 +1,6 @@ using Test @time @testset verbose=true showtiming=true "SecureArithmetic.jl tests" begin - include("test_ckks.jl") + include("test_examples.jl") end diff --git a/test/test_ckks.jl b/test/test_ckks.jl deleted file mode 100644 index 159a4aa..0000000 --- a/test/test_ckks.jl +++ /dev/null @@ -1,10 +0,0 @@ -module TestCKKS - -using Test -using SecureArithmetic - -@testset verbose=true showtiming=true "dummy" begin - @test_nowarn SecureArithmetic.greet() -end - -end # module diff --git a/test/test_examples.jl b/test/test_examples.jl new file mode 100644 index 0000000..6120ed9 --- /dev/null +++ b/test/test_examples.jl @@ -0,0 +1,15 @@ +module TestExamples + +using Test +using SecureArithmetic + +@testset verbose=true showtiming=true "test_examples.jl" begin + +@testset verbose=true showtiming=true "examples/simple_real_numbers.jl" begin + @test_nowarn include("../examples/simple_real_numbers.jl") +end + +end # @testset "test_examples.jl" + +end # module + From d6148f3a2074c21293779dff0c3e4e5d7fc52ac9 Mon Sep 17 00:00:00 2001 From: Michael Schlottke-Lakemper Date: Thu, 18 Jan 2024 09:38:41 +0100 Subject: [PATCH 07/13] Add OpenFHE to test dependencies --- test/Project.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/Project.toml b/test/Project.toml index ec84819..fa49760 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,5 +1,7 @@ [deps] +OpenFHE = "77ce9b8e-ecf5-45d1-bd8a-d31f384f2f95" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [compat] +OpenFHE = "0.1" Test = "1" From f6f41f82b57c56d315123536d78c4c633a61e81f Mon Sep 17 00:00:00 2001 From: Michael Schlottke-Lakemper Date: Thu, 18 Jan 2024 09:51:24 +0100 Subject: [PATCH 08/13] Update CI --- .github/workflows/SpellCheck.yml | 2 +- .github/workflows/ci.yml | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/workflows/SpellCheck.yml b/.github/workflows/SpellCheck.yml index a780e97..fa3a3d2 100644 --- a/.github/workflows/SpellCheck.yml +++ b/.github/workflows/SpellCheck.yml @@ -10,4 +10,4 @@ jobs: - name: Checkout Actions Repository uses: actions/checkout@v4 - name: Check spelling - uses: crate-ci/typos@v1.16.26 + uses: crate-ci/typos@v1.17.0 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ed7482..92dd33d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,18 +6,22 @@ on: - main tags: ['*'] paths-ignore: + - 'CITATION.bib' - 'LICENSE.md' - 'README.md' - '.zenodo.json' - '.github/workflows/CompatHelper.yml' + - '.github/workflows/SpellCheck.yml' - '.github/workflows/TagBot.yml' - 'docs/**' pull_request: paths-ignore: + - 'CITATION.bib' - 'LICENSE.md' - 'README.md' - '.zenodo.json' - '.github/workflows/CompatHelper.yml' + - '.github/workflows/SpellCheck.yml' - '.github/workflows/TagBot.yml' - 'docs/**' workflow_dispatch: @@ -43,6 +47,10 @@ jobs: - ubuntu-latest - macOS-latest - windows-latest + exclude: + # Include once OpenFHE.jl issues are fixed + - os: windows-latest + version: '1.8' steps: - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@v1 @@ -53,10 +61,18 @@ jobs: - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 - uses: julia-actions/julia-processcoverage@v1 + with: + directories: src,examples - uses: codecov/codecov-action@v3 with: files: lcov.info + token: ${{ secrets.CODECOV_TOKEN }} - uses: coverallsapp/github-action@master with: github-token: ${{ secrets.GITHUB_TOKEN }} path-to-lcov: ./lcov.info + # Enable tmate debugging of manually-triggered workflows if the input option was provided + - name: Setup tmate session for debugging + if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled && always() }} + uses: mxschmitt/action-tmate@v3 + timeout-minutes: 15 From 9e2e5196257e716d51fb83ae622650871ea4b439 Mon Sep 17 00:00:00 2001 From: Michael Schlottke-Lakemper Date: Thu, 18 Jan 2024 09:53:44 +0100 Subject: [PATCH 09/13] Update README --- README.md | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 827f868..30c37c0 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,94 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-success.svg)](https://opensource.org/license/mit/) SecureArithmetic.jl is a Julia package for performing cryptographically secure arithmetic -operations using fully homomorphic encryption. +operations using fully homomorphic encryption. It currently provides a backend for +OpenFHE-secured computations using [OpenFHE.jl](https://github.com/sloede/OpenFHE.jl), and +an unecrypted backend for fast verification of a computation pipeline. + + +## Getting started + +### Prerequisites +If you have not yet installed Julia, please [follow the instructions for your +operating system](https://julialang.org/downloads/platform/). +[SecureArithmetic.jl](https://github.com/sloede/SecureArithmetic.jl) works with Julia v1.8 +and later on Linux and macOS platforms, and with Julia v1.9 or later on Windows platforms. + +### Installation +Since SecureArithmetic.jl is not yet a registered Julia package, you can install it by +executing the following commands in the Julia REPL: +```julia +julia> import Pkg; Pkg.add("https://github.com/sloede/SecureArithmetic.jl") +``` +If you plan on running the examples in the +[`examples`](https://github.com/sloede/SecureArithmetic.jl/tree/main/examples) directory, +you also need to install OpenFHE.jl: +```julia +julia> import Pkg; Pkg.add("OpenFHE") +``` + +### Usage +The easiest way to get started is to run one of the examples from the +[`examples`](https://github.com/sloede/SecureArithmetic.jl/tree/main/examples) directory by +`include`ing them in Julia, e.g., +```julia +julia> using SecureArithmetic + +julia> include(joinpath(pkgdir(SecureArithmetic), "examples", "simple_real_numbers.jl")) +================================================================================ +Creating OpenFHE context... +CKKS scheme is using ring dimension 16384 + +================================================================================ +Creating unencrypted context... +================================================================================ +simple_real_numbers with an OpenFHE context +Input x1: (0.25, 0.5, 0.75, 1, 2, 3, 4, 5, ... ); Estimated precision: 50 bits + +Input x2: (5, 4, 3, 2, 1, 0.75, 0.5, 0.25, ... ); Estimated precision: 50 bits + + +Results of homomorphic computations: +x1 = (0.25, 0.5, 0.75, 1, 2, 3, 4, 5, ... ); Estimated precision: 43 bits + +x1 + x2 = (5.25, 4.5, 3.75, 3, 3, 3.75, 4.5, 5.25, ... ); Estimated precision: 43 bits + +x1 - x2 = (-4.75, -3.5, -2.25, -1, 1, 2.25, 3.5, 4.75, ... ); Estimated precision: 43 bits + +4 * x1 = (1, 2, 3, 4, 8, 12, 16, 20, ... ); Estimated precision: 41 bits + +x1 * x2 = (1.25, 2, 2.25, 2, 2, 2.25, 2, 1.25, ... ); Estimated precision: 41 bits + +x1 shifted circularly by -1 = (0.5, 0.75, 1, 2, 3, 4, 5, 0.25, ... ); Estimated precision: 43 bits + +x1 shifted circularly by 2 = (4, 5, 0.25, 0.5, 0.75, 1, 2, 3, ... ); Estimated precision: 43 bits + +================================================================================ +simple_real_numbers with an Unencrypted context +Input x1: [0.25, 0.5, 0.75, 1.0, 2.0, 3.0, 4.0, 5.0] +Input x2: [5.0, 4.0, 3.0, 2.0, 1.0, 0.75, 0.5, 0.25] + +Results of homomorphic computations: +x1 = [0.25, 0.5, 0.75, 1.0, 2.0, 3.0, 4.0, 5.0] +x1 + x2 = [5.25, 4.5, 3.75, 3.0, 3.0, 3.75, 4.5, 5.25] +x1 - x2 = [-4.75, -3.5, -2.25, -1.0, 1.0, 2.25, 3.5, 4.75] +4 * x1 = [1.0, 2.0, 3.0, 4.0, 8.0, 12.0, 16.0, 20.0] +x1 * x2 = [1.25, 2.0, 2.25, 2.0, 2.0, 2.25, 2.0, 1.25] +x1 shifted circularly by -1 = [0.5, 0.75, 1.0, 2.0, 3.0, 4.0, 5.0, 0.25] +x1 shifted circularly by 2 = [4.0, 5.0, 0.25, 0.5, 0.75, 1.0, 2.0, 3.0] +``` + + +## Referencing +If you use SecureArithmetic.jl in your own research, please cite this repository as follows: +```bibtex +@misc{schlottkelakemper2024securearithmetic, + title={{S}ecure{A}rithmetic.jl: {C}ryptographically secure arithmetic operations in {J}ulia using fully homomorphic encryption}, + author={Schlottke-Lakemper, Michael}, + year={2024}, + howpublished={\url{https://github.com/sloede/SecureArithmetic.jl}} +} +``` ## Authors From a3c733fc21f66d8dd7d62cf01192b38c1e66f5ff Mon Sep 17 00:00:00 2001 From: Michael Schlottke-Lakemper Date: Thu, 18 Jan 2024 09:54:54 +0100 Subject: [PATCH 10/13] Add CITATION.bib --- CITATION.bib | 7 +++++++ README.md | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 CITATION.bib diff --git a/CITATION.bib b/CITATION.bib new file mode 100644 index 0000000..80b18d2 --- /dev/null +++ b/CITATION.bib @@ -0,0 +1,7 @@ +@misc{schlottkelakemper2024securearithmetic, + title={{S}ecure{A}rithmetic.jl: {S}ecure arithmetic operations in {J}ulia using fully homomorphic encryption}, + author={Schlottke-Lakemper, Michael}, + year={2024}, + howpublished={\url{https://github.com/sloede/SecureArithmetic.jl}} +} + diff --git a/README.md b/README.md index 30c37c0..dc519ef 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ x1 shifted circularly by 2 = [4.0, 5.0, 0.25, 0.5, 0.75, 1.0, 2.0, 3.0] If you use SecureArithmetic.jl in your own research, please cite this repository as follows: ```bibtex @misc{schlottkelakemper2024securearithmetic, - title={{S}ecure{A}rithmetic.jl: {C}ryptographically secure arithmetic operations in {J}ulia using fully homomorphic encryption}, + title={{S}ecure{A}rithmetic.jl: {S}ecure arithmetic operations in {J}ulia using fully homomorphic encryption}, author={Schlottke-Lakemper, Michael}, year={2024}, howpublished={\url{https://github.com/sloede/SecureArithmetic.jl}} From 3359d6629357d059cc25ac67c816e9616572dbcd Mon Sep 17 00:00:00 2001 From: Michael Schlottke-Lakemper Date: Thu, 18 Jan 2024 09:55:50 +0100 Subject: [PATCH 11/13] Add Zenodo file --- .zenodo.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .zenodo.json diff --git a/.zenodo.json b/.zenodo.json new file mode 100644 index 0000000..10f0cfb --- /dev/null +++ b/.zenodo.json @@ -0,0 +1,14 @@ +{ + "title": "SecureArithmetic.jl", + "description": "Secure arithmetic operations in Julia using fully homomorphic encryption", + "license": "MIT", + "upload_type": "software", + "creators": [ + { + "affiliation": "Applied and Computational Mathematics, RWTH Aachen University, Germany", + "name": "Schlottke-Lakemper, Michael", + "orcid": "0000-0002-3195-2536" + } + ], + "access_right": "open" +} From bb2b59220ba6c73281879ac67542904e5322a056 Mon Sep 17 00:00:00 2001 From: Michael Schlottke-Lakemper Date: Thu, 18 Jan 2024 09:57:32 +0100 Subject: [PATCH 12/13] Update docs --- docs/.gitignore | 1 + docs/make.jl | 2 +- docs/src/reference.md | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 docs/.gitignore diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/docs/make.jl b/docs/make.jl index 66e09b3..c18b766 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -60,7 +60,7 @@ makedocs( # Explicitly specify documentation structure pages = [ "Home" => "index.md", - "API Reference" => "reference.md", + "API reference" => "reference.md", "License" => "license.md" ], ) diff --git a/docs/src/reference.md b/docs/src/reference.md index 9bab422..0f49988 100644 --- a/docs/src/reference.md +++ b/docs/src/reference.md @@ -6,5 +6,4 @@ CurrentModule = SecureArithmetic ```@autodocs Modules = [SecureArithmetic] -Private = false ``` From 0cd998078171999fe59b03cc8d54a4c349446c82 Mon Sep 17 00:00:00 2001 From: Michael Schlottke-Lakemper Date: Thu, 18 Jan 2024 09:58:09 +0100 Subject: [PATCH 13/13] Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dc519ef..d73be2e 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ SecureArithmetic.jl is a Julia package for performing cryptographically secure arithmetic operations using fully homomorphic encryption. It currently provides a backend for OpenFHE-secured computations using [OpenFHE.jl](https://github.com/sloede/OpenFHE.jl), and -an unecrypted backend for fast verification of a computation pipeline. +an unencrypted backend for fast verification of a computation pipeline. ## Getting started