Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
Manifest.toml
run/
LocalPreferences.toml
1 change: 1 addition & 0 deletions src/SecureArithmetic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export Unencrypted, OpenFHEBackend
# Crypto operations
export generate_keys, init_multiplication!, init_rotation!, init_bootstrapping!
export encrypt, decrypt, decrypt!, bootstrap!
export serialize, deserialize

# Query crypto objects
export level, capacity
Expand Down
25 changes: 25 additions & 0 deletions src/operations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,31 @@ function decrypt(secure_array::SecureArray, private_key::PrivateKey)
decrypt_impl(secure_array, private_key)
end

"""
serialize(obj)

Serialize `obj` to a JSON string.

See also: [`deserialize`](@ref)
"""
function serialize(obj)
# Convert from C++ string to Julia String for memory safety
String(OpenFHE.SerializeToString(obj))
end

"""
deserialize(::Type{T}, json::AbstractString)

Deserialize a JSON string `json` into a new object of type `T`.

See also: [`serialize`](@ref)
"""
function deserialize(::Type{T}, json::AbstractString) where T
obj = T()
OpenFHE.DeserializeFromString(obj, json)
return obj
end

"""
release_context_memory()

Expand Down
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ using Test

@time @testset verbose=true showtiming=true "SecureArithmetic.jl tests" begin
include("test_unit.jl")
include("test_serialization.jl")
include("test_examples.jl")
end

105 changes: 105 additions & 0 deletions test/test_serialization.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
module TestSerialization

using Test
using SecureArithmetic
using OpenFHE

@testset verbose=true showtiming=true "test_serialization.jl" begin

multiplicative_depth = 2
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)

context = SecureContext(OpenFHEBackend(cc))
public_key, private_key = generate_keys(context)

x1 = [0.25, 0.5, 0.75, 1.0, 2.0, 3.0, 4.0, 5.0]
pv1 = PlainVector(x1, context)
sv1 = encrypt(pv1, public_key)

@testset verbose=true showtiming=true "Ciphertext" begin
for ct in sv1.data
json = serialize(ct)
@test json isa String
@test !isempty(json)
ct_deserialized = deserialize(OpenFHE.Ciphertext{OpenFHE.DCRTPoly}, json)
@test ct_deserialized isa OpenFHE.Ciphertext{OpenFHE.DCRTPoly}
end
end

@testset verbose=true showtiming=true "CryptoContext" begin
json = serialize(cc)
@test json isa String
@test !isempty(json)
cc_deserialized = deserialize(OpenFHE.CryptoContext{OpenFHE.DCRTPoly}, json)
@test cc_deserialized isa OpenFHE.CryptoContext{OpenFHE.DCRTPoly}
end

@testset verbose=true showtiming=true "PublicKey" begin
json = serialize(public_key.public_key)
@test json isa String
@test !isempty(json)
pk_deserialized = deserialize(OpenFHE.PublicKey{OpenFHE.DCRTPoly}, json)
@test pk_deserialized isa OpenFHE.PublicKey{OpenFHE.DCRTPoly}
end

@testset verbose=true showtiming=true "PrivateKey" begin
json = serialize(private_key.private_key)
@test json isa String
@test !isempty(json)
sk_deserialized = deserialize(OpenFHE.PrivateKey{OpenFHE.DCRTPoly}, json)
@test sk_deserialized isa OpenFHE.PrivateKey{OpenFHE.DCRTPoly}
end

# Note: shape and capacity must be transmitted as metadata alongside the serialized
# ciphertexts, since OpenFHE serialization does not preserve this information.
@testset verbose=true showtiming=true "roundtrip vector" begin
cc_restored = deserialize(OpenFHE.CryptoContext{OpenFHE.DCRTPoly}, serialize(cc))
context_restored = SecureContext(OpenFHEBackend(cc_restored))

sk_restored = SecureArithmetic.PrivateKey(context_restored,
deserialize(OpenFHE.PrivateKey{OpenFHE.DCRTPoly}, serialize(private_key.private_key)))

restored_cts = map(sv1.data) do ct
deserialize(OpenFHE.Ciphertext{OpenFHE.DCRTPoly}, serialize(ct))
end
sv_restored = SecureArray(collect(restored_cts), size(sv1), capacity(sv1), context_restored)

@test collect(decrypt(sv_restored, sk_restored)) ≈ collect(decrypt(sv1, private_key))
end

x2 = [0.25 0.5; 0.75 1.0; 2.0 3.0; 4.0 5.0]
pm1 = PlainMatrix(x2, context)
sm1 = encrypt(pm1, public_key)

@testset verbose=true showtiming=true "roundtrip matrix" begin
cc_restored = deserialize(OpenFHE.CryptoContext{OpenFHE.DCRTPoly}, serialize(cc))
context_restored = SecureContext(OpenFHEBackend(cc_restored))

sk_restored = SecureArithmetic.PrivateKey(context_restored,
deserialize(OpenFHE.PrivateKey{OpenFHE.DCRTPoly}, serialize(private_key.private_key)))

restored_cts = map(sm1.data) do ct
deserialize(OpenFHE.Ciphertext{OpenFHE.DCRTPoly}, serialize(ct))
end
sm_restored = SecureArray(collect(restored_cts), size(sm1), capacity(sm1), context_restored)

@test collect(decrypt(sm_restored, sk_restored)) ≈ collect(decrypt(sm1, private_key))
end

release_context_memory()
GC.gc()

end # @testset "test_serialization.jl"

end # module
Loading