Encoding should be a parametric type to allow multiple dispatch
hardbyte opened this issue · 0 comments
I've been thinking about how Paillier.jl
composes with other parts of the Julia ecosystem, in particular the fact that users should be able to encode and encrypt/decrypt user defined types easily.
Currently using the library with Measurement.jl
numbers is messy. One method would be to create an array of both the value and the error from the measurement object:
> encoding = Encoding(Float64, publickey)
> Paillier.encode_and_encrypt(plaintext::Measurement, encoding::Encoding) = Paillier.encode_and_encrypt([plaintext.val, plaintext.err], encoding)
> encrypted_g_with_uncertainty = encode_and_encrypt(9.79 ± 0.02, encoding);
> decrypt_and_decode(privatekey, encrypted_g_with_uncertainty)
2-element Array{Float16,1}:
9.79
0.02
However as the encoding doesn't include the Measurement
type we can't nicely reconstruct a Measurement object after decryption and decoding.
Creating an encoding of a Measurement
doesn't work either:
> measurementencoding = Encoding(Measurement{Float64}, publickey)
> encrypted_g_2 = encode_and_encrypt(9.79 ± 0.02, measurementencoding)
MethodError: no method matching precision(::Type{Measurement{Float64}})
Closest candidates are:
precision(!Matched::BigFloat) at mpfr.jl:805
precision(!Matched::Type{Float16}) at float.jl:579
precision(!Matched::Type{Float32}) at float.jl:580
...
Stacktrace:
[1] encode(::Float64, ::Encoding) at src\Encoding.jl:200
As Julia tries to ascertain the precision of a Measurement
within the encode
method.
Now while we could probably hack the precision method to unwrap the measurement object, there is still a question of how to encode the measurement into one or more integers suitable for encryption. A better strategy for handling encoding of arbitrary user defined types might be to have users override encode
for their user defined Encoding
type.
So instead of Encoding
being defined as:
struct Encoding
datatype::DataType
public_key::PublicKey
base::Int64
end
Perhaps it should be a parametric type:
struct Encoding{T}
public_key::PublicKey
base::Int64
end
# Example use:
encoding = Encoding{Measurement{Float64}}(publickey, 16)
# Then a user can specialize the `encode` and `decode` methods for their data type. E.g.:
Paillier.encode(scalar::Measurement{Float64}, encoding::Encoding{Measurement{T}}) where T = Paillier.encode(
[scalar.val, scalar.err],
Encoding{T}(encoding.public_key, encoding.base)
)
# decode would need more work as decrypt_and_decode calls decode for each element
# of an encrypted array and not with an array of encodings. But that refactoring seems sensible
# at first glance. Which for our example of encoding a Measurement as an array of encrypted numbers give something like this for the decode method:
decode(encoded::Array{BigInt}, exponent, encoding::Encoding{Measurement{T}}) where T =
measurement(decode(encoded[1], exponent, T), decode(encoded[2], exponent, T))