feat(frontend-rust): support interop with tfhers ciphertexts

This commit is contained in:
Alexandre Péré
2025-04-28 14:28:02 +02:00
parent cc90e5f12b
commit 6948d91cc6
39 changed files with 1583 additions and 465 deletions

View File

@@ -159,7 +159,7 @@ struct Value {
/// Turns a server value to a client value, without interpreting the kind of
/// value.
static Value fromRawTransportValue(TransportValue transportVal);
static Value fromRawTransportValue(const TransportValue &transportVal);
/// Turns a client value to a raw (without kind info attached) server value.
TransportValue intoRawTransportValue() const;

View File

@@ -22,6 +22,7 @@ class TfhersExporter:
"""Convert Concrete value to TFHErs and serialize it.
Args:
value (Value): value to export
info (TfhersFheIntDescription): description of the TFHErs integer to export to

View File

@@ -12,8 +12,16 @@ if(LINUX)
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-Bsymbolic")
endif()
target_link_libraries(ConcreteRust PRIVATE ConcretelangSupport ConcretelangClientLib ConcretelangServerLib
ConcretelangRuntimeStatic)
target_link_libraries(
ConcreteRust
PRIVATE ConcretelangSupport
ConcretelangClientLib
ConcretelangServerLib
ConcretelangRuntimeStatic
LLVMSupport
capnp
capnp-json
kj)
if(APPLE)
find_library(SECURITY_FRAMEWORK Security)

View File

@@ -25,7 +25,7 @@ using concretelang::protocol::vectorToProtoPayload;
namespace concretelang {
namespace values {
Value Value::fromRawTransportValue(TransportValue transportVal) {
Value Value::fromRawTransportValue(const TransportValue &transportVal) {
Value output;
auto integerPrecision =
transportVal.asReader().getRawInfo().getIntegerPrecision();

View File

@@ -1,8 +1,28 @@
CARGO_SUBDIRS := concrete concrete-macro test concrete-keygen
.PHONY: test
.PHONY: test clean
test:
@for dir in $(CARGO_SUBDIRS); do \
echo "Running cargo test in $$dir..."; \
COMPILER_BUILD_DIRECTORY=../../../compilers/concrete-compiler/compiler/build cargo test --manifest-path $$dir/Cargo.toml || exit 1; \
COMPILER_BUILD_DIRECTORY=../../../compilers/concrete-compiler/compiler/build cargo test --all-features --manifest-path $$dir/Cargo.toml -- --nocapture || exit 1; \
done
clean:
@for dir in $(CARGO_SUBDIRS); do \
echo "Cleaning target folder in $$dir..."; \
rm -rf $$dir/target || exit 1; \
done
run:
cd test && \
COMPILER_BUILD_DIRECTORY=../../../compilers/concrete-compiler/compiler/build cargo run
format:
@for dir in $(CARGO_SUBDIRS); do \
echo "Running format in $$dir..."; \
COMPILER_BUILD_DIRECTORY=$(COMPILER_BUILD_DIRECTORY) cargo fmt --manifest-path $$dir/Cargo.toml || exit 1; \
done
regen_test_zips:
python test/python/test.py
python test/python/test_tfhers.py

View File

@@ -30,8 +30,11 @@ fn assemble_keyset_from_zip(
// Read keyset info
let mut keyset_info_file = archive.by_name(KEYSET_INFO_FILENAME).unwrap();
let mut keyset_info_buffer = Vec::new();
keyset_info_file.read_to_end(&mut keyset_info_buffer).unwrap();
let keyset_info_message = concrete_protocol_capnp::read_capnp_from_buffer(&keyset_info_buffer).unwrap();
keyset_info_file
.read_to_end(&mut keyset_info_buffer)
.unwrap();
let keyset_info_message =
concrete_protocol_capnp::read_capnp_from_buffer(&keyset_info_buffer).unwrap();
let keyset_info_proto: concrete_protocol_capnp::keyset_info::Reader<'_> =
concrete_protocol_capnp::get_reader_from_message(&keyset_info_message).unwrap();

View File

@@ -21,4 +21,5 @@ proc-macro2 = "1.0"
zip = "2.2.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
itertools = "0.14.0"
concrete = { version = "2.10.1", path = "../concrete", features = ["compiler"]}

View File

@@ -1,4 +1,6 @@
use std::{hash::{Hash, Hasher}, os::unix::fs::MetadataExt, path::PathBuf};
use std::hash::{Hash, Hasher};
use std::os::unix::fs::MetadataExt;
use std::path::PathBuf;
pub struct FastPathHasher {
path: PathBuf,

View File

@@ -1,7 +1,49 @@
use concrete::protocol::{CircuitInfo, ProgramInfo};
use concrete::protocol::{CircuitInfo, ProgramInfo, TypeInfo};
use concrete::tfhe::{FunctionSpec, IntegerType};
use itertools::multizip;
use quote::{format_ident, quote};
pub fn generate_unsafe_binding(pi: &ProgramInfo) -> proc_macro2::TokenStream {
pub fn generate(pi: &ProgramInfo, hash: u64) -> proc_macro2::TokenStream {
let lib_name = format!("concrete-artifact-{hash}");
let unsafe_binding = generate_unsafe_binding(&pi);
let infos = generate_infos(&pi);
let keyset = generate_keyset(&pi);
let client = generate_client(&pi);
let server = generate_server(&pi);
let links = if cfg!(target_os = "macos") {
quote! {
#[link(name = "ConcretelangRuntime")]
#[link(name = "omp")]
}
} else if cfg!(target_os = "linux") {
quote! {
#[link(name = "ConcretelangRuntime")]
#[link(name = "hpx_iostreams")]
#[link(name = "hpx_core")]
#[link(name = "hpx")]
#[link(name = "omp")]
}
} else {
panic!("Unsupported platform");
};
quote! {
#infos
#keyset
#client
#server
#[doc(hidden)]
pub mod _binding {
#[link(name = #lib_name, kind="static")]
#links
#unsafe_binding
}
}
}
fn generate_unsafe_binding(pi: &ProgramInfo) -> proc_macro2::TokenStream {
let func_defs = pi
.circuits
.iter()
@@ -19,7 +61,7 @@ pub fn generate_unsafe_binding(pi: &ProgramInfo) -> proc_macro2::TokenStream {
}
}
pub fn generate_infos(pi: &ProgramInfo) -> proc_macro2::TokenStream {
fn generate_infos(pi: &ProgramInfo) -> proc_macro2::TokenStream {
quote! {
pub static PROGRAM_INFO: std::sync::LazyLock<::concrete::protocol::ProgramInfo> = std::sync::LazyLock::new(|| {
#pi
@@ -27,26 +69,115 @@ pub fn generate_infos(pi: &ProgramInfo) -> proc_macro2::TokenStream {
}
}
pub fn generate_keyset() -> proc_macro2::TokenStream {
fn generate_keyset(pi: &ProgramInfo) -> proc_macro2::TokenStream {
let mut need_providing = pi
.circuits
.iter()
.flat_map(|ci| {
multizip((
std::iter::repeat(&ci.name),
std::iter::successors(Some(0), |a| Some(a + 1)),
ci.inputs.iter(),
pi.tfhers_specs
.as_ref()
.unwrap()
.get_func(&ci.name)
.unwrap()
.input_types
.iter(),
))
})
.filter(|(_, _, _, spec)| spec.is_some())
.collect::<Vec<_>>();
need_providing.dedup_by_key(|a| {
let TypeInfo::lweCiphertext(ref ct) = a.2.typeInfo else {
unreachable!()
};
ct.encryption.keyId
});
let fields_idents = need_providing
.iter()
.map(|(func_name, ith, ..)| format_ident!("{func_name}_{ith}"))
.collect::<Vec<_>>();
let fields_types = need_providing
.iter()
.map(|_| {
quote! { Option<concrete::UniquePtr<concrete::common::LweSecretKey>>}
})
.collect::<Vec<_>>();
let methods = need_providing.iter().map(|(func_name, ith, gi, ..)| {
let method_name = format_ident!("with_key_for_{func_name}_{ith}_arg");
let ident = format_ident!("{func_name}_{ith}");
let TypeInfo::lweCiphertext(ref ct) = gi.typeInfo else {
unreachable!()
};
let kid = ct.encryption.keyId;
quote! {
pub fn #method_name(mut self, key: &::tfhe::ClientKey) -> Self {
let mut key = Some(<::tfhe::ClientKey as concrete::tfhe::IntoLweSecretKey>::into_lwe_secret_key(key, Some(#kid)));
if self.#ident.is_some() {
assert_eq!(self.#ident.as_mut().unwrap().pin_mut().get_buffer(), key.as_mut().unwrap().pin_mut().get_buffer(), "Tried to set the same underlying key twice, with a different key. Something must be wrong...");
return self;
}
self.#ident = key;
self
}
}
});
quote! {
pub fn new_keyset(
secret_csprng: std::pin::Pin<&mut ::concrete::common::SecretCsprng>,
encryption_csprng: std::pin::Pin<&mut ::concrete::common::EncryptionCsprng>
) -> ::concrete::UniquePtr<::concrete::common::Keyset> {
::concrete::common::Keyset::new(
&PROGRAM_INFO.keyset,
secret_csprng,
encryption_csprng
)
#[derive(Default)]
pub struct KeysetBuilder{
#(#fields_idents: #fields_types),*
}
impl KeysetBuilder {
pub fn new() -> Self {
return Self::default();
}
#(#methods),*
pub fn generate(
self,
secret_csprng: std::pin::Pin<&mut ::concrete::common::SecretCsprng>,
encryption_csprng: std::pin::Pin<&mut ::concrete::common::EncryptionCsprng>
) -> ::concrete::UniquePtr<::concrete::common::Keyset> {
::concrete::common::Keyset::new(
&PROGRAM_INFO.keyset,
secret_csprng,
encryption_csprng,
vec![
#(
self.#fields_idents.expect(concat!("Missing tfhers key ", stringify!(#fields_idents)))
),*
]
)
}
}
}
}
pub(crate) fn generate_client(program_info: &ProgramInfo) -> proc_macro2::TokenStream {
fn generate_client(program_info: &ProgramInfo) -> proc_macro2::TokenStream {
let client_functions = program_info
.circuits
.iter()
.map(|ci| generate_client_function(ci));
.map(|ci| {
(
ci,
program_info
.tfhers_specs
.as_ref()
.unwrap()
.get_func(&ci.name)
.unwrap(),
)
})
.map(|(ci, ts)| generate_client_function(ci, Some(ts)));
quote! {
pub mod client {
#(#client_functions)*
@@ -54,87 +185,120 @@ pub(crate) fn generate_client(program_info: &ProgramInfo) -> proc_macro2::TokenS
}
}
fn generate_client_function_prepare_inputs(circuit_info: &CircuitInfo) -> proc_macro2::TokenStream {
fn generate_client_function_prepare_inputs(
circuit_info: &CircuitInfo,
tfhers_spec: Option<FunctionSpec>,
) -> proc_macro2::TokenStream {
let ith = (0..circuit_info.inputs.len()).collect::<Vec<_>>();
let input_idents = circuit_info.inputs.iter().enumerate().map(|(ith, _)| format_ident!("arg_{ith}")).collect::<Vec<_>>();
let input_types = circuit_info.inputs.iter().map(|gi| {
match (gi.rawInfo.integerPrecision, gi.rawInfo.isSigned) {
(8, true) => quote! {::concrete::common::Tensor<i8>},
(8, false) => quote! {::concrete::common::Tensor<u8>},
(16, true) => quote! {::concrete::common::Tensor<i16>},
(16, false) => quote! {::concrete::common::Tensor<u16>},
(32, true) => quote! {::concrete::common::Tensor<i32>},
(32, false) => quote! {::concrete::common::Tensor<u32>},
(64, true) => quote! {::concrete::common::Tensor<i64>},
(64, false) => quote! {::concrete::common::Tensor<u64>},
_ => unreachable!(),
}
}).collect::<Vec<_>>();
let output_types = circuit_info.inputs.iter().map(|_| {
quote! {::concrete::UniquePtr<::concrete::common::TransportValue>}
}).collect::<Vec<_>>();
quote!{
let input_specs = tfhers_spec.map_or(vec![None; circuit_info.inputs.len()], |v| {
v.input_types.to_owned()
});
let input_idents = circuit_info
.inputs
.iter()
.enumerate()
.map(|(ith, _)| format_ident!("arg_{ith}"))
.collect::<Vec<_>>();
let input_types = multizip((input_specs.iter(), circuit_info.inputs.iter()))
.map(|(spec, gate_info)| {
match (
spec,
gate_info.rawInfo.integerPrecision,
gate_info.rawInfo.isSigned,
) {
(Some(_), _, _) => quote! {()},
(_, 8, true) => quote! {::concrete::common::Tensor<i8>},
(_, 8, false) => quote! {::concrete::common::Tensor<u8>},
(_, 16, true) => quote! {::concrete::common::Tensor<i16>},
(_, 16, false) => quote! {::concrete::common::Tensor<u16>},
(_, 32, true) => quote! {::concrete::common::Tensor<i32>},
(_, 32, false) => quote! {::concrete::common::Tensor<u32>},
(_, 64, true) => quote! {::concrete::common::Tensor<i64>},
(_, 64, false) => quote! {::concrete::common::Tensor<u64>},
_ => unreachable!(),
}
})
.collect::<Vec<_>>();
let output_types = input_specs
.iter()
.map(|spec| match spec {
Some(_) => quote! {()},
None => quote! {::concrete::UniquePtr<::concrete::common::TransportValue>},
})
.collect::<Vec<_>>();
let preparations = multizip((input_specs.iter(), input_types.iter(), input_idents.iter(), ith.iter()))
.map(|(spec, typ, ident, ith)|{
match spec {
Some(..) => quote!{()},
None => quote!{self.0.pin_mut().prepare_input(<#typ as ::concrete::utils::into_value::IntoValue>::into_value(#ident), #ith)}
}
});
quote! {
pub fn prepare_inputs(&mut self, #(#input_idents: #input_types),*) -> (#(#output_types),*) {
(
#(
self.0.pin_mut().prepare_input(::concrete::common::Value::from_tensor(#input_idents), #ith)
),*
)
(#(#preparations),*)
}
}
}
fn generate_client_function_process_outputs(circuit_info: &CircuitInfo) -> proc_macro2::TokenStream {
fn generate_client_function_process_outputs(
circuit_info: &CircuitInfo,
tfhers_spec: Option<FunctionSpec>,
) -> proc_macro2::TokenStream {
let ith = (0..circuit_info.outputs.len()).collect::<Vec<_>>();
let input_idents = circuit_info.outputs.iter().enumerate().map(|(ith, _)| format_ident!("res_{ith}")).collect::<Vec<_>>();
let input_types = circuit_info.outputs.iter().map(|_| {
quote! {::concrete::UniquePtr<::concrete::common::TransportValue>}
}).collect::<Vec<_>>();
let output_types = circuit_info.outputs.iter().map(|gi| {
match (gi.rawInfo.integerPrecision, gi.rawInfo.isSigned) {
(8, true) => quote! {::concrete::common::Tensor<i8>},
(8, false) => quote! {::concrete::common::Tensor<u8>},
(16, true) => quote! {::concrete::common::Tensor<i16>},
(16, false) => quote! {::concrete::common::Tensor<u16>},
(32, true) => quote! {::concrete::common::Tensor<i32>},
(32, false) => quote! {::concrete::common::Tensor<u32>},
(64, true) => quote! {::concrete::common::Tensor<i64>},
(64, false) => quote! {::concrete::common::Tensor<u64>},
_ => unreachable!(),
}
}).collect::<Vec<_>>();
let output_unwrap = circuit_info.outputs.iter().map(|gi| {
match (gi.rawInfo.integerPrecision, gi.rawInfo.isSigned) {
(8, true) => quote! {get_tensor::<i8>().unwrap()},
(8, false) => quote! {get_tensor::<u8>().unwrap()},
(16, true) => quote! {get_tensor::<i16>().unwrap()},
(16, false) => quote! {get_tensor::<u16>().unwrap()},
(32, true) => quote! {get_tensor::<i32>().unwrap()},
(32, false) => quote! {get_tensor::<u32>().unwrap()},
(64, true) => quote! {get_tensor::<i64>().unwrap()},
(64, false) => quote! {get_tensor::<u64>().unwrap()},
_ => unreachable!(),
}
}).collect::<Vec<_>>();
quote!{
let output_specs = tfhers_spec.map_or(vec![None; circuit_info.outputs.len()], |v| {
v.output_types.to_owned()
});
let input_idents = circuit_info
.outputs
.iter()
.enumerate()
.map(|(ith, _)| format_ident!("res_{ith}"))
.collect::<Vec<_>>();
let input_types = output_specs
.iter()
.map(|spec| match spec {
Some(_) => quote! {()},
None => quote! {::concrete::UniquePtr<::concrete::common::TransportValue>},
})
.collect::<Vec<_>>();
let output_types = multizip((circuit_info.outputs.iter(), output_specs.iter()))
.map(
|(gi, ts)| match (ts, gi.rawInfo.integerPrecision, gi.rawInfo.isSigned) {
(Some(_), _, _) => quote! {()},
(_, 8, true) => quote! {::concrete::common::Tensor<i8>},
(_, 8, false) => quote! {::concrete::common::Tensor<u8>},
(_, 16, true) => quote! {::concrete::common::Tensor<i16>},
(_, 16, false) => quote! {::concrete::common::Tensor<u16>},
(_, 32, true) => quote! {::concrete::common::Tensor<i32>},
(_, 32, false) => quote! {::concrete::common::Tensor<u32>},
(_, 64, true) => quote! {::concrete::common::Tensor<i64>},
(_, 64, false) => quote! {::concrete::common::Tensor<u64>},
_ => unreachable!(),
},
)
.collect::<Vec<_>>();
let unwrappers = multizip((output_specs.iter(), output_types.iter(), input_idents.iter(), ith.iter()))
.map(|(spec, typ, ident, ith)|{
match spec {
Some(_) => quote!{()},
None => quote!{<#typ as ::concrete::utils::from_value::FromValue>::from_value((), self.0.pin_mut().process_output(#ident, #ith))},
}
});
quote! {
pub fn process_outputs(&mut self, #(#input_idents: #input_types),*) -> (#(#output_types),*) {
(
#(
self.0.pin_mut().process_output(#input_idents, #ith).#output_unwrap
),*
)
(#(#unwrappers),*)
}
}
}
fn generate_client_function(circuit_info: &CircuitInfo) -> proc_macro2::TokenStream {
fn generate_client_function(
circuit_info: &CircuitInfo,
tfhers_spec: Option<FunctionSpec>,
) -> proc_macro2::TokenStream {
let function_identifier = format_ident!("{}", circuit_info.name);
let prepare_inputs = generate_client_function_prepare_inputs(circuit_info);
let process_outputs = generate_client_function_process_outputs(circuit_info);
let prepare_inputs = generate_client_function_prepare_inputs(circuit_info, tfhers_spec.clone());
let process_outputs =
generate_client_function_process_outputs(circuit_info, tfhers_spec.clone());
quote! {
pub mod #function_identifier{
@@ -166,11 +330,22 @@ fn generate_client_function(circuit_info: &CircuitInfo) -> proc_macro2::TokenStr
}
}
pub(crate) fn generate_server(program_info: &ProgramInfo) -> proc_macro2::TokenStream {
fn generate_server(program_info: &ProgramInfo) -> proc_macro2::TokenStream {
let server_functions = program_info
.circuits
.iter()
.map(|ci| generate_server_function(ci));
.map(|ci| {
(
ci,
program_info
.tfhers_specs
.as_ref()
.unwrap()
.get_func(&ci.name)
.unwrap(),
)
})
.map(|(ci, spec)| generate_server_function(ci, Some(spec)));
quote! {
pub mod server {
#(#server_functions)*
@@ -178,11 +353,13 @@ pub(crate) fn generate_server(program_info: &ProgramInfo) -> proc_macro2::TokenS
}
}
fn generate_server_function(circuit_info: &CircuitInfo) -> proc_macro2::TokenStream {
fn generate_server_function(
circuit_info: &CircuitInfo,
tfhers_spec: Option<FunctionSpec>,
) -> proc_macro2::TokenStream {
let function_identifier = format_ident!("{}", circuit_info.name);
let binding_identifier = format_ident!("_mlir_concrete_{}", circuit_info.name);
let invoke = generate_server_function_invoke(circuit_info);
let invoke = generate_server_function_invoke(circuit_info, tfhers_spec);
quote! {
pub mod #function_identifier{
@@ -207,26 +384,150 @@ fn generate_server_function(circuit_info: &CircuitInfo) -> proc_macro2::TokenStr
}
}
}
}
fn generate_server_function_invoke(circuit_info: &CircuitInfo) -> proc_macro2::TokenStream {
let args_idents = (0..circuit_info.inputs.len()).map(|a| format_ident!("arg_{a}")).collect::<Vec<_>>();
let args_types = (0..circuit_info.inputs.len()).map(|_| quote!{::concrete::UniquePtr<::concrete::common::TransportValue>}).collect::<Vec<_>>();
let results_idents = (0..circuit_info.outputs.len()).map(|a| format_ident!("res_{a}")).collect::<Vec<_>>();
let results_types = (0..circuit_info.outputs.len()).map(|_| quote!{::concrete::UniquePtr<::concrete::common::TransportValue>}).collect::<Vec<_>>();
fn generate_types(ts: &Option<IntegerType>) -> proc_macro2::TokenStream {
match ts {
Some(IntegerType {
bit_width: 2,
is_signed: true,
..
}) => quote! {::tfhe::FheInt2},
Some(IntegerType {
bit_width: 2,
is_signed: false,
..
}) => quote! {::tfhe::FheUint2},
Some(IntegerType {
bit_width: 4,
is_signed: true,
..
}) => quote! {::tfhe::FheInt4},
Some(IntegerType {
bit_width: 4,
is_signed: false,
..
}) => quote! {::tfhe::FheUint4},
Some(IntegerType {
bit_width: 6,
is_signed: true,
..
}) => quote! {::tfhe::FheInt6},
Some(IntegerType {
bit_width: 6,
is_signed: false,
..
}) => quote! {::tfhe::FheUint6},
Some(IntegerType {
bit_width: 8,
is_signed: true,
..
}) => quote! {::tfhe::FheInt8},
Some(IntegerType {
bit_width: 8,
is_signed: false,
..
}) => quote! {::tfhe::FheUint8},
Some(IntegerType {
bit_width: 10,
is_signed: true,
..
}) => quote! {::tfhe::FheInt10},
Some(IntegerType {
bit_width: 10,
is_signed: false,
..
}) => quote! {::tfhe::FheUint10},
Some(IntegerType {
bit_width: 12,
is_signed: true,
..
}) => quote! {::tfhe::FheInt12},
Some(IntegerType {
bit_width: 12,
is_signed: false,
..
}) => quote! {::tfhe::FheUint12},
Some(IntegerType {
bit_width: 14,
is_signed: true,
..
}) => quote! {::tfhe::FheInt14},
Some(IntegerType {
bit_width: 14,
is_signed: false,
..
}) => quote! {::tfhe::FheUint14},
Some(IntegerType {
bit_width: 16,
is_signed: true,
..
}) => quote! {::tfhe::FheInt16},
Some(IntegerType {
bit_width: 16,
is_signed: false,
..
}) => quote! {::tfhe::FheUint16},
None => quote! {::concrete::UniquePtr<::concrete::common::TransportValue>},
_ => unreachable!(),
}
}
fn generate_server_function_invoke(
circuit_info: &CircuitInfo,
tfhers_spec: Option<FunctionSpec>,
) -> proc_macro2::TokenStream {
let input_specs = tfhers_spec
.clone()
.map_or(vec![None; circuit_info.inputs.len()], |v| {
v.input_types.to_owned()
});
let output_specs = tfhers_spec
.clone()
.map_or(vec![None; circuit_info.outputs.len()], |v| {
v.output_types.to_owned()
});
let args_idents = (0..circuit_info.inputs.len())
.map(|a| format_ident!("arg_{a}"))
.collect::<Vec<_>>();
let args_types = input_specs
.iter()
.map(|s| generate_types(s))
.collect::<Vec<_>>();
let results_idents = (0..circuit_info.outputs.len())
.map(|a| format_ident!("res_{a}"))
.collect::<Vec<_>>();
let results_types = output_specs
.iter()
.map(|s| generate_types(s))
.collect::<Vec<_>>();
let output_len = circuit_info.outputs.len();
quote!{
let preludes = multizip((input_specs.iter(), args_idents.iter(), args_types.iter(), circuit_info.inputs.iter()))
.map(|(spec, ident, typ, gi)| {
match spec{
Some(_) => {
let type_info_json_string = serde_json::to_string(&gi.typeInfo).unwrap();
quote!{ <#typ as ::concrete::utils::into_value::IntoValue>::into_value(#ident).into_transport_value(#type_info_json_string)}
}
None => quote!{#ident}
}
});
let postludes = multizip((output_specs.iter(), results_idents.iter(), results_types.iter()))
.map(|(spec, ident, typ)|{
match spec {
Some(s) => quote!{ <#typ as ::concrete::utils::from_value::FromValue>::from_value(#s, #ident.to_value())},
None => quote!{#ident}
}
});
quote! {
pub fn invoke(&mut self, server_keyset: &::concrete::common::ServerKeyset, #(#args_idents: #args_types),*) -> (#(#results_types),*) {
let inputs = vec![
#(#args_idents),*
];
let inputs = vec![#(#preludes),*];
let output = self.0.pin_mut().call(server_keyset, inputs);
let [#(#results_idents),*] = <[::concrete::UniquePtr<::concrete::common::TransportValue>; #output_len]>::try_from(output).unwrap();
(
#(#results_idents),*
)
(#(#postludes),*)
}
}
}

View File

@@ -1,14 +1,13 @@
#![allow(stable_features)]
#![feature(file_lock)]
#[allow(unused)]
use quote::quote;
use concrete::{compiler, protocol::ProgramInfo};
use configuration::Configuration;
use concrete::compiler;
use concrete::protocol::ProgramInfo;
use proc_macro::{
TokenStream, {self},
};
use std::{fs::read_to_string, path::PathBuf};
use std::fs::read_to_string;
use std::hash::{DefaultHasher, Hash, Hasher};
use std::path::PathBuf;
use syn::LitStr;
const CONCRETE_BUILD_DIR: &'static str = env!("CONCRETE_BUILD_DIR");
@@ -17,14 +16,14 @@ const PATH_PROGRAM_INFO: &'static str = "program_info.concrete.params.json";
const PATH_CIRCUIT: &'static str = "circuit.mlir";
const PATH_COMPOSITION_RULES: &'static str = "composition_rules.json";
const PATH_SIMULATED: &'static str = "is_simulated";
const CLIENT_SPECS: &'static str = "client.specs.json";
const PATH_CONFIGURATION: &'static str = "configuration.json";
const DEFAULT_GLOBAL_P_ERROR: Option<f64> = Some(0.00001);
const DEFAULT_P_ERROR: Option<f64> = None;
mod configuration;
mod fast_path_hasher;
mod unzip;
mod generation;
mod unzip;
#[proc_macro]
pub fn from_concrete_python_export_zip(input: TokenStream) -> TokenStream {
@@ -61,7 +60,9 @@ pub fn from_concrete_python_export_zip(input: TokenStream) -> TokenStream {
.open(concrete_build_dir.join(format!("{hash_val}.lock")))
.expect("Failed to open lock file.");
lock_file.lock().expect("Failed to acquire lock on the lock file");
lock_file
.lock()
.expect("Failed to acquire lock on the lock file");
let concrete_hash_dir = concrete_build_dir.join(format!("{hash_val}"));
if !concrete_hash_dir.exists() {
@@ -91,15 +92,25 @@ pub fn from_concrete_python_export_zip(input: TokenStream) -> TokenStream {
if !config_path.exists() {
panic!("Missing `configuration.json` file in the export. Did you save your server with the `via_mlir` option ?");
}
let configuration_string = read_to_string(config_path).expect("Failed to read configuration to string");
let conf: Configuration = serde_json::from_str(configuration_string.as_str()).expect("Failed to deserialize configuration");
let configuration_string =
read_to_string(config_path).expect("Failed to read configuration to string");
let conf: concrete::utils::configuration::Configuration =
serde_json::from_str(configuration_string.as_str())
.expect("Failed to deserialize configuration");
let client_specs_path = concrete_hash_dir.join(CLIENT_SPECS);
if !client_specs_path.exists() {
panic!("Missing `client.specs.json` file in the export. Did you save your server with the `via_mlir` option ?");
}
if !composition_rules_path.exists() {
panic!("Missing `composition_rules.json` file in the export. Did you save your server with the `via_mlir` option ?");
}
let composition_rules_string = read_to_string(composition_rules_path).expect("Failed to read composition rules to string");
let composition_rules_string = read_to_string(composition_rules_path)
.expect("Failed to read composition rules to string");
let composition_rules: Vec<serde_json::Value> =
serde_json::from_str(composition_rules_string.as_str()).expect("Failed to deserialize composition rules");
serde_json::from_str(composition_rules_string.as_str())
.expect("Failed to deserialize composition rules");
let mut opts = compiler::CompilationOptions::new();
opts.pin_mut()
@@ -133,22 +144,22 @@ pub fn from_concrete_python_export_zip(input: TokenStream) -> TokenStream {
}
match conf.parameter_selection_strategy {
configuration::ParameterSelectionStrategy::V0 => {
concrete::utils::configuration::ParameterSelectionStrategy::V0 => {
opts.pin_mut().set_optimizer_strategy(0)
}
configuration::ParameterSelectionStrategy::Mono => {
concrete::utils::configuration::ParameterSelectionStrategy::Mono => {
opts.pin_mut().set_optimizer_strategy(1)
}
configuration::ParameterSelectionStrategy::Multi => {
concrete::utils::configuration::ParameterSelectionStrategy::Multi => {
opts.pin_mut().set_optimizer_strategy(2)
}
}
match conf.multi_parameter_strategy {
configuration::MultiParameterStrategy::Precision => {
concrete::utils::configuration::MultiParameterStrategy::Precision => {
opts.pin_mut().set_optimizer_multi_parameter_strategy(0)
}
configuration::MultiParameterStrategy::PrecisionAndNorm2 => {
concrete::utils::configuration::MultiParameterStrategy::PrecisionAndNorm2 => {
opts.pin_mut().set_optimizer_multi_parameter_strategy(1)
}
}
@@ -163,8 +174,12 @@ pub fn from_concrete_python_export_zip(input: TokenStream) -> TokenStream {
opts.pin_mut()
.set_keyset_restriction(&conf.keyset_restriction.map(|a| a.0).unwrap_or("".into()));
match conf.security_level {
configuration::SecurityLevel::Security128Bits => opts.pin_mut().set_security_level(128),
configuration::SecurityLevel::Security132Bits => opts.pin_mut().set_security_level(132),
concrete::utils::configuration::SecurityLevel::Security128Bits => {
opts.pin_mut().set_security_level(128)
}
concrete::utils::configuration::SecurityLevel::Security132Bits => {
opts.pin_mut().set_security_level(132)
}
}
for rule in composition_rules {
@@ -208,40 +223,29 @@ pub fn from_concrete_python_export_zip(input: TokenStream) -> TokenStream {
let output_path = concrete_build_dir.join(format!("libconcrete-artifact-{hash_val}.a"));
if !output_path.exists() {
std::fs::copy(concrete_hash_dir.join(PATH_STATIC_LIB), output_path)
.unwrap();
std::fs::copy(concrete_hash_dir.join(PATH_STATIC_LIB), output_path).unwrap();
}
let client_specs_path = concrete_hash_dir.join(CLIENT_SPECS);
if !client_specs_path.exists() {
panic!("Missing `client.specs.json` file in the export. Did you save your server with the `via_mlir` option ?");
}
let mut client_specs: ProgramInfo =
serde_json::from_reader(std::fs::File::open(client_specs_path).unwrap()).unwrap();
client_specs.eventually_patch_tfhers_specs();
let concrete_program_info_path = concrete_hash_dir.join(PATH_PROGRAM_INFO);
if !concrete_program_info_path.exists() {
panic!("Missing `program_info.concrete.params.json` file after compilation. Something is wrong. Delete target folder and re-compile.");
}
let program_info: ProgramInfo = serde_json::from_reader(
std::fs::File::open(concrete_program_info_path).unwrap(),
)
.unwrap();
let mut program_info: ProgramInfo =
serde_json::from_reader(std::fs::File::open(concrete_program_info_path).unwrap()).unwrap();
program_info.tfhers_specs = client_specs.tfhers_specs.clone();
// assert_eq!(client_specs, program_info, "Export client specs, and compiled program info do not match. Something is wrong. Get in touch with developers.");
lock_file.unlock().unwrap();
let lib_name = format!("concrete-artifact-{hash_val}");
let unsafe_binding = generation::generate_unsafe_binding(&program_info);
let infos = generation::generate_infos(&program_info);
let keyset = generation::generate_keyset();
let client = generation::generate_client(&program_info);
let server = generation::generate_server(&program_info);
quote! {
#infos
#keyset
#client
#server
#[doc(hidden)]
pub mod _binding {
#[link(name = "ConcretelangRuntime", kind="dylib")]
#[link(name = #lib_name, kind="static")]
#unsafe_binding
}
}
.into()
generation::generate(&program_info, hash_val).into()
}

View File

@@ -28,8 +28,7 @@ pub fn unzip(zip_path: &Path, to: &Path) {
use std::os::unix::fs::PermissionsExt;
if let Some(mode) = file.unix_mode() {
std::fs::set_permissions(&outpath, std::fs::Permissions::from_mode(mode))
.unwrap();
std::fs::set_permissions(&outpath, std::fs::Permissions::from_mode(mode)).unwrap();
}
}
}

View File

@@ -18,6 +18,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
quote = {version = "1.0", optional =true }
proc-macro2 = {version = "1.0", optional = true}
tfhe = {version = "1.1", features = ["integer"], optional = true}
[build-dependencies]
zip = "2.6"
@@ -28,4 +29,6 @@ cxx-build = "1.0"
ar = "0.9"
[features]
default = ["tfhe-rs", "compiler"]
compiler = ["dep:quote", "dep:proc-macro2"]
tfhe-rs = ["dep:tfhe"]

View File

@@ -9,7 +9,7 @@ const ARTIFACTS: &[&str] = if cfg!(target_os = "macos") {
&[
"libConcreteRust.dylib",
"libConcretelangRuntime.dylib",
"libomp.dylib"
"libomp.dylib",
]
} else if cfg!(target_os = "linux") {
&[
@@ -76,14 +76,14 @@ fn copy_local_artifacts(out_dir: &Path, mut lib_dir: PathBuf) {
lib_dir.push("lib");
lib_dir = lib_dir.canonicalize().unwrap();
do_with_lock(&out_dir.join(INSTALL_LOCK), || {
for art in ARTIFACTS {
for art in ARTIFACTS {
println!("cargo::rerun-if-changed={}", &lib_dir.join(art).display());
std::fs::copy(&lib_dir.join(art), &out_dir.join(art)).unwrap();
}
});
}
fn fetch_artifacts(out_dir: &Path) {
struct Archive(Vec<u8>);
impl Handler for Archive {
fn write(&mut self, data: &[u8]) -> Result<usize, WriteError> {

View File

@@ -19,6 +19,7 @@
#include "concretelang/Support/V0Parameters.h"
#include "cxx.h"
#include <cstddef>
#include <iostream>
#include <memory>
#include <ostream>
#include <sys/types.h>
@@ -176,6 +177,16 @@ std::unique_ptr<Library> compile(rust::Str sources,
}
template <typename T> struct Key : T {
static std::unique_ptr<Key<T>> _from_buffer_and_info(rust::Slice<const uint64_t> buffer_slice, rust::Str info_json) {
auto info = typename T::InfoType();
auto info_string = std::string(info_json);
assert(info.readJsonFromString(info_string).has_value());
auto buffer = std::make_shared<std::vector<uint64_t>>(buffer_slice.begin(), buffer_slice.end());
auto output =std::make_unique<T>(buffer, info);
return std::unique_ptr<Key<T>>(reinterpret_cast<Key<T> *>(output.release()));
}
rust::Slice<const uint64_t> get_buffer() {
auto buffer = this->getBuffer();
return {buffer.data(), buffer.size()};
@@ -186,6 +197,12 @@ template <typename T> struct Key : T {
};
typedef Key<concretelang::keys::LweSecretKey> LweSecretKey;
inline std::unique_ptr<LweSecretKey>
_lwe_secret_key_from_buffer_and_info(rust::Slice<const uint64_t> buffer_slice, rust::Str info_json) {
return LweSecretKey::_from_buffer_and_info(buffer_slice, info_json);
}
typedef Key<concretelang::keys::LweBootstrapKey> LweBootstrapKey;
typedef Key<concretelang::keys::LweKeyswitchKey> LweKeyswitchKey;
typedef Key<concretelang::keys::PackingKeyswitchKey> PackingKeyswitchKey;
@@ -301,11 +318,17 @@ struct Keyset : concretelang::keysets::Keyset {
std::unique_ptr<Keyset> _keyset_new(rust::Str keyset_info,
SecretCsprng &secret_csprng,
EncryptionCsprng &encryption_csprng) {
EncryptionCsprng &encryption_csprng,
rust::Slice<std::unique_ptr<LweSecretKey>> initial_keys) {
auto info = Message<concreteprotocol::KeysetInfo>();
info.readJsonFromString(std::string(keyset_info)).value();
auto map = std::map<uint32_t, concretelang::keys::LweSecretKey>();
for (auto &key : initial_keys) {
auto info = key->getInfo();
map.insert(std::make_pair(info.asReader().getId(), std::move(*key.release())));
}
auto output = std::make_unique<concretelang::keysets::Keyset>(
info, secret_csprng, encryption_csprng);
info, secret_csprng, encryption_csprng, map);
return std::unique_ptr<Keyset>(reinterpret_cast<Keyset *>(output.release()));
}
@@ -345,6 +368,24 @@ const auto _tensor_i32_new = _tensor_new<int32_t, TensorI32>;
const auto _tensor_u64_new = _tensor_new<uint64_t, TensorU64>;
const auto _tensor_i64_new = _tensor_new<int64_t, TensorI64>;
struct TransportValue : concretelang::values::TransportValue {
std::unique_ptr<TransportValue> to_owned() const {
return std::make_unique<TransportValue>(*this);
}
rust::Vec<uint8_t> serialize() const {
auto output = rust::Vec<uint8_t>();
auto vec_ostream = VecOStream(output);
auto ostream = std::ostream(&vec_ostream);
this->writeBinaryToOstream(
ostream
).value();
ostream.flush();
return output;
}
};
struct Value : concretelang::values::Value {
bool _has_element_type_u8() const { return hasElementType<uint8_t>(); }
bool _has_element_type_i8() const { return hasElementType<int8_t>(); }
@@ -403,9 +444,19 @@ struct Value : concretelang::values::Value {
}
rust::Slice<const size_t> get_dimensions() const {
auto vecref = getDimensions();
const auto& vecref = getDimensions();
return {vecref.data(), vecref.size()};
}
std::unique_ptr<TransportValue> into_transport_value(rust::Str type_info_json) const {
auto first = intoRawTransportValue();
auto info = Message<concreteprotocol::TypeInfo>();
info.readJsonFromString(std::string(type_info_json)).value();
first.asBuilder().setTypeInfo(info.asReader());
auto output =
std::make_unique<::concretelang::values::TransportValue>(first);
return std::unique_ptr<TransportValue>(reinterpret_cast<TransportValue *>(output.release()));
}
};
template <typename T>
@@ -423,23 +474,6 @@ const auto _value_from_tensor_i32 = _value_from_tensor<TensorI32>;
const auto _value_from_tensor_u64 = _value_from_tensor<TensorU64>;
const auto _value_from_tensor_i64 = _value_from_tensor<TensorI64>;
struct TransportValue : concretelang::values::TransportValue {
std::unique_ptr<TransportValue> to_owned() const {
return std::make_unique<TransportValue>(*this);
}
rust::Vec<uint8_t> serialize() const {
auto output = rust::Vec<uint8_t>();
auto vec_ostream = VecOStream(output);
auto ostream = std::ostream(&vec_ostream);
this->writeBinaryToOstream(
ostream
).value();
ostream.flush();
return output;
}
};
std::unique_ptr<TransportValue> _deserialize_transport_value(rust::Slice<const uint8_t> slice) {
auto output = TransportValue();
auto slice_istream = SliceIStream(slice);
@@ -448,6 +482,12 @@ std::unique_ptr<TransportValue> _deserialize_transport_value(rust::Slice<const u
return std::make_unique<TransportValue>(output);
}
std::unique_ptr<Value> _transport_value_to_value(TransportValue const &tv) {
auto output =
std::make_unique<::concretelang::values::Value>(::concretelang::values::Value::fromRawTransportValue(tv));
return std::unique_ptr<Value>(reinterpret_cast<Value *>(output.release()));
}
struct ClientFunction : concretelang::clientlib::ClientCircuit {
std::unique_ptr<TransportValue> prepare_input(std::unique_ptr<Value> arg,
size_t pos) {
@@ -543,7 +583,14 @@ struct ServerFunction : concretelang::serverlib::ServerCircuit {
for (size_t i = 0; i < args.length(); i++) {
oargs.push_back(*args[i].release());
}
auto res = std::make_unique<std::vector<::concretelang::values::TransportValue>>(call(keys, oargs).value());
auto maybe_res = call(keys, oargs);
if (maybe_res.has_error()){
std::cout << "Failed to perform call:\n";
std::cout << maybe_res.error().mesg;
std::cout.flush();
assert(false);
}
auto res = std::make_unique<std::vector<::concretelang::values::TransportValue>>(maybe_res.value());
return std::unique_ptr<std::vector<TransportValue>>(
reinterpret_cast<std::vector<TransportValue> *>(res.release()));
}

View File

@@ -8,6 +8,7 @@ use crate::protocol::{
CircuitInfo, KeysetInfo, LweBootstrapKeyInfo, LweKeyswitchKeyInfo, LweSecretKeyInfo,
PackingKeyswitchKeyInfo, ProgramInfo,
};
use crate::utils::into_value::IntoValue;
use cxx::{CxxVector, SharedPtr, UniquePtr};
#[cxx::bridge(namespace = "concrete_rust")]
@@ -144,6 +145,11 @@ mod ffi {
fn get_buffer(self: Pin<&mut LweSecretKey>) -> &[u64];
#[doc(hidden)]
fn _get_info_json(self: &LweSecretKey) -> String;
#[doc(hidden)]
fn _lwe_secret_key_from_buffer_and_info(
buffer: &[u64],
info: &str,
) -> UniquePtr<LweSecretKey>;
/// A Keyset object holding both the [`ClientKeyset`] and the [`ServerKeyset`].
type Keyset;
@@ -152,6 +158,7 @@ mod ffi {
keyset_info_json: &str,
secret_csprng: Pin<&mut SecretCsprng>,
encryption_csprng: Pin<&mut EncryptionCsprng>,
initial_keys: &mut [UniquePtr<LweSecretKey>],
) -> UniquePtr<Keyset>;
/// Return the associated server keyset.
fn get_server(self: &Keyset) -> UniquePtr<ServerKeyset>;
@@ -319,6 +326,7 @@ mod ffi {
#[doc(hidden)]
fn _get_tensor_i64(self: &Value) -> UniquePtr<TensorI64>;
fn get_dimensions(self: &Value) -> &[usize];
fn into_transport_value(self: &Value, type_info_json: &str) -> UniquePtr<TransportValue>;
/// A serialized value which can be transported between the server and the client.
type TransportValue;
@@ -328,6 +336,8 @@ mod ffi {
fn serialize(self: &TransportValue) -> Vec<u8>;
#[doc(hidden)]
fn _deserialize_transport_value(bytes: &[u8]) -> UniquePtr<TransportValue>;
#[doc(hidden)]
fn _transport_value_to_value(tv: &TransportValue) -> UniquePtr<Value>;
// ------------------------------------------------------------------------------------------- Client
@@ -429,6 +439,7 @@ mod ffi {
}
pub use ffi::*;
use serde_json::map::IntoValues;
impl ServerKeyset {
/// Deserialize a server keyset from bytes.
@@ -449,6 +460,10 @@ impl TransportValue {
pub fn deserialize(bytes: &[u8]) -> UniquePtr<TransportValue> {
_deserialize_transport_value(bytes)
}
pub fn to_value(&self) -> UniquePtr<Value> {
_transport_value_to_value(self)
}
}
impl ServerFunction {
@@ -1062,11 +1077,13 @@ impl Keyset {
keyset_info: &KeysetInfo,
secret_csprng: Pin<&mut SecretCsprng>,
encryption_csprng: Pin<&mut EncryptionCsprng>,
mut initial_keys: Vec<UniquePtr<LweSecretKey>>,
) -> UniquePtr<Keyset> {
_keyset_new(
&serde_json::to_string(keyset_info).unwrap(),
secret_csprng,
encryption_csprng,
initial_keys.as_mut_slice(),
)
}
}

View File

@@ -1,7 +1,9 @@
pub use cxx::{UniquePtr, SharedPtr};
pub use cxx::{SharedPtr, UniquePtr};
pub use ffi::c_void;
mod ffi;
#[cfg(feature = "tfhe-rs")]
pub mod tfhe;
#[cfg(feature = "compiler")]
#[doc(hidden)]
@@ -25,3 +27,6 @@ pub mod server {
}
pub mod protocol;
#[doc(hidden)]
pub mod utils;

View File

@@ -1,14 +1,50 @@
#![allow(non_camel_case_types, non_snake_case, unused)]
use crate::tfhe::ModuleSpec;
use serde::{Deserialize, Serialize};
/// A complete program can be described by the ensemble of circuit signatures, and the description
/// of the keyset that go with it. This structure regroup those informations.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct ProgramInfo {
/// The informations on the keyset of the program.
pub keyset: KeysetInfo,
/// The informations for the different circuits of the program.
pub circuits: Vec<CircuitInfo>,
/// The tfhers spec.
pub tfhers_specs: Option<ModuleSpec>,
}
impl ProgramInfo {
// Generates a `tfhers_specs` field from the circuits informations.
//
// If the `tfhers_specs` field is not available, it means that no tfhers interoperrability is needed.
// We can generate a dummy `tfhers_specs` field to make further use of the program info object simpler.
pub fn eventually_patch_tfhers_specs(&mut self) {
if self.tfhers_specs.is_none() {
self.tfhers_specs = Some(ModuleSpec {
input_types_per_func: self
.circuits
.iter()
.map(|c| (c.name.clone(), vec![None; c.inputs.len()]))
.collect(),
output_types_per_func: self
.circuits
.iter()
.map(|c| (c.name.clone(), vec![None; c.outputs.len()]))
.collect(),
input_shapes_per_func: self
.circuits
.iter()
.map(|c| (c.name.clone(), vec![None; c.inputs.len()]))
.collect(),
output_shapes_per_func: self
.circuits
.iter()
.map(|c| (c.name.clone(), vec![None; c.outputs.len()]))
.collect(),
});
}
}
}
/// A circuit signature can be described completely by the type informations for its input and
@@ -17,7 +53,7 @@ pub struct ProgramInfo {
/// Note:
/// The order of the input and output lists matters. The order of values should be the same when
/// executing the circuit. Also, the name is expected to be unique in the program.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct CircuitInfo {
/// The ordered list of input types.
pub inputs: Vec<GateInfo>,
@@ -29,7 +65,7 @@ pub struct CircuitInfo {
/// A value flowing in or out of a circuit is expected to be of a given type, according to the
/// signature of this circuit. This structure represents such a type in a circuit signature.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct GateInfo {
/// The raw information that raw data must be possible to parse with.
pub rawInfo: RawInfo,
@@ -42,7 +78,7 @@ pub struct GateInfo {
/// tensor of proper shape, signedness and precision before being pre-processed and passed to the
/// computation. This structure represents the informations needed to parse this payload into the
/// expected tensor.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct RawInfo {
/// The shape of the tensor.
pub shape: Shape,
@@ -57,14 +93,14 @@ pub struct RawInfo {
///
/// Note:
/// If the dimensions vector is empty, the message is interpreted as a scalar.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct Shape {
/// The dimensions of the value.
pub dimensions: Vec<u32>,
}
/// The different possible type of values.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub enum TypeInfo {
lweCiphertext(LweCiphertextTypeInfo),
plaintext(PlaintextTypeInfo),
@@ -73,7 +109,7 @@ pub enum TypeInfo {
/// A plaintext value can flow in and out of a circuit. This structure represents the informations
/// needed to verify and pre-or-post process this value.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct PlaintextTypeInfo {
/// The shape of the value.
pub shape: Shape,
@@ -85,7 +121,7 @@ pub struct PlaintextTypeInfo {
/// A plaintext value can flow in and out of a circuit. This structure represents the informations
/// needed to verify and pre-or-post process this value.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct IndexTypeInfo {
/// The shape of the value.
pub shape: Shape,
@@ -103,7 +139,7 @@ pub struct IndexTypeInfo {
/// would have if the values were cleartext. That is, it does not take into account the encryption
/// process. The concrete shape is the final shape of the object accounting for the encryption,
/// that usually add one or more dimension to the object.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct LweCiphertextTypeInfo {
/// The abstract shape of the value.
pub abstractShape: Shape,
@@ -121,7 +157,7 @@ pub struct LweCiphertextTypeInfo {
/// The encryption of a cleartext value requires some parameters to operate. This structure
/// represents those parameters.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct LweCiphertextEncryptionInfo {
/// The identifier of the secret key used to perform the encryption.
pub keyId: u32,
@@ -136,7 +172,7 @@ pub struct LweCiphertextEncryptionInfo {
///
/// Note:
/// Not all compressions are available for every types of evaluation keys or ciphertexts.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub enum Compression {
none,
seed,
@@ -144,7 +180,7 @@ pub enum Compression {
}
/// The encoding of the value stored inside the ciphertext.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub enum LweCiphretextTypeInfo_Encoding {
integer(IntegerCiphertextEncodingInfo),
boolean(BooleanCiphertextEncodingInfo),
@@ -152,7 +188,7 @@ pub enum LweCiphretextTypeInfo_Encoding {
/// A ciphertext can be used to represent an integer value. This structure represents the
/// informations needed to encode such an integer.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct IntegerCiphertextEncodingInfo {
/// The bitwidth of the encoded integer.
pub width: u32,
@@ -163,7 +199,7 @@ pub struct IntegerCiphertextEncodingInfo {
}
/// The mode used to encode the integer.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub enum IntegerCiphertextEncodingInfo_Mode {
native(IntegerCiphertextEncodingInfo_Mode_NativeMode),
chunked(IntegerCiphertextEncodingInfo_Mode_ChunkedMode),
@@ -173,12 +209,12 @@ pub enum IntegerCiphertextEncodingInfo_Mode {
/// An integer of width from 1 to 8 bits can be encoded in a single ciphertext natively, by
/// being shifted in the most significant bits. This structure represents this integer encoding
/// mode.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct IntegerCiphertextEncodingInfo_Mode_NativeMode {}
/// An integer of width from 1 to n can be encoded in a set of ciphertexts by chunking the bits
/// of the original integer. This structure represents this integer encoding mode.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct IntegerCiphertextEncodingInfo_Mode_ChunkedMode {
/// The number of chunks to be used.
pub size: u32,
@@ -188,7 +224,7 @@ pub struct IntegerCiphertextEncodingInfo_Mode_ChunkedMode {
/// An integer of width 1 to 16 can be encoded in a set of ciphertexts, by decomposing a value
/// using a set of pairwise coprimes. This structure represents this integer encoding mode.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct IntegerCiphertextEncodingInfo_Mode_CrtMode {
/// The coprimes used to decompose the original value.
pub moduli: Vec<u32>,
@@ -196,12 +232,12 @@ pub struct IntegerCiphertextEncodingInfo_Mode_CrtMode {
/// A ciphertext can be used to represent a boolean value. This structure represents such an
/// encoding.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct BooleanCiphertextEncodingInfo {}
/// Secret Keys can be drawn from different ranges of values, using different distributions. This
/// enumeration encodes the different supported ways.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub enum KeyType {
binary = 0,
ternary = 1,
@@ -209,14 +245,14 @@ pub enum KeyType {
/// Ciphertext operations are performed using modular arithmetic. Depending on the use, different
/// modulus can be used for the operations. This structure encodes the different supported ways.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct Modulus {
/// The modulus expected to be used.
pub modulus: Modulus_enum,
}
/// The modulus expected to be used.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub enum Modulus_enum {
native(NativeModulus),
powerOfTwo(PowerOfTwoModulus),
@@ -231,14 +267,14 @@ pub enum Modulus_enum {
///
/// Example:
/// 2^64 when the ciphertext is stored using 64 bits integers.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct NativeModulus {}
/// Operations are performed using a modulus that is a power of two.
///
/// Example:
/// 2^n for any n between 0 and the bitwidth of the integer used to store the ciphertext.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct PowerOfTwoModulus {
/// The power used to raise 2.
pub power: u32,
@@ -249,7 +285,7 @@ pub struct PowerOfTwoModulus {
/// Example:
/// n for any n between 0 and 2^N where N is the bitwidth of the integer used to store the
/// ciphertext.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct IntegerModulus {
/// The value used as modulus.
pub modulus: u32,
@@ -261,7 +297,7 @@ pub struct IntegerModulus {
/// Note:
/// Secret keys with same parameters are allowed to co-exist in a program, as long as they
/// have different ids.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct LweSecretKeyInfo {
/// The identifier of the key.
pub id: u32,
@@ -271,7 +307,7 @@ pub struct LweSecretKeyInfo {
/// A secret key is parameterized by a few quantities of cryptographic importance. This structure
/// represents those parameters.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct LweSecretKeyParams {
/// The LWE dimension, e.g. the length of the key.
pub lweDimension: u32,
@@ -287,7 +323,7 @@ pub struct LweSecretKeyParams {
/// Note:
/// Keyswitch keys with same parameters, compression, input and output id, are allowed to co-exist
/// in a program as long as they have different ids.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct LweKeyswitchKeyInfo {
/// The identifier of the keyswitch key.
pub id: u32,
@@ -306,7 +342,7 @@ pub struct LweKeyswitchKeyInfo {
///
/// Note:
/// For now, only keys with the same input and output key types can be represented.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct LweKeyswitchKeyParams {
/// The number of levels of the ciphertexts.
pub levelCount: u32,
@@ -333,7 +369,7 @@ pub struct LweKeyswitchKeyParams {
/// Note:
/// Packing keyswitch keys with same parameters, compression, input and output id, are allowed to
/// co-exist in a program as long as they have different ids.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct PackingKeyswitchKeyInfo {
/// The identifier of the packing keyswitch key.
pub id: u32,
@@ -352,7 +388,7 @@ pub struct PackingKeyswitchKeyInfo {
///
/// Note:
/// For now, only keys with the same input and output key types can be represented.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct PackingKeyswitchKeyParams {
/// The number of levels of the ciphertexts.
pub levelCount: u32,
@@ -382,7 +418,7 @@ pub struct PackingKeyswitchKeyParams {
/// Note:
/// Bootstrap keys with same parameters, compression, input and output id, are allowed to co-exist
/// in a program as long as they have different ids.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct LweBootstrapKeyInfo {
/// The identifier of the bootstrap key.
pub id: u32,
@@ -401,7 +437,7 @@ pub struct LweBootstrapKeyInfo {
///
/// Note:
/// For now, only keys with the same input and output key types can be represented.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct LweBootstrapKeyParams {
/// The number of levels of the ciphertexts.
pub levelCount: u32,
@@ -425,7 +461,7 @@ pub struct LweBootstrapKeyParams {
/// The keyset needed for an application can be described by an ensemble of descriptions of the
/// different keys used in the program. This structure represents such a description.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct KeysetInfo {
/// The secret key descriptions.
pub lweSecretKeys: Vec<LweSecretKeyInfo>,
@@ -455,10 +491,15 @@ mod to_tokens {
.iter()
.map(|circuit| quote! { #circuit })
.collect::<Vec<_>>();
let tfhers_specs = match &self.tfhers_specs {
Some(s) => quote! {Some(#s)},
None => quote! {None},
};
tokens.extend(quote! {
::concrete::protocol::ProgramInfo {
keyset: #keyset,
circuits: vec![#(#circuits),*],
tfhers_specs: #tfhers_specs
}
});
}
@@ -975,3 +1016,16 @@ mod to_tokens {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_deserialize_program_info() {
let string = r#"
{"keyset": {"lweSecretKeys": [{"id": 0, "params": {"lweDimension": 2048, "integerPrecision": 64, "keyType": "binary"}}, {"id": 1, "params": {"lweDimension": 4096, "integerPrecision": 64, "keyType": "binary"}}, {"id": 2, "params": {"lweDimension": 776, "integerPrecision": 64, "keyType": "binary"}}, {"id": 3, "params": {"lweDimension": 626, "integerPrecision": 64, "keyType": "binary"}}, {"id": 4, "params": {"lweDimension": 2048, "integerPrecision": 64, "keyType": "binary"}}], "lweBootstrapKeys": [{"id": 0, "inputId": 2, "outputId": 0, "params": {"levelCount": 2, "baseLog": 15, "glweDimension": 2, "polynomialSize": 1024, "variance": 8.442253112932959e-31, "integerPrecision": 64, "modulus": {"modulus": {"native": {}}}, "keyType": "binary", "inputLweDimension": 776}, "compression": "none"}, {"id": 1, "inputId": 3, "outputId": 4, "params": {"levelCount": 11, "baseLog": 4, "glweDimension": 4, "polynomialSize": 512, "variance": 8.442253112932959e-31, "integerPrecision": 64, "modulus": {"modulus": {"native": {}}}, "keyType": "binary", "inputLweDimension": 626}, "compression": "none"}], "lweKeyswitchKeys": [{"id": 0, "inputId": 1, "outputId": 2, "params": {"levelCount": 5, "baseLog": 3, "variance": 4.0324907628621766e-11, "integerPrecision": 64, "modulus": {"modulus": {"native": {}}}, "keyType": "binary", "inputLweDimension": 4096, "outputLweDimension": 776}, "compression": "none"}, {"id": 1, "inputId": 0, "outputId": 1, "params": {"levelCount": 1, "baseLog": 31, "variance": 4.70197740328915e-38, "integerPrecision": 64, "modulus": {"modulus": {"native": {}}}, "keyType": "binary", "inputLweDimension": 2048, "outputLweDimension": 4096}, "compression": "none"}, {"id": 2, "inputId": 1, "outputId": 3, "params": {"levelCount": 5, "baseLog": 2, "variance": 8.437693323536307e-09, "integerPrecision": 64, "modulus": {"modulus": {"native": {}}}, "keyType": "binary", "inputLweDimension": 4096, "outputLweDimension": 626}, "compression": "none"}, {"id": 3, "inputId": 4, "outputId": 1, "params": {"levelCount": 2, "baseLog": 21, "variance": 4.70197740328915e-38, "integerPrecision": 64, "modulus": {"modulus": {"native": {}}}, "keyType": "binary", "inputLweDimension": 2048, "outputLweDimension": 4096}, "compression": "none"}], "packingKeyswitchKeys": []}, "circuits": [{"inputs": [{"rawInfo": {"shape": {"dimensions": [8, 4097]}, "integerPrecision": 64, "isSigned": false}, "typeInfo": {"lweCiphertext": {"abstractShape": {"dimensions": [8]}, "concreteShape": {"dimensions": [8, 4097]}, "integerPrecision": 64, "encryption": {"keyId": 1, "variance": 4.70197740328915e-38, "lweDimension": 4096, "modulus": {"modulus": {"native": {}}}}, "compression": "none", "encoding": {"integer": {"width": 4, "isSigned": false, "mode": {"native": {}}}}}}}, {"rawInfo": {"shape": {"dimensions": [8, 4097]}, "integerPrecision": 64, "isSigned": false}, "typeInfo": {"lweCiphertext": {"abstractShape": {"dimensions": [8]}, "concreteShape": {"dimensions": [8, 4097]}, "integerPrecision": 64, "encryption": {"keyId": 1, "variance": 4.70197740328915e-38, "lweDimension": 4096, "modulus": {"modulus": {"native": {}}}}, "compression": "none", "encoding": {"integer": {"width": 4, "isSigned": false, "mode": {"native": {}}}}}}}], "outputs": [{"rawInfo": {"shape": {"dimensions": [8, 4097]}, "integerPrecision": 64, "isSigned": false}, "typeInfo": {"lweCiphertext": {"abstractShape": {"dimensions": [8]}, "concreteShape": {"dimensions": [8, 4097]}, "integerPrecision": 64, "encryption": {"keyId": 1, "variance": 4.70197740328915e-38, "lweDimension": 4096, "modulus": {"modulus": {"native": {}}}}, "compression": "none", "encoding": {"integer": {"width": 4, "isSigned": false, "mode": {"native": {}}}}}}}], "name": "my_func"}], "tfhers_specs": {"input_types_per_func": {"my_func": [{"is_signed": false, "bit_width": 16, "carry_width": 2, "msg_width": 2, "params": {"lwe_dimension": 909, "glwe_dimension": 1, "polynomial_size": 4096, "pbs_base_log": 15, "pbs_level": 2, "lwe_noise_distribution": 0, "glwe_noise_distribution": 2.168404344971009e-19, "encryption_key_choice": 0}}, {"is_signed": false, "bit_width": 16, "carry_width": 2, "msg_width": 2, "params": {"lwe_dimension": 909, "glwe_dimension": 1, "polynomial_size": 4096, "pbs_base_log": 15, "pbs_level": 2, "lwe_noise_distribution": 0, "glwe_noise_distribution": 2.168404344971009e-19, "encryption_key_choice": 0}}]}, "output_types_per_func": {"my_func": [{"is_signed": false, "bit_width": 16, "carry_width": 2, "msg_width": 2, "params": {"lwe_dimension": 909, "glwe_dimension": 1, "polynomial_size": 4096, "pbs_base_log": 15, "pbs_level": 2, "lwe_noise_distribution": 0, "glwe_noise_distribution": 2.168404344971009e-19, "encryption_key_choice": 0}}]}, "input_shapes_per_func": {"my_func": [[], []]}, "output_shapes_per_func": {"my_func": [[]]}}}
"#;
let val: ProgramInfo = serde_json::from_str(string).unwrap();
}
}

View File

@@ -0,0 +1,65 @@
use super::{EncryptionKeyChoice, IntegerType};
use crate::ffi::Value;
use crate::utils::from_value::FromValue;
use cxx::UniquePtr;
use tfhe::core_crypto::prelude::LweCiphertext;
use tfhe::integer::ciphertext::{DataKind, Expandable};
use tfhe::shortint::parameters::{Degree, NoiseLevel};
use tfhe::shortint::{CarryModulus, Ciphertext, CiphertextModulus, MessageModulus, PBSOrder, AtomicPatternKind};
use tfhe::{
FheInt10, FheInt12, FheInt14, FheInt16, FheInt2, FheInt4, FheInt6, FheInt8, FheUint10,
FheUint12, FheUint14, FheUint16, FheUint2, FheUint4, FheUint6, FheUint8,
};
macro_rules! impl_from_value_integer {
($ty:ty, $datakind:expr) => {
impl FromValue for $ty {
type Spec = IntegerType;
fn from_value(s: Self::Spec, v: UniquePtr<Value>) -> Self {
let lwe_size = s.params.polynomial_size + 1;
let vals = v.get_tensor::<u64>().unwrap();
let cts = (0..s.n_cts())
.map(|i| {
Ciphertext::new(
LweCiphertext::from_container(
vals.values()[i * lwe_size..(i + 1) * lwe_size].to_vec(),
CiphertextModulus::new_native(),
),
Degree::new(2u64.pow(s.msg_width as u32) - 1),
NoiseLevel::UNKNOWN,
MessageModulus(2u64.pow(s.msg_width as u32)),
CarryModulus(2u64.pow(s.carry_width as u32)),
match s.params.encryption_key_choice {
EncryptionKeyChoice::BIG => {
AtomicPatternKind::Standard(PBSOrder::KeyswitchBootstrap)
}
EncryptionKeyChoice::SMALL => {
AtomicPatternKind::Standard(PBSOrder::BootstrapKeyswitch)
}
},
)
})
.collect();
<$ty>::from_expanded_blocks(cts, $datakind).unwrap()
}
}
};
}
impl_from_value_integer!(FheUint2, DataKind::Unsigned(2));
impl_from_value_integer!(FheUint4, DataKind::Unsigned(4));
impl_from_value_integer!(FheUint6, DataKind::Unsigned(6));
impl_from_value_integer!(FheUint8, DataKind::Unsigned(8));
impl_from_value_integer!(FheUint10, DataKind::Unsigned(10));
impl_from_value_integer!(FheUint12, DataKind::Unsigned(12));
impl_from_value_integer!(FheUint14, DataKind::Unsigned(14));
impl_from_value_integer!(FheUint16, DataKind::Unsigned(16));
impl_from_value_integer!(FheInt2, DataKind::Signed(2));
impl_from_value_integer!(FheInt4, DataKind::Signed(4));
impl_from_value_integer!(FheInt6, DataKind::Signed(6));
impl_from_value_integer!(FheInt8, DataKind::Signed(8));
impl_from_value_integer!(FheInt10, DataKind::Signed(10));
impl_from_value_integer!(FheInt12, DataKind::Signed(12));
impl_from_value_integer!(FheInt14, DataKind::Signed(14));
impl_from_value_integer!(FheInt16, DataKind::Signed(16));

View File

@@ -0,0 +1,30 @@
use crate::ffi::LweSecretKey;
use crate::protocol::{KeyType, LweSecretKeyInfo, LweSecretKeyParams};
use cxx::UniquePtr;
use tfhe::ClientKey;
pub trait IntoLweSecretKey {
fn into_lwe_secret_key(&self, id: Option<u32>) -> UniquePtr<LweSecretKey>;
}
impl IntoLweSecretKey for ClientKey {
fn into_lwe_secret_key(&self, id: Option<u32>) -> cxx::UniquePtr<crate::ffi::LweSecretKey> {
let (integer_ck, _, _, _, _) = self.clone().into_raw_parts();
let shortint_ck = integer_ck.into_raw_parts();
let (glwe_secret_key, _, _) = shortint_ck.into_raw_parts();
let lwe_secret_key = glwe_secret_key.into_lwe_secret_key();
let buffer = lwe_secret_key.as_view().into_container();
let info = LweSecretKeyInfo {
id: id.unwrap_or(0),
params: LweSecretKeyParams {
lweDimension: buffer.len() as u32,
integerPrecision: 64,
keyType: KeyType::binary,
},
};
crate::ffi::_lwe_secret_key_from_buffer_and_info(
buffer,
&serde_json::to_string(&info).unwrap(),
)
}
}

View File

@@ -0,0 +1,74 @@
use crate::ffi::{Tensor, Value};
use crate::utils::into_value::IntoValue;
use cxx::UniquePtr;
use std::ptr::copy_nonoverlapping;
use tfhe::integer::IntegerCiphertext;
use tfhe::{
FheInt10, FheInt12, FheInt14, FheInt16, FheInt2, FheInt4, FheInt6, FheInt8, FheUint10,
FheUint12, FheUint14, FheUint16, FheUint2, FheUint4, FheUint6, FheUint8,
};
macro_rules! impl_into_value {
($($type:ty),*) => {
$(
impl IntoValue for $type {
fn into_value(self) -> UniquePtr<Value> {
let (radix, _, _) = self.into_raw_parts();
let n_cts = radix.blocks().len();
let lwe_size = radix.blocks()[0].ct.lwe_size().0;
let mut vals: Vec<u64> = Vec::with_capacity(n_cts * lwe_size);
// SAFETY: We are setting the length of the vector to match its capacity.
// This is safe because the vector was allocated with exactly `n_cts * lwe_size` capacity,
// and we will initialize all elements before using them.
unsafe { vals.set_len(n_cts * lwe_size) };
for (i, block) in radix.blocks().iter().enumerate() {
unsafe {
// SAFETY:
// 1. `block.ct.as_view().into_container().as_ptr()` points to valid memory
// because `block.ct` is a valid ciphertext.
// 2. `vals.as_mut_ptr().add(i * lwe_size)` points to a valid, non-overlapping
// region of memory within the allocated vector because `vals` was allocated
// with sufficient capacity.
// 3. `lwe_size` elements are copied, which matches the size of the source and
// destination regions.
// 4. `block` and `vals` are non overlapping, because `block` is allocated before
// the function, and `vals` is allocated inside the scope of the function.
copy_nonoverlapping(
block.ct.as_view().into_container().as_ptr(),
vals.as_mut_ptr().add(i * lwe_size),
lwe_size,
);
}
}
Value::from_tensor(Tensor::<u64>::new(vals, vec![n_cts, lwe_size]))
}
}
)*
};
}
impl_into_value!(
FheInt2, FheInt4, FheInt6, FheInt8, FheInt10, FheInt12, FheInt14, FheInt16, FheUint2, FheUint4,
FheUint6, FheUint8, FheUint10, FheUint12, FheUint14, FheUint16
);
#[cfg(test)]
mod tests {
use super::*;
use tfhe::prelude::*;
use tfhe::{generate_keys, ConfigBuilder, FheUint8};
#[test]
fn test_into_value() {
let config = ConfigBuilder::default().build();
let (client_key, _) = generate_keys(config);
let clear_a = 27u8;
let a = FheUint8::encrypt(clear_a, &client_key);
let val = a.into_value();
assert!(val._has_element_type_u64());
assert_eq!(val.get_dimensions(), [4, 2049]);
}
}

View File

@@ -0,0 +1,11 @@
mod from_value;
mod into_value;
mod into_key;
pub use into_key::*;
mod types;
pub use types::*;
mod spec;
pub use spec::*;

View File

@@ -0,0 +1,189 @@
use super::IntegerType;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct ModuleSpec {
pub input_types_per_func: HashMap<String, Vec<Option<IntegerType>>>,
pub output_types_per_func: HashMap<String, Vec<Option<IntegerType>>>,
pub input_shapes_per_func: HashMap<String, Vec<Option<Vec<usize>>>>,
pub output_shapes_per_func: HashMap<String, Vec<Option<Vec<usize>>>>,
}
impl ModuleSpec {
pub fn get_func(&self, name: &str) -> Option<FunctionSpec> {
if !self.input_types_per_func.contains_key(name) {
return None;
}
Some(FunctionSpec {
input_types: self.input_types_per_func.get(name).unwrap(),
output_types: self.output_types_per_func.get(name).unwrap(),
input_shapes: self.input_shapes_per_func.get(name).unwrap(),
output_shapes: self.output_shapes_per_func.get(name).unwrap(),
})
}
}
#[derive(Debug, Clone)]
pub struct FunctionSpec<'a> {
pub input_types: &'a Vec<Option<IntegerType>>,
pub output_types: &'a Vec<Option<IntegerType>>,
pub input_shapes: &'a Vec<Option<Vec<usize>>>,
pub output_shapes: &'a Vec<Option<Vec<usize>>>,
}
#[cfg(feature = "compiler")]
mod to_tokens {
//! This module contains `ToTokens` implementations. This allows protocol
//! values to be interpolated in the `quote!` macro as constructors of the values.
//! Useful to construct static protocol values.
use super::*;
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
impl ToTokens for ModuleSpec {
fn to_tokens(&self, tokens: &mut TokenStream) {
let input_types_per_func = &self
.input_types_per_func
.iter()
.map(|(key, value)| {
(
key,
value.iter().map(|maybe_type| match maybe_type {
Some(typ) => quote! {Some(#typ)},
None => quote! { None },
}),
)
})
.map(|(key, value)| quote! { (#key.to_string(), vec![#(#value),*]) })
.collect::<Vec<_>>();
let output_types_per_func = &self
.output_types_per_func
.iter()
.map(|(key, value)| {
(
key,
value.iter().map(|maybe_type| match maybe_type {
Some(typ) => quote! {Some(#typ)},
None => quote! { None },
}),
)
})
.map(|(key, value)| quote! { (#key.to_string(), vec![#(#value),*]) })
.collect::<Vec<_>>();
let input_shapes_per_func = &self
.input_shapes_per_func
.iter()
.map(|(key, value)| {
(
key,
value.iter().map(|maybe_shape| match maybe_shape {
Some(shape) => quote! {Some(vec![#(#shape),*])},
None => quote! { None },
}),
)
})
.map(|(key, value)| quote! { (#key.to_string(), vec![#(#value),*]) })
.collect::<Vec<_>>();
let output_shapes_per_func = &self
.output_shapes_per_func
.iter()
.map(|(key, value)| {
(
key,
value.iter().map(|maybe_shape| match maybe_shape {
Some(shape) => quote! {Some(vec![#(#shape),*])},
None => quote! { None },
}),
)
})
.map(|(key, value)| quote! { (#key.to_string(), vec![#(#value),*]) })
.collect::<Vec<_>>();
tokens.extend(quote! {
::concrete::tfhe::ModuleSpec {
input_types_per_func: vec![#(#input_types_per_func),*].into_iter().collect(),
output_types_per_func: vec![#(#output_types_per_func),*].into_iter().collect(),
input_shapes_per_func: vec![#(#input_shapes_per_func),*].into_iter().collect(),
output_shapes_per_func: vec![#(#output_shapes_per_func),*].into_iter().collect(),
}
});
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use quote::quote;
#[test]
fn test_deserialize_spec() {
let string = r#"{
"input_types_per_func": {
"my_func": [
{
"is_signed": false,
"bit_width": 16,
"carry_width": 2,
"msg_width": 2,
"params": {
"lwe_dimension": 909,
"glwe_dimension": 1,
"polynomial_size": 4096,
"pbs_base_log": 15,
"pbs_level": 2,
"lwe_noise_distribution": 0,
"glwe_noise_distribution": 2.168404344971009e-19,
"encryption_key_choice": 0
}
},
{
"is_signed": false,
"bit_width": 16,
"carry_width": 2,
"msg_width": 2,
"params": {
"lwe_dimension": 909,
"glwe_dimension": 1,
"polynomial_size": 4096,
"pbs_base_log": 15,
"pbs_level": 2,
"lwe_noise_distribution": 0,
"glwe_noise_distribution": 2.168404344971009e-19,
"encryption_key_choice": 0
}
}
]
},
"output_types_per_func": {
"my_func": [
{
"is_signed": false,
"bit_width": 16,
"carry_width": 2,
"msg_width": 2,
"params": {
"lwe_dimension": 909,
"glwe_dimension": 1,
"polynomial_size": 4096,
"pbs_base_log": 15,
"pbs_level": 2,
"lwe_noise_distribution": 0,
"glwe_noise_distribution": 2.168404344971009e-19,
"encryption_key_choice": 0
}
}
]
},
"input_shapes_per_func": { "my_func": [[], []] },
"output_shapes_per_func": { "my_func": [[]] }
}
"#;
let cp: ModuleSpec = serde_json::from_str(string).unwrap();
let a = quote! {#cp};
dbg!(a.to_string());
}
}

View File

@@ -0,0 +1,133 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, PartialEq, Copy, Debug)]
pub enum EncryptionKeyChoice {
BIG = 0,
SMALL = 1,
}
impl TryFrom<i32> for EncryptionKeyChoice {
type Error = &'static str;
fn try_from(value: i32) -> Result<Self, Self::Error> {
match value {
0 => Ok(EncryptionKeyChoice::BIG),
1 => Ok(EncryptionKeyChoice::SMALL),
_ => Err("Invalid value for EncryptionKeyChoice"),
}
}
}
impl Serialize for EncryptionKeyChoice {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
(*self as i32).serialize(serializer)
}
}
impl<'de> Deserialize<'de> for EncryptionKeyChoice {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = i32::deserialize(deserializer)?;
EncryptionKeyChoice::try_from(value).map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CryptoParams {
pub lwe_dimension: usize,
pub glwe_dimension: usize,
pub polynomial_size: usize,
pub pbs_base_log: usize,
pub pbs_level: usize,
pub lwe_noise_distribution: f64,
pub glwe_noise_distribution: f64,
pub encryption_key_choice: EncryptionKeyChoice,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct IntegerType {
pub carry_width: usize,
pub msg_width: usize,
pub is_signed: bool,
pub bit_width: usize,
pub params: CryptoParams,
}
impl IntegerType {
pub fn n_cts(&self) -> usize {
self.bit_width / self.msg_width
}
}
#[cfg(feature = "compiler")]
mod to_tokens {
//! This module contains `ToTokens` implementations. This allows protocol
//! values to be interpolated in the `quote!` macro as constructors of the values.
//! Useful to construct static protocol values.
use super::*;
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
impl ToTokens for EncryptionKeyChoice {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
EncryptionKeyChoice::BIG => {
tokens.extend(quote! {::concrete::tfhe::EncryptionKeyChoice::BIG})
}
EncryptionKeyChoice::SMALL => {
tokens.extend(quote! {::concrete::tfhe::EncryptionKeyChoice::SMALL})
}
}
}
}
impl ToTokens for CryptoParams {
fn to_tokens(&self, tokens: &mut TokenStream) {
let lwe_dimension = self.lwe_dimension;
let glwe_dimension = self.glwe_dimension;
let polynomial_size = self.polynomial_size;
let pbs_base_log = self.pbs_base_log;
let pbs_level = self.pbs_level;
let lwe_noise_distribution = self.lwe_noise_distribution;
let glwe_noise_distribution = self.glwe_noise_distribution;
let encryption_key_choice = &self.encryption_key_choice;
tokens.extend(quote! {
::concrete::tfhe::CryptoParams {
lwe_dimension: #lwe_dimension,
glwe_dimension: #glwe_dimension,
polynomial_size: #polynomial_size,
pbs_base_log: #pbs_base_log,
pbs_level: #pbs_level,
lwe_noise_distribution: #lwe_noise_distribution,
glwe_noise_distribution: #glwe_noise_distribution,
encryption_key_choice: #encryption_key_choice,
}
});
}
}
impl ToTokens for IntegerType {
fn to_tokens(&self, tokens: &mut TokenStream) {
let carry_width = self.carry_width;
let msg_width = self.msg_width;
let params = &self.params;
let bit_width = &self.bit_width;
let is_signed = &self.is_signed;
tokens.extend(quote! {
::concrete::tfhe::IntegerType {
carry_width: #carry_width,
msg_width: #msg_width,
params: #params,
bit_width: #bit_width,
is_signed: #is_signed
}
});
}
}
}

View File

@@ -1,98 +1,7 @@
use super::python::{PythonPickledEnum, PythonPickledObject};
use serde::{Deserialize, Deserializer};
use serde_json::Value;
struct PythonPickledObject {
py_object: String,
py_serialized: String,
}
impl<'de> Deserialize<'de> for PythonPickledObject {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let json = Value::deserialize(deserializer)?;
let Value::Object(obj) = json else {
return Err(<D::Error as serde::de::Error>::custom("Missing object"));
};
let Some(Value::String(py_object)) = obj.get("py/object") else {
return Err(<D::Error as serde::de::Error>::custom(
"Missing field \"py/object\"",
));
};
let Some(py_serialized) = obj.get("serialized") else {
return Err(<D::Error as serde::de::Error>::custom(
"Missing field \"serialized\"",
));
};
Ok(PythonPickledObject {
py_object: py_object.clone(),
py_serialized: py_serialized.to_string(),
})
}
}
struct PythonPickledEnum {
py_type: String,
py_tuple: Value,
}
impl<'de> Deserialize<'de> for PythonPickledEnum {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let json = Value::deserialize(deserializer)?;
let Value::Object(obj) = json else {
return Err(<D::Error as serde::de::Error>::custom("Missing object"));
};
let Some(py_reduce) = obj.get("py/reduce") else {
return Err(<D::Error as serde::de::Error>::custom(
"Missing field \"py/reduce\"",
));
};
let Value::Array(arr) = py_reduce else {
return Err(<D::Error as serde::de::Error>::custom("Missing array"));
};
if arr.len() != 2 {
return Err(<D::Error as serde::de::Error>::custom(
"Unexpected py_reduce array length",
));
}
let Some(Value::Object(py_type_obj)) = arr.get(0) else {
return Err(<D::Error as serde::de::Error>::custom("Missing object"));
};
let Some(Value::String(py_type)) = py_type_obj.get("py/type") else {
return Err(<D::Error as serde::de::Error>::custom(
"Missing \"py/type\" field.",
));
};
let Some(Value::Object(py_tuple_obj)) = arr.get(1) else {
return Err(<D::Error as serde::de::Error>::custom("Missing object"));
};
let Some(py_tuple_value) = py_tuple_obj.get("py/tuple") else {
return Err(<D::Error as serde::de::Error>::custom(
"Missing \"py/tuple\" field.",
));
};
let Value::Array(py_tuple_arr) = py_tuple_value else {
return Err(<D::Error as serde::de::Error>::custom(
"\"py/tuple\" is not an array.",
));
};
if py_tuple_arr.len() != 1 {
return Err(<D::Error as serde::de::Error>::custom(
"Unexpected py_tuple array length",
));
}
let py_tuple = py_tuple_arr.get(0).unwrap();
Ok(PythonPickledEnum {
py_type: py_type.clone(),
py_tuple: py_tuple.clone(),
})
}
}
#[derive(Debug)]
pub enum ParameterSelectionStrategy {
V0,

View File

@@ -0,0 +1,19 @@
use cxx::UniquePtr;
use crate::ffi::{GetTensor, Tensor, Value};
pub trait FromValue {
type Spec;
fn from_value(s: Self::Spec, v: UniquePtr<Value>) -> Self;
}
impl<T> FromValue for Tensor<T>
where
Value: GetTensor<T>,
{
type Spec = ();
fn from_value(_s: Self::Spec, v: UniquePtr<Value>) -> Self {
v.get_tensor().unwrap()
}
}

View File

@@ -0,0 +1,12 @@
use crate::ffi::{Tensor, Value};
use cxx::UniquePtr;
pub trait IntoValue {
fn into_value(self) -> UniquePtr<Value>;
}
impl<T> IntoValue for Tensor<T> {
fn into_value(self) -> UniquePtr<Value> {
Value::from_tensor(self)
}
}

View File

@@ -0,0 +1,4 @@
pub mod configuration;
pub mod from_value;
pub mod into_value;
pub mod python;

View File

@@ -0,0 +1,94 @@
use serde::{Deserialize, Deserializer};
use serde_json::Value;
pub struct PythonPickledObject {
pub py_object: String,
pub py_serialized: String,
}
impl<'de> Deserialize<'de> for PythonPickledObject {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let json = Value::deserialize(deserializer)?;
let Value::Object(obj) = json else {
return Err(<D::Error as serde::de::Error>::custom("Missing object"));
};
let Some(Value::String(py_object)) = obj.get("py/object") else {
return Err(<D::Error as serde::de::Error>::custom(
"Missing field \"py/object\"",
));
};
let Some(py_serialized) = obj.get("serialized") else {
return Err(<D::Error as serde::de::Error>::custom(
"Missing field \"serialized\"",
));
};
Ok(PythonPickledObject {
py_object: py_object.clone(),
py_serialized: py_serialized.to_string(),
})
}
}
pub struct PythonPickledEnum {
pub py_type: String,
pub py_tuple: Value,
}
impl<'de> Deserialize<'de> for PythonPickledEnum {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let json = Value::deserialize(deserializer)?;
let Value::Object(obj) = json else {
return Err(<D::Error as serde::de::Error>::custom("Missing object"));
};
let Some(py_reduce) = obj.get("py/reduce") else {
return Err(<D::Error as serde::de::Error>::custom(
"Missing field \"py/reduce\"",
));
};
let Value::Array(arr) = py_reduce else {
return Err(<D::Error as serde::de::Error>::custom("Missing array"));
};
if arr.len() != 2 {
return Err(<D::Error as serde::de::Error>::custom(
"Unexpected py_reduce array length",
));
}
let Some(Value::Object(py_type_obj)) = arr.get(0) else {
return Err(<D::Error as serde::de::Error>::custom("Missing object"));
};
let Some(Value::String(py_type)) = py_type_obj.get("py/type") else {
return Err(<D::Error as serde::de::Error>::custom(
"Missing \"py/type\" field.",
));
};
let Some(Value::Object(py_tuple_obj)) = arr.get(1) else {
return Err(<D::Error as serde::de::Error>::custom("Missing object"));
};
let Some(py_tuple_value) = py_tuple_obj.get("py/tuple") else {
return Err(<D::Error as serde::de::Error>::custom(
"Missing \"py/tuple\" field.",
));
};
let Value::Array(py_tuple_arr) = py_tuple_value else {
return Err(<D::Error as serde::de::Error>::custom(
"\"py/tuple\" is not an array.",
));
};
if py_tuple_arr.len() != 1 {
return Err(<D::Error as serde::de::Error>::custom(
"Unexpected py_tuple array length",
));
}
let py_tuple = py_tuple_arr.get(0).unwrap();
Ok(PythonPickledEnum {
py_type: py_type.clone(),
py_tuple: py_tuple.clone(),
})
}
}

View File

@@ -4,5 +4,6 @@ version = "0.0.0"
edition = "2021"
[dependencies]
tfhe = "1.0"
concrete-macro = { path = "../concrete-macro" }
concrete = { path = "../concrete" }

View File

@@ -0,0 +1,16 @@
from concrete import fhe
@fhe.module()
class MyModule:
@fhe.function({"x": "encrypted"})
def inc(x):
return (x + 1) % 20
@fhe.function({"x": "encrypted"})
def dec(x):
return (x - 1) % 20
inputset = list(range(20))
my_module = MyModule.compile({"inc": inputset, "dec": inputset})
my_module.server.save("test.zip", via_mlir=True)

View File

@@ -0,0 +1,35 @@
from concrete import fhe
from concrete.fhe import tfhers
TFHERS_UINT_8_3_2_4096 = tfhers.TFHERSIntegerType(
False,
bit_width=8,
carry_width=3,
msg_width=2,
params=tfhers.CryptoParams(
lwe_dimension=909,
glwe_dimension=1,
polynomial_size=4096,
pbs_base_log=15,
pbs_level=2,
lwe_noise_distribution=0,
glwe_noise_distribution=2.168404344971009e-19,
encryption_key_choice=tfhers.EncryptionKeyChoice.BIG,
),
)
@fhe.module()
class MyModule:
@fhe.function({"x": "encrypted", "y":"encrypted"})
def my_func(x, y):
x = tfhers.to_native(x)
y = tfhers.to_native(y)
return tfhers.from_native(x + y, TFHERS_UINT_8_3_2_4096)
def t(v):
return tfhers.TFHERSInteger(TFHERS_UINT_8_3_2_4096, v)
inputset = [(t(0), t(0)), (t(2**6), t(2**6))]
my_module = MyModule.compile({"my_func": inputset})
my_module.server.save("test_tfhers.zip", via_mlir=True)

View File

@@ -0,0 +1,41 @@
mod precompile {
use concrete_macro::from_concrete_python_export_zip;
from_concrete_python_export_zip!("src/test.zip");
}
#[cfg(test)]
mod test {
use super::precompile;
use concrete::common::{ClientKeyset, ServerKeyset, Tensor, TransportValue};
#[test]
fn test() {
let mut secret_csprng = concrete::common::SecretCsprng::new(0u128);
let mut encryption_csprng = concrete::common::EncryptionCsprng::new(0u128);
let keyset = precompile::KeysetBuilder::new()
.generate(secret_csprng.pin_mut(), encryption_csprng.pin_mut());
let client_keyset = keyset.get_client();
let serialized_client_keyset = client_keyset.serialize();
let deserialized_client_keyset =
ClientKeyset::deserialize(serialized_client_keyset.as_slice());
let server_keyset = keyset.get_server();
let serialized_server_keyset = server_keyset.serialize();
let deserialized_server_keyset =
ServerKeyset::deserialize(serialized_server_keyset.as_slice());
let mut dec_client = precompile::client::dec::ClientFunction::new(
&deserialized_client_keyset,
encryption_csprng,
);
let mut dec_server = precompile::server::dec::ServerFunction::new();
let input = Tensor::new(vec![5], vec![]);
let prepared_input = dec_client.prepare_inputs(input);
let serialized_input = prepared_input.serialize();
let deserialized_input = TransportValue::deserialize(serialized_input.as_slice());
let output = dec_server.invoke(&deserialized_server_keyset, deserialized_input);
let serialized_output = output.serialize();
let deserialized_output = TransportValue::deserialize(serialized_output.as_slice());
let processed_output = dec_client.process_outputs(deserialized_output);
assert_eq!(processed_output.values(), [4]);
assert_eq!(processed_output.dimensions().len(), 0);
}
}

View File

@@ -1,36 +1,2 @@
mod precompile{
use concrete_macro::from_concrete_python_export_zip;
from_concrete_python_export_zip!("src/test.zip");
}
#[cfg(test)]
mod test {
use concrete::common::{ClientKeyset, ServerKeyset, Tensor, TransportValue};
use crate::precompile;
#[test]
fn test() {
let mut secret_csprng = concrete::common::SecretCsprng::new(0u128);
let mut encryption_csprng = concrete::common::EncryptionCsprng::new(0u128);
let keyset = precompile::new_keyset(secret_csprng.pin_mut(), encryption_csprng.pin_mut());
let client_keyset = keyset.get_client();
let serialized_client_keyset = client_keyset.serialize();
let deserialized_client_keyset = ClientKeyset::deserialize(serialized_client_keyset.as_slice());
let server_keyset = keyset.get_server();
let serialized_server_keyset = server_keyset.serialize();
let deserialized_server_keyset = ServerKeyset::deserialize(serialized_server_keyset.as_slice());
let mut dec_client =
precompile::client::dec::ClientFunction::new(&deserialized_client_keyset, encryption_csprng);
let mut dec_server = precompile::server::dec::ServerFunction::new();
let input = Tensor::new(vec![5], vec![]);
let prepared_input = dec_client.prepare_inputs(input);
let serialized_input = prepared_input.serialize();
let deserialized_input = TransportValue::deserialize(serialized_input.as_slice());
let output = dec_server.invoke(&deserialized_server_keyset, deserialized_input);
let serialized_output = output.serialize();
let deserialized_output = TransportValue::deserialize(serialized_output.as_slice());
let processed_output = dec_client.process_outputs(deserialized_output);
assert_eq!(processed_output.values(), [4]);
assert_eq!(processed_output.dimensions().len(), 0);
}
}
mod default;
mod tfhers;

View File

@@ -1,22 +1,27 @@
use concrete::common::Tensor;
use tfhe::prelude::{FheDecrypt, FheEncrypt};
use tfhe::shortint::parameters::v0_10::classic::gaussian::p_fail_2_minus_64::ks_pbs::V0_10_PARAM_MESSAGE_2_CARRY_3_KS_PBS_GAUSSIAN_2M64;
use tfhe::{generate_keys, FheUint8};
mod precompile {
use concrete_macro::from_concrete_python_export_zip;
from_concrete_python_export_zip!("src/test.zip");
from_concrete_python_export_zip!("src/test_tfhers.zip");
}
fn main() {
let mut secret_csprng = concrete::common::SecretCsprng::new(0u128);
let mut encryption_csprng = concrete::common::EncryptionCsprng::new(0u128);
let keyset = precompile::new_keyset(secret_csprng.pin_mut(), encryption_csprng.pin_mut());
let client_keyset = keyset.get_client();
let config = tfhe::ConfigBuilder::with_custom_parameters(
V0_10_PARAM_MESSAGE_2_CARRY_3_KS_PBS_GAUSSIAN_2M64,
);
let (client_key, _) = generate_keys(config);
let keyset = precompile::KeysetBuilder::new()
.with_key_for_my_func_0_arg(&client_key)
.generate(secret_csprng.pin_mut(), encryption_csprng.pin_mut());
let server_keyset = keyset.get_server();
let mut dec_client =
precompile::client::dec::ClientFunction::new(&client_keyset, encryption_csprng);
let mut dec_server = precompile::server::dec::ServerFunction::new();
let input = Tensor::new(vec![5], vec![]);
let prepared_input = dec_client.prepare_inputs(input);
let output = dec_server.invoke(&server_keyset, prepared_input);
let processed_output = dec_client.process_outputs(output);
println!("{:?}", processed_output.values());
let mut server = precompile::server::my_func::ServerFunction::new();
let arg_0 = FheUint8::encrypt(6u8, &client_key);
let arg_1 = FheUint8::encrypt(4u8, &client_key);
let output = server.invoke(&server_keyset, arg_0, arg_1);
let decrypted: u8 = output.decrypt(&client_key);
assert_eq!(decrypted, 10);
}

Binary file not shown.

View File

@@ -0,0 +1,51 @@
mod precompile {
use concrete_macro::from_concrete_python_export_zip;
from_concrete_python_export_zip!("src/test_tfhers.zip");
}
#[cfg(test)]
mod test {
use super::precompile;
use tfhe::prelude::{FheDecrypt, FheEncrypt};
use tfhe::shortint::parameters::v0_10::classic::gaussian::p_fail_2_minus_64::ks_pbs::{V0_10_PARAM_MESSAGE_2_CARRY_2_KS_PBS_GAUSSIAN_2M64, V0_10_PARAM_MESSAGE_2_CARRY_3_KS_PBS_GAUSSIAN_2M64};
use tfhe::{generate_keys, FheUint8};
#[test]
fn test() {
let mut secret_csprng = concrete::common::SecretCsprng::new(0u128);
let mut encryption_csprng = concrete::common::EncryptionCsprng::new(0u128);
let config = tfhe::ConfigBuilder::with_custom_parameters(
V0_10_PARAM_MESSAGE_2_CARRY_3_KS_PBS_GAUSSIAN_2M64,
);
let (client_key, _) = generate_keys(config);
let keyset = precompile::KeysetBuilder::new()
.with_key_for_my_func_0_arg(&client_key)
.generate(secret_csprng.pin_mut(), encryption_csprng.pin_mut());
let server_keyset = keyset.get_server();
let mut server = precompile::server::my_func::ServerFunction::new();
let arg_0 = FheUint8::encrypt(6u8, &client_key);
let arg_1 = FheUint8::encrypt(4u8, &client_key);
let output = server.invoke(&server_keyset, arg_0, arg_1);
let decrypted: u8 = output.decrypt(&client_key);
assert_eq!(decrypted, 10);
}
#[test]
#[should_panic]
fn test_reset_key() {
let mut secret_csprng = concrete::common::SecretCsprng::new(0u128);
let mut encryption_csprng = concrete::common::EncryptionCsprng::new(0u128);
let config1 = tfhe::ConfigBuilder::with_custom_parameters(
V0_10_PARAM_MESSAGE_2_CARRY_3_KS_PBS_GAUSSIAN_2M64,
);
let config2 = tfhe::ConfigBuilder::with_custom_parameters(
V0_10_PARAM_MESSAGE_2_CARRY_2_KS_PBS_GAUSSIAN_2M64,
);
let (client_key1, _) = generate_keys(config1);
let (client_key2, _) = generate_keys(config2);
precompile::KeysetBuilder::new()
.with_key_for_my_func_0_arg(&client_key1)
.with_key_for_my_func_0_arg(&client_key2)
.generate(secret_csprng.pin_mut(), encryption_csprng.pin_mut());
}
}

View File

@@ -1,10 +1,10 @@
# Concrete Protocol
#
# The following document contains a programatic description of a communication protocol to store and
# exchange data with applications of the concrete framework.
#
# The following document contains a programatic description of a communication protocol to store and
# exchange data with applications of the concrete framework.
#
# Todo:
# + Use `storagePrecision` instead of `integerPrecision` to better differentiate between the
# + Use `storagePrecision` instead of `integerPrecision` to better differentiate between the
# message and the storage.
# + Use `storageInfo` instead of `rawInfo`.
@@ -17,20 +17,20 @@ $Cxx.namespace("concreteprotocol");
######################################################################################### Commons ##
enum KeyType {
# Secret Keys can be drawn from different ranges of values, using different distributions. This
# Secret Keys can be drawn from different ranges of values, using different distributions. This
# enumeration encodes the different supported ways.
binary @0; # Uniform sampling in {0, 1}
ternary @1; # Uniform sampling in {-1, 0, 1}
}
struct Modulus {
# Ciphertext operations are performed using modular arithmetic. Depending on the use, different
# modulus can be used for the operations. This structure encodes the different supported ways.
# Ciphertext operations are performed using modular arithmetic. Depending on the use, different
# modulus can be used for the operations. This structure encodes the different supported ways.
modulus :union $Cxx.name("mod") {
# The modulus expected to be used.
native @0 :NativeModulus;
powerOfTwo @1 :PowerOfTwoModulus;
integer @2 :IntegerModulus;
@@ -38,50 +38,50 @@ struct Modulus {
}
struct NativeModulus{
# Operations are performed using the modulus of the integers used to store the ciphertexts.
#
# Operations are performed using the modulus of the integers used to store the ciphertexts.
#
# Note:
# The bitwidth of the integer storage is represented implicitly here, and must be grabbed from
# The bitwidth of the integer storage is represented implicitly here, and must be grabbed from
# the rest of the description.
#
#
# Example:
# 2^64 when the ciphertext is stored using 64 bits integers.
}
struct PowerOfTwoModulus{
# Operations are performed using a modulus that is a power of two.
#
# Example:
# Operations are performed using a modulus that is a power of two.
#
# Example:
# 2^n for any n between 0 and the bitwidth of the integer used to store the ciphertext.
power @0 :UInt32; # The power used to raise 2.
}
struct IntegerModulus{
# Operations are performed using a modulus that is an arbitrary integer.
# Operations are performed using a modulus that is an arbitrary integer.
#
# Example:
# n for any n between 0 and 2^N where N is the bitwidth of the integer used to store the
# n for any n between 0 and 2^N where N is the bitwidth of the integer used to store the
# ciphertext.
modulus @0 :UInt32; # The value used as modulus.
}
struct Shape{
# Scalar and tensor values are represented by the same types. This structure contains a
# description of the shape of value.
# Scalar and tensor values are represented by the same types. This structure contains a
# description of the shape of value.
#
# Note:
# If the dimensions vector is empty, the message is interpreted as a scalar.
dimensions @0 :List(UInt32); # The dimensions of the value.
}
struct RawInfo{
# A value exchanged at the boundary between two parties of a computation will be transmitted as a
# binary payload containing a tensor of integers. This payload will first have to be parsed to a
# tensor of proper shape, signedness and precision before being pre-processed and passed to the
# computation. This structure represents the informations needed to parse this payload into the
# A value exchanged at the boundary between two parties of a computation will be transmitted as a
# binary payload containing a tensor of integers. This payload will first have to be parsed to a
# tensor of proper shape, signedness and precision before being pre-processed and passed to the
# computation. This structure represents the informations needed to parse this payload into the
# expected tensor.
shape @0 :Shape; # The shape of the tensor.
@@ -93,8 +93,8 @@ struct Payload{
# A structure carrying a binary payload.
#
# Note:
# There is a limit to the maximum size of a Data type. For this reason, large payloads must be
# split into several blobs stored sequentially in a list. All but the last blobs store the
# There is a limit to the maximum size of a Data type. For this reason, large payloads must be
# split into several blobs stored sequentially in a list. All but the last blobs store the
# maximum amount of data allowed by Data, and the last store the remainder.
data @0 :List(Data); # The binary data of the payload
}
@@ -102,12 +102,12 @@ struct Payload{
##################################################################################### Compression ##
enum Compression{
# Evaluation keys and ciphertexts can be compressed when transported over the wire. This
# Evaluation keys and ciphertexts can be compressed when transported over the wire. This
# enumeration encodes the different compressions that can be used to compress scheme objects.
#
#
# Note:
# Not all compressions are available for every types of evaluation keys or ciphertexts.
none @0; # No compression is used.
seed @1; # The mask is represented by the seed of a csprng.
paillier @2; # An output lwe ciphertext transciphered to the paillier cryptosystem.
@@ -116,22 +116,22 @@ enum Compression{
################################################################################# LWE secret keys ##
struct LweSecretKeyParams {
# A secret key is parameterized by a few quantities of cryptographic importance. This structure
# A secret key is parameterized by a few quantities of cryptographic importance. This structure
# represents those parameters.
lweDimension @0 :UInt32; # The LWE dimension, e.g. the length of the key.
integerPrecision @1 :UInt32; # The bitwidth of the integers used for storage.
keyType @2 :KeyType; # The kind of distribution used to sample the key.
}
struct LweSecretKeyInfo {
# A secret key value is uniquely described by cryptographic parameters and an identifier. This
# A secret key value is uniquely described by cryptographic parameters and an identifier. This
# structure represents this description of a secret key.
#
#
# Note:
# Secret keys with same parameters are allowed to co-exist in a program, as long as they
# Secret keys with same parameters are allowed to co-exist in a program, as long as they
# have different ids.
id @0 :UInt32; # The identifier of the key.
params @1 :LweSecretKeyParams; # The cryptographic parameters of the keys.
}
@@ -139,7 +139,7 @@ struct LweSecretKeyInfo {
struct LweSecretKey {
# A secret key value is a payload and a description to interpret this payload. This structure
# can be used to store and communicate a secret key.
info @0 :LweSecretKeyInfo; # The description of the secret key.
payload @1 :Payload; # The payload
}
@@ -147,12 +147,12 @@ struct LweSecretKey {
############################################################################## LWE bootstrap keys ##
struct LweBootstrapKeyParams {
# A bootstrap key is parameterized by a few quantities of cryptographic importance. This structure
# A bootstrap key is parameterized by a few quantities of cryptographic importance. This structure
# represents those parameters.
#
# Note:
# For now, only keys with the same input and output key types can be represented.
# For now, only keys with the same input and output key types can be represented.
levelCount @0 :UInt32; # The number of levels of the ciphertexts.
baseLog @1 :UInt32; # The logarithm of the base of the ciphertext.
glweDimension @2 :UInt32; # The dimension of the ciphertexts.
@@ -167,7 +167,7 @@ struct LweBootstrapKeyParams {
struct LweBootstrapKeyInfo {
# A bootstrap key value is uniquely described by cryptographic parameters and a few application
# related quantities. This structure represents this description of a bootstrap key.
#
#
# Note:
# Bootstrap keys with same parameters, compression, input and output id, are allowed to co-exist
# in a program as long as they have different ids.
@@ -180,9 +180,9 @@ struct LweBootstrapKeyInfo {
}
struct LweBootstrapKey {
# A bootstrap key value is a payload and a description to interpret this payload. This structure
# A bootstrap key value is a payload and a description to interpret this payload. This structure
# can be used to store and communicate a bootstrap key.
info @0 :LweBootstrapKeyInfo; # The description of the bootstrap key.
payload @1 :Payload; # The payload.
}
@@ -190,11 +190,11 @@ struct LweBootstrapKey {
############################################################################## LWE keyswitch keys ##
struct LweKeyswitchKeyParams {
# A keyswitch key is parameterized by a few quantities of cryptographic importance. This structure
# A keyswitch key is parameterized by a few quantities of cryptographic importance. This structure
# represents those parameters.
#
# Note:
# For now, only keys with the same input and output key types can be represented.
# For now, only keys with the same input and output key types can be represented.
levelCount @0 :UInt32; # The number of levels of the ciphertexts.
baseLog @1 :UInt32; # The logarithm of the base of ciphertexts.
@@ -209,7 +209,7 @@ struct LweKeyswitchKeyParams {
struct LweKeyswitchKeyInfo {
# A keyswitch key value is uniquely described by cryptographic parameters and a few application
# related quantities. This structure represents this description of a keyswitch key.
#
#
# Note:
# Keyswitch keys with same parameters, compression, input and output id, are allowed to co-exist
# in a program as long as they have different ids.
@@ -222,9 +222,9 @@ struct LweKeyswitchKeyInfo {
}
struct LweKeyswitchKey {
# A keyswitch key value is a payload and a description to interpret this payload. This structure
# A keyswitch key value is a payload and a description to interpret this payload. This structure
# can be used to store and communicate a keyswitch key.
info @0 :LweKeyswitchKeyInfo; # The description of the keyswitch key.
payload @1 :Payload; # The payload.
}
@@ -232,11 +232,11 @@ struct LweKeyswitchKey {
########################################################################## Packing keyswitch keys ##
struct PackingKeyswitchKeyParams {
# A packing keyswitch key is parameterized by a few quantities of cryptographic importance. This
# A packing keyswitch key is parameterized by a few quantities of cryptographic importance. This
# structure represents those parameters.
#
#
# Note:
# For now, only keys with the same input and output key types can be represented.
# For now, only keys with the same input and output key types can be represented.
levelCount @0 :UInt32; # The number of levels of the ciphertexts.
baseLog @1 :UInt32; # The logarithm of the base of the ciphertexts.
@@ -251,12 +251,12 @@ struct PackingKeyswitchKeyParams {
}
struct PackingKeyswitchKeyInfo {
# A packing keyswitch key value is uniquely described by cryptographic parameters and a few
# application related quantities. This structure represents this description of a packing
# A packing keyswitch key value is uniquely described by cryptographic parameters and a few
# application related quantities. This structure represents this description of a packing
# keyswitch key.
#
#
# Note:
# Packing keyswitch keys with same parameters, compression, input and output id, are allowed to
# Packing keyswitch keys with same parameters, compression, input and output id, are allowed to
# co-exist in a program as long as they have different ids.
id @0 :UInt32; # The identifier of the packing keyswitch key.
@@ -267,9 +267,9 @@ struct PackingKeyswitchKeyInfo {
}
struct PackingKeyswitchKey {
# A packiing keyswitch key value is a payload and a description to interpret this payload. This
# A packiing keyswitch key value is a payload and a description to interpret this payload. This
# structure can be used to store and communicate a packing keyswitch key.
info @0 :PackingKeyswitchKeyInfo; # The description of the packing keyswitch key.
payload @1 :Payload; # The payload.
}
@@ -277,7 +277,7 @@ struct PackingKeyswitchKey {
######################################################################################### Keysets ##
struct KeysetInfo {
# The keyset needed for an application can be described by an ensemble of descriptions of the
# The keyset needed for an application can be described by an ensemble of descriptions of the
# different keys used in the program. This structure represents such a description.
lweSecretKeys @0 :List(LweSecretKeyInfo); # The secret key descriptions.
@@ -287,25 +287,25 @@ struct KeysetInfo {
}
struct ServerKeyset {
# A server keyset is represented by an ensemble of evaluation key values. This structure allows to
# A server keyset is represented by an ensemble of evaluation key values. This structure allows to
# store and communicate such a keyset.
lweBootstrapKeys @0 :List(LweBootstrapKey); # The bootstrap key values.
lweKeyswitchKeys @1 :List(LweKeyswitchKey); # The keyswitch key values.
packingKeyswitchKeys @2 :List(PackingKeyswitchKey); # The packing keyswitch key values.
}
struct ClientKeyset {
# A client keyset is represented by an ensemble of secret key values. This structure allows to
# A client keyset is represented by an ensemble of secret key values. This structure allows to
# store and communicate such a keyset.
lweSecretKeys @0 :List(LweSecretKey); # The secret key values.
}
struct Keyset {
# A complete application keyset is the union of a server keyset, and a client keyset. This
# A complete application keyset is the union of a server keyset, and a client keyset. This
# structure allows to store and communicate such a keyset.
server @0 :ServerKeyset;
client @1 :ClientKeyset;
}
@@ -314,18 +314,18 @@ struct Keyset {
struct EncodingInfo {
# A value in an fhe program can encode various kind of informations, be it encrypted or not.
# To correctly communicate, the different parties participating in the execution of the program
# To correctly communicate, the different parties participating in the execution of the program
# must share informations about what encoding is used for values exchanged at their boundaries.
# This structure represents such informations.
#
#
# Note:
# The shape field is expected to contain the _abstract_ shape of the value. This means that for
# an encrypted value, the shape must not contain informations about the shape of the
# ciphertext(s) themselves. Said differently, the shape must be the one that would be used if
# The shape field is expected to contain the _abstract_ shape of the value. This means that for
# an encrypted value, the shape must not contain informations about the shape of the
# ciphertext(s) themselves. Said differently, the shape must be the one that would be used if
# the value was not encrypted.
shape @0 :Shape; # The shape of the value.
encoding :union {
encoding :union {
# The encoding for each scalar element of the value.
integerCiphertext @1 :IntegerCiphertextEncodingInfo;
@@ -336,12 +336,12 @@ struct EncodingInfo {
}
struct IntegerCiphertextEncodingInfo {
# A ciphertext can be used to represent an integer value. This structure represents the
# A ciphertext can be used to represent an integer value. This structure represents the
# informations needed to encode such an integer.
width @0 :UInt32; # The bitwidth of the encoded integer.
isSigned @1 :Bool; # The signedness of the encoded integer.
mode :union {
mode :union {
# The mode used to encode the integer.
native @2 :NativeMode;
@@ -350,39 +350,39 @@ struct IntegerCiphertextEncodingInfo {
}
struct NativeMode {
# An integer of width from 1 to 8 bits can be encoded in a single ciphertext natively, by
# being shifted in the most significant bits. This structure represents this integer encoding
# An integer of width from 1 to 8 bits can be encoded in a single ciphertext natively, by
# being shifted in the most significant bits. This structure represents this integer encoding
# mode.
}
}
struct ChunkedMode {
# An integer of width from 1 to n can be encoded in a set of ciphertexts by chunking the bits
# An integer of width from 1 to n can be encoded in a set of ciphertexts by chunking the bits
# of the original integer. This structure represents this integer encoding mode.
size @0 :UInt32; # The number of chunks to be used.
width @1 :UInt32; # The number of bits encoded by each chunks.
}
struct CrtMode {
# An integer of width 1 to 16 can be encoded in a set of ciphertexts, by decomposing a value
# An integer of width 1 to 16 can be encoded in a set of ciphertexts, by decomposing a value
# using a set of pairwise coprimes. This structure represents this integer encoding mode.
moduli @0 :List(UInt32); # The coprimes used to decompose the original value.
}
}
struct BooleanCiphertextEncodingInfo {
# A ciphertext can be used to represent a boolean value. This structure represents such an
# A ciphertext can be used to represent a boolean value. This structure represents such an
# encoding.
}
struct PlaintextEncodingInfo {
# A cleartext value can be used to represent a plaintext value used in computation with
# A cleartext value can be used to represent a plaintext value used in computation with
# ciphertexts. This structure represent such an encoding.
}
struct IndexEncodingInfo {
# A cleartext value can be used to represent an index value used to index in a tensor of values.
# A cleartext value can be used to represent an index value used to index in a tensor of values.
# This structure represent such an encoding.
}
@@ -391,25 +391,25 @@ struct CircuitEncodingInfo {
# name. This structure represents this circuit encoding signature.
#
# Note:
# The order of the input and output lists matters. The order of values should be the same when
# The order of the input and output lists matters. The order of values should be the same when
# executing the circuit. Also, the name is expected to be unique in the program.
inputs @0 :List(EncodingInfo); # The ordered list of input encoding infos.
outputs @1 :List(EncodingInfo); # The ordered list of output encoding infos.
name @2 :Text; # The name of the circuit.
}
struct ProgramEncodingInfo {
# A program encodings is described by the set of circuit encodings. This structure represents
# A program encodings is described by the set of circuit encodings. This structure represents
# this ensemble of encoding signatures.
circuits @0 :List(CircuitEncodingInfo); # The list of the circuit encoding infos.
}
###################################################################################### Encryption ##
struct LweCiphertextEncryptionInfo {
# The encryption of a cleartext value requires some parameters to operate. This structure
# The encryption of a cleartext value requires some parameters to operate. This structure
# represents those parameters.
keyId @0 :UInt32; # The identifier of the secret key used to perform the encryption.
@@ -435,9 +435,9 @@ struct LweCiphertextTypeInfo {
# needed to verify and pre-or-post process this value.
#
# Note:
# Two shape information are carried in this type. The abstract shape is the shape the tensor
# would have if the values were cleartext. That is, it does not take into account the encryption
# process. The concrete shape is the final shape of the object accounting for the encryption,
# Two shape information are carried in this type. The abstract shape is the shape the tensor
# would have if the values were cleartext. That is, it does not take into account the encryption
# process. The concrete shape is the final shape of the object accounting for the encryption,
# that usually add one or more dimension to the object.
abstractShape @0 :Shape; # The abstract shape of the value.
@@ -448,13 +448,13 @@ struct LweCiphertextTypeInfo {
encoding :union {
# The encoding of the value stored inside the ciphertext.
integer @5 :IntegerCiphertextEncodingInfo;
integer @5 :IntegerCiphertextEncodingInfo;
boolean @6 :BooleanCiphertextEncodingInfo;
}
}
struct PlaintextTypeInfo {
# A plaintext value can flow in and out of a circuit. This structure represents the informations
# A plaintext value can flow in and out of a circuit. This structure represents the informations
# needed to verify and pre-or-post process this value.
shape @0 :Shape; # The shape of the value.
@@ -470,23 +470,23 @@ struct IndexTypeInfo {
integerPrecision @1 :UInt32; # The precision of the indexes.
isSigned @2 :Bool; # The signedness of the indexes.
}
############################################################################### Circuit signature ##
struct GateInfo {
# A value flowing in or out of a circuit is expected to be of a given type, according to the
# A value flowing in or out of a circuit is expected to be of a given type, according to the
# signature of this circuit. This structure represents such a type in a circuit signature.
rawInfo @0 :RawInfo; # The raw information that raw data must be possible to parse with.
typeInfo @1 :TypeInfo; # The type of the value expected at the gate.
}
struct CircuitInfo {
# A circuit signature can be described completely by the type informations for its input and
# A circuit signature can be described completely by the type informations for its input and
# outputs, as well as its name. This structure regroup those informations.
#
#
# Note:
# The order of the input and output lists matters. The order of values should be the same when
# The order of the input and output lists matters. The order of values should be the same when
# executing the circuit. Also, the name is expected to be unique in the program.
inputs @0 :List(GateInfo); # The ordered list of input types.
@@ -497,7 +497,7 @@ struct CircuitInfo {
struct ProgramInfo {
# A complete program can be described by the ensemble of circuit signatures, and the description
# of the keyset that go with it. This structure regroup those informations.
keyset @0 :KeysetInfo; # The informations on the keyset of the program.
circuits @1 :List(CircuitInfo); # The informations for the different circuits of the program.
}
@@ -506,14 +506,14 @@ struct ProgramInfo {
struct Value {
# A value is the union of a binary payload, raw informations to turn this payload into an integer
# tensor, and typ informations to check and pre-post process values at the boundary of a
# circuit. This structure can be used to store, or communicate a value used during a program
# tensor, and typ informations to check and pre-post process values at the boundary of a
# circuit. This structure can be used to store, or communicate a value used during a program
# execution.
#
#
# Note:
# The value info is a smaller runtime equivalent of the gate types used in the circuit
# The value info is a smaller runtime equivalent of the gate types used in the circuit
# signatures.
payload @0 :Payload; # The binary payload containing a raw integer tensor.
rawInfo @1 :RawInfo; # The informations to parse the binary payload.
typeInfo @2 :TypeInfo; # The type of the value.
@@ -522,11 +522,9 @@ struct Value {
################################################################################### Public values ##
struct PublicArguments {
args @0 :List(Value);
args @0 :List(Value);
}
struct PublicResults {
results @0 :List(Value);
}