Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Secure Array #59

Merged
merged 37 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
bf9c883
Start working on arrays
ArseniyKholod Jan 10, 2025
d9c8fb9
add rotation alg 1d (didn't test it yet)
ArseniyKholod Jan 11, 2025
d659d30
Test and fix bugs, add unit test, alg works!!!
ArseniyKholod Jan 12, 2025
2c4f66c
fix a bit coverage
ArseniyKholod Jan 12, 2025
62a5157
improve coverage
ArseniyKholod Jan 12, 2025
ff9ba83
spell check
ArseniyKholod Jan 12, 2025
6c34a82
first implementation of nd rotation, not optimal
ArseniyKholod Jan 15, 2025
cc4486a
"better" implementation of nD rotation
ArseniyKholod Jan 15, 2025
57cf46e
spell check
ArseniyKholod Jan 15, 2025
a4b9c59
replace Vectors amd Matricies with aliases for Array
ArseniyKholod Jan 22, 2025
ba59d91
spell check
ArseniyKholod Jan 22, 2025
c4221c5
add docs to types
ArseniyKholod Jan 23, 2025
a3eb83f
add docs to arithmetic.jl
ArseniyKholod Jan 23, 2025
7d2a607
add docs in openfhe.jl and unencrypted.jl
ArseniyKholod Jan 23, 2025
6582c62
Merge branch 'main' into Add-SecureArray
ArseniyKholod Jan 23, 2025
8a1485f
Introduce multithreading
ArseniyKholod Jan 23, 2025
3a05988
correct unittests and add array example
ArseniyKholod Jan 23, 2025
fb76c84
improve convergence
ArseniyKholod Jan 23, 2025
657503a
update compat
ArseniyKholod Jan 23, 2025
e862234
try new openfhe_julia_jll
ArseniyKholod Jan 24, 2025
486d1c8
update OpenFHE.jl version
ArseniyKholod Jan 29, 2025
c96c6ce
julia requirements 1.10
ArseniyKholod Jan 29, 2025
6082198
clear unnecessary code and make small corrections
ArseniyKholod Jan 29, 2025
83dd735
get back `init_matrix_rotation`
ArseniyKholod Jan 29, 2025
4155862
Apply suggestions from code review
ArseniyKholod Jan 29, 2025
ad526c4
apply some suggestions
ArseniyKholod Jan 29, 2025
8dddbaa
apply some suggestions
ArseniyKholod Jan 29, 2025
1877020
remove threaded
ArseniyKholod Jan 30, 2025
35a2aa8
change `init_rotation!` api
ArseniyKholod Jan 31, 2025
cfd42ef
update circshift
ArseniyKholod Jan 31, 2025
bab919a
apply changes
ArseniyKholod Feb 3, 2025
95efabc
apply some changes
ArseniyKholod Feb 3, 2025
a979891
Merge branch 'main' into Add-SecureArray
ArseniyKholod Feb 3, 2025
ae52cad
compute_rotation_indices
ArseniyKholod Feb 3, 2025
2507c67
Merge branch 'Add-SecureArray' of https://github.com/ArseniyKholod/Se…
ArseniyKholod Feb 3, 2025
b307986
Create .typos.toml
ArseniyKholod Feb 3, 2025
8e06fb5
Update .typos.toml
ArseniyKholod Feb 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,12 @@ jobs:
fail-fast: false
matrix:
version:
- '1.8'
- '1.9'
- '1.10'
- '1.11'
os:
- 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@v2
Expand Down
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ version = "0.1.6-dev"
OpenFHE = "77ce9b8e-ecf5-45d1-bd8a-d31f384f2f95"

[compat]
OpenFHE = "0.1.11"
julia = "1.8"
OpenFHE = "0.1.12"
julia = "1.10"
134 changes: 134 additions & 0 deletions examples/simple_array_operations.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
using SecureArithmetic
using OpenFHE

function simple_array_operations(context)
public_key, private_key = generate_keys(context)

init_multiplication!(context, private_key)
init_bootstrapping!(context, private_key)
init_rotation!(context, private_key, (3, 3, 3), (1, -1, 1), (0, 1, 0))

a1 = reshape(Vector(range(1, 27)), (3, 3, 3))
a2 = reshape(Vector(range(27, 1, step=-1)), (3, 3, 3))

pa1 = PlainArray(a1, context)
pa2 = PlainArray(a2, context)

println("Input array a1: ", pa1)
println("Input array a2: ", pa2)

sa1 = encrypt(pa1, public_key)
sa2 = encrypt(pa2, public_key)

sa_add = sa1 + sa2

sa_sub = sa1 - sa2

sa_scalar = sa1 * 4.0

sa_mult = sa1 * sa2

sa_shift1 = circshift(sa1, (0, 1, 0))
sa_shift2 = circshift(sa1, (1, -1, 1))

# Perform the bootstrapping operation over an array. The goal is to increase the number of
# levels remaining for HE computation.
sa_after_bootstrap = bootstrap!(sa1)


println()
println("Results of homomorphic computations: ")

result_sa1 = decrypt(sa1, private_key)
println("a1 = ", result_sa1)

result_sa_add = decrypt(sa_add, private_key)
println("a1 + a2 = ", result_sa_add)

result_sa_sub = decrypt(sa_sub, private_key)
println("a1 - a2 = ", result_sa_sub)

result_sa_scalar = decrypt(sa_scalar, private_key)
println("4 * a1 = ", result_sa_scalar)

result_sa_mult = decrypt(sa_mult, private_key)
println("a1 * a2 = ", result_sa_mult)

result_sa_shift1 = decrypt(sa_shift1, private_key)
println("a1 shifted circularly by (0, 1, 0) = ", result_sa_shift1)

result_sa_shift2 = decrypt(sa_shift2, private_key)
println("a1 shifted circularly by (1, -1, 1) = ", result_sa_shift2)

result_after_bootstrap = decrypt(sa_after_bootstrap, private_key)
println("a1 after bootstrapping \n\t", result_after_bootstrap)

# Clean all `OpenFHE.CryptoContext`s and generated keys.
release_context_memory()
GC.gc()
end


################################################################################
println("="^80)
println("Creating OpenFHE context...")

parameters = CCParams{CryptoContextCKKSRNS}()

secret_key_distribution = UNIFORM_TERNARY
SetSecretKeyDist(parameters, secret_key_distribution)

SetSecurityLevel(parameters, HEStd_NotSet)
SetRingDim(parameters, 1 << 5)

rescale_technique = FLEXIBLEAUTO
dcrt_bits = 59
first_modulus = 60

SetScalingModSize(parameters, dcrt_bits)
SetScalingTechnique(parameters, rescale_technique)
SetFirstModSize(parameters, first_modulus)

level_budget = [4, 4]

levels_available_after_bootstrap = 10
depth = levels_available_after_bootstrap + GetBootstrapDepth(level_budget, secret_key_distribution)
SetMultiplicativeDepth(parameters, depth)

cc = GenCryptoContext(parameters)

Enable(cc, PKE)
Enable(cc, KEYSWITCH)
Enable(cc, LEVELEDSHE)
Enable(cc, ADVANCEDSHE)
Enable(cc, FHE)

ring_dimension = GetRingDimension(cc)
# This is the maximum number of slots that can be used for full packing.
num_slots = div(ring_dimension, 2)
println("CKKS scheme is using ring dimension ", ring_dimension)
println()

EvalBootstrapSetup(cc; level_budget)

context_openfhe = SecureContext(OpenFHEBackend(cc))


################################################################################
println("="^80)
println("Creating unencrypted context...")
println()

context_unencrypted = SecureContext(Unencrypted())


################################################################################
println("="^80)
println("simple_array_operations with an OpenFHE context")
simple_array_operations(context_openfhe)


################################################################################
println("="^80)
println("simple_array_operations with an Unencrypted context")
simple_array_operations(context_unencrypted)
2 changes: 1 addition & 1 deletion examples/simple_matrix_operations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ function simple_matrix_operations(context)

init_multiplication!(context, private_key)
init_bootstrapping!(context, private_key)
init_matrix_rotation!(context, private_key, [(1, -1), (0, 1)], (3, 3))
init_rotation!(context, private_key, (3, 3), (1, -1), (0, 1))

m1 = [0.25 0.5 0.75;
1.0 2.0 3.0;
Expand Down
2 changes: 1 addition & 1 deletion examples/simple_real_numbers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ function simple_real_numbers(context)
public_key, private_key = generate_keys(context)

init_multiplication!(context, private_key)
init_rotation!(context, private_key, [-1, 2])
init_rotation!(context, private_key, (8,), -1, 2)
ArseniyKholod marked this conversation as resolved.
Show resolved Hide resolved


x1 = [0.25, 0.5, 0.75, 1.0, 2.0, 3.0, 4.0, 5.0]
Expand Down
6 changes: 3 additions & 3 deletions src/SecureArithmetic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ module SecureArithmetic
using OpenFHE: OpenFHE

# Basic types
export SecureContext, SecureVector, PlainVector, PlainMatrix, SecureMatrix
export SecureContext, SecureVector, PlainVector, PlainMatrix, SecureMatrix,
PlainArray, SecureArray

# Keys
export PrivateKey, PublicKey
Expand All @@ -12,8 +13,7 @@ export PrivateKey, PublicKey
export Unencrypted, OpenFHEBackend

# Crypto operations
export generate_keys, init_multiplication!, init_rotation!, init_bootstrapping!,
init_matrix_rotation!
export generate_keys, init_multiplication!, init_rotation!, init_bootstrapping!
export encrypt, decrypt, decrypt!, bootstrap!

# Query crypto objects
Expand Down
104 changes: 32 additions & 72 deletions src/arithmetic.jl
Original file line number Diff line number Diff line change
@@ -1,90 +1,50 @@
# Add
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.:+(sv::SecureVector, scalar::Real) = add(sv, scalar)
Base.:+(scalar::Real, sv::SecureVector) = add(sv, scalar)
Base.:+(sa1::SecureArray{B, N}, sa2::SecureArray{B, N}) where {B, N} = add(sa1, sa2)
Base.:+(sa::SecureArray{B, N}, pa::PlainArray{B, N}) where {B, N} = add(sa, pa)
Base.:+(pa::PlainArray{B, N}, sa::SecureArray{B, N}) where {B, N} = add(sa, pa)
Base.:+(sa::SecureArray, scalar::Real) = add(sa, scalar)
Base.:+(scalar::Real, sa::SecureArray) = add(sa, scalar)

# Subtract
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.:-(sv::SecureVector, scalar::Real) = subtract(sv, scalar)
Base.:-(scalar::Real, sv::SecureVector) = subtract(scalar, sv)

Base.:-(sa1::SecureArray{B, N}, sa2::SecureArray{B, N}) where {B, N} = subtract(sa1, sa2)
Base.:-(sa::SecureArray{B, N}, pa::PlainArray{B, N}) where {B, N} = subtract(sa, pa)
Base.:-(pa::PlainArray{B, N}, sa::SecureArray{B, N}) where {B, N} = subtract(pa, sa)
Base.:-(sa::SecureArray, scalar::Real) = subtract(sa, scalar)
Base.:-(scalar::Real, sa::SecureArray) = subtract(scalar, sa)
# Negate
Base.:-(sv::SecureVector) = negate(sv)
Base.:-(sa::SecureArray) = negate(sa)

# Multiply
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.:*(sa1::SecureArray{B, N}, sa2::SecureArray{B, N}) where {B, N} = multiply(sa1, sa2)
Base.:*(sa::SecureArray{B, N}, pa::PlainArray{B, N}) where {B, N} = multiply(sa, pa)
Base.:*(pa::PlainArray{B, N}, sa::SecureArray{B, N}) where {B, N} = multiply(sa, pa)
Base.:*(sa::SecureArray, scalar::Real) = multiply(sa, scalar)
Base.:*(scalar::Real, sa::SecureArray) = multiply(sa, scalar)

# Circular shift
"""
circshift(sv::SecureVector, shift; wrap_by = :capacity)
circshift(sa::SecureArray, shifts)

Circularly shift, i.e., rotate the data in `sv` by `shift` positions, similarly to Julia's
`circshift` for regular arrays. `wrap_by` indicates whether the rotation should be applied
with respect to the current data length of `sv` (`wrap_by :length`) or with respect to its
maximum capacity (`wrap_by = :capacity`).
Circularly shift, i.e., rotate the data in `sa` by `shifts` positions, similarly to Julia's
`circshift` for regular arrays.

Note: If `sv`'s length is less than its capacity, wrapping by `:length` increases the
multiplicative depth of your algorithm by one and is more expensive to compute. Furthermore,
one additional rotation is applied with a shift of
`-sign(shift) * (length(sv) - abs(shift))`.
Note: If `N` is greater than one, this operation increases the multiplicative level by two,
otherwise by one.

See also: [`SecureVector`](@ref), [`length`](@ref), [`capacity`](@ref)
"""
function Base.circshift(sv::SecureVector, shift::Integer; wrap_by = :capacity)
if !(wrap_by in (:length, :capacity))
throw(ArgumentError("Unsupported value '$wrap_by' passed to `wrap_by` (must be `:length` or `:capacity`)"))
end
Note: To precompute all required rotation indexes, use `init_rotation!`.

if shift == 0
return sv
See also: [`SecureArray`](@ref), [`init_rotation!`](@ref)
"""
function Base.circshift(sa::SecureArray, shifts)
if length(shifts) > ndims(sa)
throw(ArgumentError("Got rotation index with length $(length(shifts)), expected $(ndims(sa))"))
elseif length(shifts) < ndims(sa)
shifts = vcat(collect(shifts), zeros(Integer, ndims(sa) - length(shifts)))
end

rotate(sv, shift; wrap_by)
end


############################################################################################
# Matrix
############################################################################################

# Add
Base.:+(sm1::SecureMatrix, sm2::SecureMatrix) = add(sm1, sm2)
Base.:+(sm::SecureMatrix, pm::PlainMatrix) = add(sm, pm)
Base.:+(pm::PlainMatrix, sm::SecureMatrix) = add(sm, pm)
Base.:+(sm::SecureMatrix, scalar::Real) = add(sm, scalar)
Base.:+(scalar::Real, sm::SecureMatrix) = add(sm, scalar)

# Subtract
Base.:-(sm1::SecureMatrix, sm2::SecureMatrix) = subtract(sm1, sm2)
Base.:-(sm::SecureMatrix, pm::PlainMatrix) = subtract(sm, pm)
Base.:-(pm::PlainMatrix, sm::SecureMatrix) = subtract(pm, sm)
Base.:-(sm::SecureMatrix, scalar::Real) = subtract(sm, scalar)
Base.:-(scalar::Real, sm::SecureMatrix) = subtract(scalar, sm)

# Negate
Base.:-(sm::SecureMatrix) = negate(sm)

# Multiply
Base.:*(sm1::SecureMatrix, sm2::SecureMatrix) = multiply(sm1, sm2)
Base.:*(sm::SecureMatrix, pm::PlainMatrix) = multiply(sm, pm)
Base.:*(pm::PlainMatrix, sm::SecureMatrix) = multiply(sm, pm)
Base.:*(sm::SecureMatrix, scalar::Real) = multiply(sm, scalar)
Base.:*(scalar::Real, sm::SecureMatrix) = multiply(sm, scalar)

# Circular shift
function Base.circshift(sm::SecureMatrix, shift::Tuple{Integer, Integer})
if shift[1] % size(sm, 1) == 0 && shift[2] % size(sm, 2) == 0
return sm
if all(shifts .% size(sa) .== 0)
return sa
end

rotate(sm, shift)
rotate(sa, shifts)
end

Loading
Loading