Compare commits

...

75 Commits

Author SHA1 Message Date
Charlotte.Bonte
9575943ce1 change parameters test pfks 2023-06-01 16:34:19 +02:00
Charlotte.Bonte
3c65c9c38a change parameters test pfks 2023-06-01 16:25:18 +02:00
Charlotte.Bonte
0f4a5c6162 change parameters test pfks 2023-06-01 16:24:32 +02:00
Charlotte.Bonte
dba6535b17 change parameters test pfks 2023-05-31 21:57:54 +02:00
Charlotte.Bonte
ceeb968af1 change parameters test pfks 2023-05-31 21:45:51 +02:00
Charlotte.Bonte
bf1bd2226e test pfks 2023-05-31 18:29:17 +02:00
Charlotte.Bonte
34f2d81c9d split up tests 2023-05-31 18:15:15 +02:00
Charlotte.Bonte
5a59b1d32e split up tests 2023-05-31 17:42:03 +02:00
Charlotte.Bonte
1cb209cd93 add more parameter sets 2023-05-31 15:27:38 +02:00
Charlotte.Bonte
7eb2f47a58 switch parameters to make a comparison 2023-05-31 15:04:45 +02:00
Charlotte.Bonte
1082746e91 fix test for public functional key switching 2023-05-31 11:53:03 +02:00
Charlotte.Bonte
74fef3dad8 tix test public functional packing key switching 2023-05-31 10:53:19 +02:00
Charlotte.Bonte
d3dca9919b write test for public functional key switching 2023-05-31 10:42:18 +02:00
Charlotte.Bonte
54f4547340 write test for public functional key switching 2023-05-31 10:31:56 +02:00
Charlotte.Bonte
dd5407f9ce reduce the sample size 2023-05-30 13:40:34 +02:00
Charlotte.Bonte
f9c997adca activate the benches for the three functions, fix parameters 2023-05-30 13:09:17 +02:00
Charlotte.Bonte
cf854ee2fd add benchmarks for tensor product 2023-05-30 12:13:35 +02:00
Carl-Zama
7d07cef91b feat(tfhe): add glwe keyswitch, glwe tensor product, trace packing keyswitch and public functional packing keyswitch 2023-05-16 17:15:48 +01:00
Arthur Meyre
1c837fa6f0 test(core): add normality test based on Shapiro-Francia 2023-05-16 10:12:28 +02:00
tmontaigu
1ec7e4762a feat(integer): make wopbs compile on wasm
The goal here is just to make the code compile
and not allow js api to generate wopbs key yet.
2023-05-15 22:06:36 +02:00
tmontaigu
20fb697d57 refactor(hlapi): disable WopPBS by default in hlapi
In the HLAPI, the WopPBS is enabled by default,
meaning the WopPBS key is generated when integers
are enabled.

This is not really good as the wopbs key is huge
(~700MB with PARAM_2_2) and only used for function evaluation
which does not scale for all types exposed by the halpi
and is still a bit experimental so not really advertised in the docs.

Also keys for wopbs are not compressible yet
(that is why the HLAPI does not yet have a CompressedServerKey).

So disabling wopbs by default will enable to have a compressed server
key that actually compresse things.
2023-05-15 19:01:53 +02:00
tmontaigu
0429d56cf3 chore(U256): add small tests 2023-05-15 11:40:44 +02:00
tmontaigu
509bf3e284 docs(bench): update results of benchmarks in the docs 2023-05-12 21:58:47 +02:00
Arthur Meyre
b2fc1d5266 refactor(shortint): make a difference between PBS and Wopbs parameters
- preparatory work to manage several PBS implementations and harmonize
parameters management

BREAKING CHANGE:
- parameters structures changed
- gen_keys for integer now takes parameters by value to uniformize with
shortint
2023-05-12 17:20:05 +02:00
Arthur Meyre
62d94dbee8 chore(tfhe): fix double Example heading in docstring 2023-05-12 17:20:05 +02:00
Agnes Leroy
fbe911d7db chore(tfhe): hard set number of threads to 10 for the multi-bit PBS
It's the optimal value measured on an m6i.metal instance where we run the benchmarks
2023-05-12 15:12:11 +02:00
tmontaigu
ba72faf828 chore(readme): remove non-needed mut in boolean example 2023-05-11 22:25:12 +02:00
tmontaigu
c387b9340f feat(integer): improve mul and scalar mul
This improves the mul and scalar_mul algorithms
to be faster

The improvement is made within the code
that was responsible for summing up all
the terms by making better use of carries
and avoiding uncessary propagations.

The scalar mul forwards the call to a right shift
when the scalar is a power of two as it just cost one
PBS so it will always be faster.

For 64-bits, target-cpu=native + avx512:
- mul before: 3.4s
- mul after: 900ms
2023-05-11 14:07:11 +02:00
Arthur Meyre
cbb7d30fb8 chore(core): avoid having branching depending on secret values in PKE 2023-05-11 11:12:43 +02:00
David Testé
6e4a707eff chore(ci): compute throughput as operations per second
Since most of the operations are over 1 ms, there is no point
to compute the number of operation per millisecond.
2023-05-11 08:58:45 +02:00
tmontaigu
06b700f904 feat(integer): improve parallel algorithms for add/sub
This adds fully parallel algorithms for the addition and subtraction

These algorithms take ciphertexts with clean carries and
return creates a sum ciphertext that also has clean carries.

The carries are propagated in parallel, using
parallel algorithms for prefix sum / cumulative sum.

The is one based on Hillis and Steele, it the fastest
but uses a lot of threads.

The other on Blelloch, it requires less threads but is a
bit slower.

256bits addition using param_2_2 goes down from ~2.7s to:
- 364ms using Hillis and Steele
- 474ms using Blelloch

The commit also adds bitwise not, as it it necessary for the
subtraction.
2023-05-10 14:43:40 +02:00
dependabot[bot]
cfbabf7480 chore(deps): bump JS-DevTools/npm-publish from 2.0.0 to 2.1.0
Bumps [JS-DevTools/npm-publish](https://github.com/JS-DevTools/npm-publish) from 2.0.0 to 2.1.0.
- [Release notes](https://github.com/JS-DevTools/npm-publish/releases)
- [Changelog](https://github.com/JS-DevTools/npm-publish/blob/main/CHANGELOG.md)
- [Commits](0be441d808...541aa6b21b)

---
updated-dependencies:
- dependency-name: JS-DevTools/npm-publish
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-09 11:15:16 +02:00
tmontaigu
291ed9026f feat(hlapi): add casting between integer types
This adds the casting of integer types.

Downcasting truncates blocks.
Upcasting appends 0s

Casting is done via the introduced `cast_from` associated
function and the `cast_into` method. They are the equivalent of
the `From` and `Into` traits.

It was not possible to implement casting by implementing the
standard `From` and `Into` traits as initially planned because:

```rust
impl<P1, P2> From<GenericInteger<P1>> for GenericInteger<P2>
where P1: IntegerParameter,
      P2: Integer Parameter, {
    fn from(_: GenericInteger<P1>) -> Self {
        todo!()
    }
}
```

As it conflicts the blanket impl found in the stdlib
`impl<T> From<T> for T;` as P1, P2 may be the same and we have no way
of telling the compiler to consider this impl only when P1 != P2.
So we had to create our own stuff
2023-05-05 21:43:10 +02:00
tmontaigu
610f0010b8 refactor(hlapi): remove RefCell used to wrap integers
Our integer types are based on tfhe::integer.

Originally, operators (+, -, *, <<, etc) were mapped to "smart" operations
until commit "ee96a0ff185fedb9c4467a5b0c8195798c30b19f" where we swapped
to "default" ops.

The motivation of swapping from smart to default was that default ops
timing was always the same, so its easier to predict and reason about when
comparing with available benchmarks.
However, they may give worse performance depending
on the computations being done (addition/subtraction heavy or not).

In the High level API, we overloaded operators on const ref e.g. `&a + &b`
but as we initially mapped to smart operations we needed interior mutability.
RefCell was chosen, mainly because using Mutexes would have allowed
users to write "fake" parallel code, that is, the code compiles but is not
truly parallel due to the mutexes. RefCell makes writting parallel code
harder but If you manage to do it, its truly parallel.

After moving to default ops for the hlapi, we kept the inner RefCells
to allow time to decide to retract the change.

The final choice is to keep default ops as the default, so that means
we can remove the RefCells.

Entry points to smart operation will be added later to enable
'power users' to explicitely try them and see if they bring
improvement(s) for them.
Letting advanced user explicitely handle mutability of smart
operations and let them choose their synchronisation is likely a better
choice than making it for them as its an important choice.
2023-05-05 18:21:43 +02:00
tmontaigu
8b3d31ae8a refactor(hlapi): use one unique key for integers
This refactor of the inner workings of the High Level API
makes it so that all integer types share and use the same key
to encrypt their blocks

Before this commit, users that wanted to use integers via the
hlapi needed to select the type amongst the ones available
and enable it (eg i want to use 16 bits integers so I call
enable_default_uint16).

This meant that if users wanted to use many integer types
they would have to manually enable them. Since each type had its
own key, it meant they were completely separate (no casting possible)
and the different key type `ClientKey`, `ServerKey` would become very
big. So in practice you would stick to one and only integer type in your
program.

With this changes, users that wishes to use integers, will just need
to enable them (enable_default_integers()), and will get access to
all statically difined integer types (FheUint8, FheUint16, etc)
at the cost of one key for of these types.

This reduces complexity, memory footprint and,
will enable to introduce in commits later the ability to cast between
integer types.

BREAKING CHANGES:
 - Serialized keys are not backward compatible
 - enable_default_uint[8,16,etc] become enable_default_integers
2023-05-05 16:05:58 +02:00
sarah el kazdadi
98539aaa61 fix(pbs): fix bug in rounding code in f128 pbs 2023-05-05 15:32:59 +02:00
Arthur Meyre
9a80a01dc3 feat(integer): add trim/extend APIs for radix ciphertexts 2023-05-04 09:46:06 +02:00
tmontaigu
ecf9d50058 feat(integer): add parallelized scalar rotate_left/right
Like shifts, rotates are implemented by combining
a rotation of the block and bivariate PBSs in case
the rotation number `n` is not a multiple of the number
of bits in a block.

Since the behaviour of rotations is to 'cycle' bits
back the end/beginning of the 'bit slice' (i.e. no bits is ever lost like
it can with shifts), the performance is always the same when
(n % nb_bits_in block) != 0. However the implementation is simpler.

So assuming a machine where the number of threads
is >= to the ciphertext's number of block, the operation
cost one bivariate PBS.
2023-05-02 17:52:44 +02:00
Arthur Meyre
65e4aab38d chore(shortint): fix docstrings which were mixing big and small key params 2023-05-02 17:21:39 +02:00
Arthur Meyre
ac348870ba refactor(shortint): add encryption key choice in parameters
BREAKING CHANGE:
- Parameters layout change
- C API removal of SHORTINT_NATIVE_MODULUS which was a leftover from
a refactor
2023-05-02 17:21:39 +02:00
Arthur Meyre
6adfcaa5f7 chore(tfhe): bump version to 0.3.0 2023-05-02 11:10:14 +02:00
Arthur Meyre
bc6bbe66d9 chore(shortint): fix some wopbs function signature 2023-05-02 11:10:04 +02:00
Arthur Meyre
871d4aea17 refactor(core): refactor CiphertextModulus to be less error prone 2023-04-28 16:40:33 +02:00
Arthur Meyre
f81376b762 chore(ci): start benches only on our repo 2023-04-28 11:17:31 +02:00
Arthur Meyre
64813bae18 chore(tfhe): as seen there are uses of ilog2 which come from rust 1.67 2023-04-28 11:01:06 +02:00
Arthur Meyre
16ce2a8a3f refactor(wopbs): manage LUTs for wopbs to avoid copies 2023-04-28 09:45:11 +02:00
tmontaigu
f018987eac feat(integer): improve scalar shifts performances
This reworks the left/right scalar shift method.

This new implementation takes more advantage of the
radix representation property and use a combo
of shifting/moving blocks via a rotate_left/right
optionnaly followed by in-block shifting and propagation
to other blocks.

This new implementation requires the carries to be empty
(not sure what the preconditions were for the previous implementation)
and the output will also have clean carries.
Requiring empty carries allows to do the shift in a way
that scales well with the number of blocks as we can use truly parallel
operations.

This means that the time required to shift is dependent
in the shift value, which should not be a security problem
as its a clear value. (The previous implementation also
had timing that depended on the shift value)

There are two scenarios possible:
- The shift only require to move blocks -> its fast
- The shift requires moving + in-block shifting -> slower,
  but still faster than the previous implementation.

Worst case is when shift is less than the number of bits
in a block.

This also changes the type of the `shift` parameter from
`usize` to `u64` to be consistent with other scalar operations.

The follwing pseudo bench code on 64 bits
gives the following time ranges:

With changes:
```
unchecked_left: BenchStat {
    min: 16.743µs
    max: 526.518563ms
    mean: 150.77871ms
}
unchecked_left_parallelized: BenchStat {
    min: 17.291µs
    max: 94.408455ms
    mean: 30.092279ms
}
unchecked_right: BenchStat {
    min: 16.723µs
    max: 548.417332ms
    mean: 160.345234ms
}
unchecked_right_parallelized: BenchStat {
    min: 16.978µs
    max: 97.955322ms
    mean: 33.500562ms
}
Measured in 37.890296743s
```

Previous code:
```
unchecked_left: BenchStat {
    min: 1.055401595s
    max: 1.156574075s
    mean: 1.085648592s
}
unchecked_left_parallelized: BenchStat {
    min: 559.636545ms
    max: 630.83338ms
    mean: 584.747893ms
}
unchecked_right: BenchStat {
    min: 1.055041354s
    max: 2.314891255s
    mean: 1.644513996s
}
unchecked_right_parallelized: BenchStat {
    min: 562.017144ms
    max: 1.275945891s
    mean: 894.812286ms
}
Measured in 421.412913883s
```

```rust
use rand::Rng;
use std::time::Instant;
use tfhe::integer::{gen_keys, RadixClientKey};
use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;

const NB_CTXT: usize = 32;
const NB_TEST: usize = 100;

struct BenchStat {
    min: Option<std::time::Duration>,
    max: Option<std::time::Duration>,
    sum: std::time::Duration,
    count: u32,
}

impl BenchStat {
    fn update(&mut self, elapsed: std::time::Duration) {
        if self.min.is_none() {
            self.min = Some(elapsed);
        } else {
            self.min = self.min.map(|l| l.min(elapsed));
        }
        if self.max.is_none() {
            self.max = Some(elapsed);
        } else {
            self.max = self.max.map(|l| l.max(elapsed));
        }
        self.sum += elapsed;
        self.count += 1;
    }

    fn print(&self) {
        println!("BenchStat {{");
        println!("    min: {:?}", self.min.unwrap());
        println!("    max: {:?}", self.max.unwrap());
        println!("    mean: {:?}", self.sum / self.count);
        println!("}}");
    }
}

type ShiftType = u64;

fn main() {
    let mut unchecked_left_timing = BenchStat::default();
    let mut unchecked_left_parallelized_timing = BenchStat::default();
    let mut unchecked_right_timing = BenchStat::default();
    let mut unchecked_right_parallelized_timing = BenchStat::default();

    let total = Instant::now();

    let param = PARAM_MESSAGE_2_CARRY_2;
    let (cks, sks) = gen_keys(&param);
    let cks = RadixClientKey::from((cks, NB_CTXT));

    let mut rng = rand::thread_rng();

    //Nb of bits to shift
    let tmp_f64 = param.message_modulus.0 as f64;
    let nb_bits = tmp_f64.log2().floor() as usize * NB_CTXT;
    let modulus = (param.message_modulus.0 as u128).pow(NB_CTXT as u32);
    assert_eq!(nb_bits, 64);

    for i in 0..NB_TEST {
        println!("{} / {NB_TEST}", i + 1);
        let clear = rng.gen::<u128>() % modulus;
        let scalar = rng.gen::<u128>() % nb_bits as u128;

        println!("clear: {clear}, scalar: {scalar}");

        let ct = cks.encrypt(clear);

        {
            let before = Instant::now();
            let ct_res = sks.unchecked_scalar_left_shift(&ct, scalar as ShiftType);
            unchecked_left_timing.update(before.elapsed());
            //assert!(ct_res.block_carries_are_empty());
            let dec_res: u128 = cks.decrypt(&ct_res);
            assert_eq!((clear << scalar) % modulus, dec_res);

            let before = Instant::now();
            let ct_res = sks.unchecked_scalar_left_shift_parallelized(&ct, scalar as ShiftType);
            unchecked_left_parallelized_timing.update(before.elapsed());
            //assert!(ct_res.block_carries_are_empty());
            let dec_res: u128 = cks.decrypt(&ct_res);
            assert_eq!((clear << scalar) % modulus, dec_res);
        }

        {
            let before = Instant::now();
            let ct_res = sks.unchecked_scalar_right_shift(&ct, scalar as ShiftType);
            unchecked_right_timing.update(before.elapsed());
            // assert!(ct_res.block_carries_are_empty());
            let dec_res: u128 = cks.decrypt(&ct_res);
            assert_eq!((clear >> scalar) % modulus, dec_res);

            let before = Instant::now();
            let ct_res = sks.unchecked_scalar_right_shift_parallelized(&ct, scalar as ShiftType);
            unchecked_right_parallelized_timing.update(before.elapsed());
            //assert!(ct_res.block_carries_are_empty());
            let dec_res: u128 = cks.decrypt(&ct_res);
            assert_eq!((clear >> scalar) % modulus, dec_res);
        }
    }

    print!("unchecked_left: ");
    unchecked_left_timing.print();
    print!("unchecked_left_parallelized: ");
    unchecked_left_parallelized_timing.print();

    print!("unchecked_right: ");
    unchecked_right_timing.print();
    print!("unchecked_right_parallelized: ");
    unchecked_right_parallelized_timing.print();

    println!("Measured in {:?}", total.elapsed());
}
```

BREAKING CHANGE: parameter type changed from usize to u64
2023-04-27 17:18:55 +02:00
Arthur Meyre
20f6c5419b chore(core): re-enable split pbs for u128 2023-04-25 17:42:40 +02:00
Arthur Meyre
58b530f40b chore(doc): fix docstring ref 2023-04-25 17:42:40 +02:00
Arthur Meyre
689ad195f3 refactor(integer): remove usage of Mutex for determinism 2023-04-25 15:25:51 +02:00
sarah el kazdadi
4a1eda25d3 fix(split): fix split pbs backward conversion 2023-04-24 16:08:23 +02:00
Arthur Meyre
af936df064 chore(core): change rng tests to better avoid false failures
- we still check we generate non zero values but add retry conditions or
have less stringent checks, to allow some values to be zero for example as
it's a valid value that can be generated
- each test suite (test and doctest) for these tests ran 1000 times without
failure
2023-04-24 13:26:35 +02:00
dependabot[bot]
0233a69ea6 chore(deps): bump JS-DevTools/npm-publish from 1.4.3 to 2.0.0
Bumps [JS-DevTools/npm-publish](https://github.com/JS-DevTools/npm-publish) from 1.4.3 to 2.0.0.
- [Release notes](https://github.com/JS-DevTools/npm-publish/releases)
- [Changelog](https://github.com/JS-DevTools/npm-publish/blob/main/CHANGELOG.md)
- [Commits](0f451a9417...0be441d808)

---
updated-dependencies:
- dependency-name: JS-DevTools/npm-publish
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-24 12:56:59 +02:00
Arthur Meyre
f72a6ec835 chore(doc): fix typo 2023-04-24 09:30:16 +02:00
David Testé
25a2586eae chore(ci): publish tfhe release on-demand
This will perform on-demand release publication.
It will publish on the following channels:
 * crates.io
 * web and node package on npmjs
2023-04-21 14:39:36 +02:00
Arthur Meyre
c112a43a63 chore(core): add more sanity checks on RNG 2023-04-21 14:36:14 +02:00
Arthur Meyre
2813812380 fix(core): fix rng 2023-04-21 14:36:14 +02:00
tmontaigu
84a6036789 feat(boolean): add BooleanEngine::replace_thread_local
This new associated function allows to replace
the engine used in the thread.
2023-04-20 15:16:13 +02:00
David Testé
658368d0b6 chore(ci): create dummy release workflow
This is done to be able to test the effective worklfow
implementation in a development branch.
2023-04-20 10:26:31 +02:00
Arthur Meyre
9368049adf chore(core): disable split pbs128 2023-04-19 18:38:22 +02:00
David Testé
5e8ca0b52c chore(ci): fix decomposition basis and add bit size to params
Decomposition basis wasn't correctly set to handle CRT. Now it uses
a Vec that would be displayed as a string in the database.
In addition the bit size has been added to ease comparison between
various of them in Grafana.
2023-04-19 17:58:22 +02:00
Arthur Meyre
605cd5b3b0 chore(doc): updated benchmarks for min to reflect the fix done to min/max 2023-04-19 16:56:46 +02:00
David Testé
4bfe9c22d4 chore(ci): remove unused env variable in boolean benchmarks 2023-04-19 16:28:13 +02:00
Arthur Meyre
1c0b36c672 chore(bench): only run avx512 benches 2023-04-19 09:23:01 +02:00
Arthur Meyre
7dccb01a8d fix(integer): fix mul correctness
- update benches accordingly
2023-04-19 09:23:01 +02:00
Arthur Meyre
7bff348367 chore(bench): more multi-bit bench params 2023-04-19 09:03:58 +02:00
tmontaigu
74a5a278b6 fix(hlapi): use correct number of blocks for FheUint32
The FheUint32 was wrongly defined as being 32 blocks of 2 bits
when it should have been 16 blocks.
2023-04-18 18:40:39 +02:00
tmontaigu
426ced3295 feat(hlapi): add trivial encryptions 2023-04-18 15:07:06 +02:00
tmontaigu
7af5fcc7eb feat(integer): add trivial encryption 2023-04-18 15:07:06 +02:00
Arthur Meyre
12d9947149 chore(integer): add non regression test for scalar mul fix 2023-04-18 10:46:19 +02:00
dependabot[bot]
7c54896e68 chore(deps): bump actions/checkout from 3.5.0 to 3.5.2
Bumps [actions/checkout](https://github.com/actions/checkout) from 3.5.0 to 3.5.2.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](8f4b7f8486...8e5e7e5ab8)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-17 11:17:58 +02:00
J-B Orfila
04533dedfe chore(doc): fix typo 2023-04-14 18:30:30 +02:00
J-B Orfila
d01be35557 chore(doc): fix TOML 2023-04-14 18:30:30 +02:00
J-B Orfila
9fc32e2f52 chore(doc): fix dead links 2023-04-14 13:37:03 +02:00
Arthur Meyre
e1e78b8b9d chore(integer): restore empty carry check for default comparator tests
- only extract assign message instead of doing a full propagate as carries
are not supposed to be non zero (though the degree will have grown)
2023-04-14 13:34:35 +02:00
207 changed files with 14553 additions and 3757 deletions

View File

@@ -44,7 +44,7 @@ jobs:
echo "Type: ${{ github.event.inputs.instance_type }}"
echo "Request ID: ${{ github.event.inputs.request_id }}"
- uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
- name: Set up home
run: |

View File

@@ -44,7 +44,7 @@ jobs:
echo "Type: ${{ github.event.inputs.instance_type }}"
echo "Request ID: ${{ github.event.inputs.request_id }}"
- uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
- name: Set up home
run: |

View File

@@ -5,19 +5,19 @@ on:
workflow_dispatch:
inputs:
instance_id:
description: 'Instance ID'
description: "Instance ID"
type: string
instance_image_id:
description: 'Instance AMI ID'
description: "Instance AMI ID"
type: string
instance_type:
description: 'Instance product type'
description: "Instance product type"
type: string
runner_name:
description: 'Action runner name'
description: "Action runner name"
type: string
request_id:
description: 'Slab request ID'
description: "Slab request ID"
type: string
env:
@@ -42,7 +42,7 @@ jobs:
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
- name: Checkout tfhe-rs repo with tags
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
with:
fetch-depth: 0
@@ -57,9 +57,9 @@ jobs:
toolchain: nightly
override: true
- name: Run benchmarks
- name: Run benchmarks with AVX512
run: |
make bench_boolean
make AVX512_SUPPORT=ON bench_boolean
- name: Parse results
run: |
@@ -73,24 +73,8 @@ jobs:
--commit-date "${COMMIT_DATE}" \
--bench-date "${{ env.BENCH_DATE }}" \
--walk-subdirs \
--throughput
- name: Remove previous raw results
run: |
rm -rf target/criterion
- name: Run benchmarks with AVX512
run: |
make AVX512_SUPPORT=ON bench_boolean
- name: Parse AVX512 results
run: |
python3 ./ci/benchmark_parser.py target/criterion ${{ env.RESULTS_FILENAME }} \
--hardware ${{ inputs.instance_type }} \
--name-suffix avx512 \
--walk-subdirs \
--throughput \
--append-results
--throughput
- name: Measure key sizes
run: |
@@ -101,7 +85,7 @@ jobs:
python3 ./ci/benchmark_parser.py tfhe/boolean_key_sizes.csv ${{ env.RESULTS_FILENAME }} \
--key-sizes \
--append-results
- name: Upload parsed results artifact
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
with:
@@ -109,7 +93,7 @@ jobs:
path: ${{ env.RESULTS_FILENAME }}
- name: Checkout Slab repo
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
with:
repository: zama-ai/slab
path: slab
@@ -117,8 +101,6 @@ jobs:
- name: Send data to Slab
shell: bash
env:
COMPRESSED_RESULTS : ${{ env.RESULTS_FILENAME }}.gz
run: |
echo "Computing HMac on results file"
SIGNATURE="$(slab/scripts/hmac_calculator.sh ${{ env.RESULTS_FILENAME }} '${{ secrets.JOB_SECRET }}')"

View File

@@ -21,7 +21,7 @@ jobs:
fail-fast: false
steps:
- uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
- name: Run pcc checks
run: |

View File

@@ -5,19 +5,19 @@ on:
workflow_dispatch:
inputs:
instance_id:
description: 'Instance ID'
description: "Instance ID"
type: string
instance_image_id:
description: 'Instance AMI ID'
description: "Instance AMI ID"
type: string
instance_type:
description: 'Instance product type'
description: "Instance product type"
type: string
runner_name:
description: 'Action runner name'
description: "Action runner name"
type: string
request_id:
description: 'Slab request ID'
description: "Slab request ID"
type: string
env:
@@ -42,7 +42,7 @@ jobs:
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
- name: Checkout tfhe-rs repo with tags
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
with:
fetch-depth: 0
@@ -57,9 +57,9 @@ jobs:
toolchain: nightly
override: true
- name: Run benchmarks
- name: Run benchmarks with AVX512
run: |
make bench_integer
make AVX512_SUPPORT=ON bench_integer
- name: Parse results
run: |
@@ -73,24 +73,8 @@ jobs:
--commit-date "${COMMIT_DATE}" \
--bench-date "${{ env.BENCH_DATE }}" \
--walk-subdirs \
--throughput
- name: Remove previous raw results
run: |
rm -rf target/criterion
- name: Run benchmarks with AVX512
run: |
make AVX512_SUPPORT=ON bench_integer
- name: Parse AVX512 results
run: |
python3 ./ci/benchmark_parser.py target/criterion ${{ env.RESULTS_FILENAME }} \
--hardware ${{ inputs.instance_type }} \
--walk-subdirs \
--name-suffix avx512 \
--throughput \
--append-results
--throughput
- name: Upload parsed results artifact
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
@@ -99,7 +83,7 @@ jobs:
path: ${{ env.RESULTS_FILENAME }}
- name: Checkout Slab repo
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
with:
repository: zama-ai/slab
path: slab

View File

@@ -20,7 +20,7 @@ jobs:
runs-on: ["self-hosted", "m1mac"]
steps:
- uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
- name: Install latest stable
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af

52
.github/workflows/make_release.yml vendored Normal file
View File

@@ -0,0 +1,52 @@
# Publish new release of tfhe-rs on various platform.
name: Publish release
on:
workflow_dispatch:
inputs:
dry_run:
description: "Dry-run"
type: boolean
default: true
jobs:
publish_release:
name: Publish Release
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
with:
fetch-depth: 0
- name: Publish crate.io package
env:
CRATES_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
DRY_RUN: ${{ inputs.dry_run && '--dry-run' || '' }}
run: |
cargo publish -p tfhe --token ${{ env.CRATES_TOKEN }} ${{ env.DRY_RUN }}
- name: Build web package
run: |
make build_web_js_api
- name: Publish web package
uses: JS-DevTools/npm-publish@541aa6b21b4a1e9990c95a92c21adc16b35e9551
with:
token: ${{ secrets.NPM_TOKEN }}
package: tfhe/pkg/package.json
dry-run: ${{ inputs.dry_run }}
- name: Build Node package
run: |
rm -rf tfhe/pkg
make build_node_js_api
sed -i 's/"tfhe"/"node-tfhe"/g' tfhe/pkg/package.json
- name: Publish Node package
uses: JS-DevTools/npm-publish@541aa6b21b4a1e9990c95a92c21adc16b35e9551
with:
token: ${{ secrets.NPM_TOKEN }}
package: tfhe/pkg/package.json
dry-run: ${{ inputs.dry_run }}

View File

@@ -5,22 +5,21 @@ on:
workflow_dispatch:
inputs:
instance_id:
description: 'Instance ID'
description: "Instance ID"
type: string
instance_image_id:
description: 'Instance AMI ID'
description: "Instance AMI ID"
type: string
instance_type:
description: 'Instance product type'
description: "Instance product type"
type: string
runner_name:
description: 'Action runner name'
description: "Action runner name"
type: string
request_id:
description: 'Slab request ID'
description: "Slab request ID"
type: string
env:
CARGO_TERM_COLOR: always
RESULTS_FILENAME: parsed_benchmark_results_${{ github.sha }}.json
@@ -43,7 +42,7 @@ jobs:
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
- name: Checkout tfhe-rs repo with tags
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
with:
fetch-depth: 0
@@ -62,44 +61,44 @@ jobs:
run: |
make AVX512_SUPPORT=ON bench_pbs
- name: Parse results
run: |
COMMIT_DATE="$(git --no-pager show -s --format=%cd --date=iso8601-strict ${{ github.sha }})"
COMMIT_HASH="$(git describe --tags --dirty)"
python3 ./ci/benchmark_parser.py target/criterion ${{ env.RESULTS_FILENAME }} \
--database tfhe_rs \
--hardware ${{ inputs.instance_type }} \
--project-version "${COMMIT_HASH}" \
--branch ${{ github.ref_name }} \
--commit-date "${COMMIT_DATE}" \
--bench-date "${{ env.BENCH_DATE }}" \
--name-suffix avx512 \
--walk-subdirs \
--throughput
# - name: Parse results
# run: |
# COMMIT_DATE="$(git --no-pager show -s --format=%cd --date=iso8601-strict ${{ github.sha }})"
# COMMIT_HASH="$(git describe --tags --dirty)"
# python3 ./ci/benchmark_parser.py target/criterion ${{ env.RESULTS_FILENAME }} \
# --database tfhe_rs \
# --hardware ${{ inputs.instance_type }} \
# --project-version "${COMMIT_HASH}" \
# --branch ${{ github.ref_name }} \
# --commit-date "${COMMIT_DATE}" \
# --bench-date "${{ env.BENCH_DATE }}" \
# --name-suffix avx512 \
# --walk-subdirs \
# --throughput
- name: Upload parsed results artifact
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
with:
name: ${{ github.sha }}_pbs
path: ${{ env.RESULTS_FILENAME }}
# - name: Upload parsed results artifact
# uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
# with:
# name: ${{ github.sha }}_pbs
# path: ${{ env.RESULTS_FILENAME }}
#
# - name: Checkout Slab repo
# uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
# with:
# repository: zama-ai/slab
# path: slab
# token: ${{ secrets.CONCRETE_ACTIONS_TOKEN }}
- name: Checkout Slab repo
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
with:
repository: zama-ai/slab
path: slab
token: ${{ secrets.CONCRETE_ACTIONS_TOKEN }}
- name: Send data to Slab
shell: bash
run: |
echo "Computing HMac on downloaded artifact"
SIGNATURE="$(slab/scripts/hmac_calculator.sh ${{ env.RESULTS_FILENAME }} '${{ secrets.JOB_SECRET }}')"
echo "Sending results to Slab..."
curl -v -k \
-H "Content-Type: application/json" \
-H "X-Slab-Repository: ${{ github.repository }}" \
-H "X-Slab-Command: store_data_v2" \
-H "X-Hub-Signature-256: sha256=${SIGNATURE}" \
-d @${{ env.RESULTS_FILENAME }} \
${{ secrets.SLAB_URL }}
# - name: Send data to Slab
# shell: bash
# run: |
# echo "Computing HMac on downloaded artifact"
# SIGNATURE="$(slab/scripts/hmac_calculator.sh ${{ env.RESULTS_FILENAME }} '${{ secrets.JOB_SECRET }}')"
# echo "Sending results to Slab..."
# curl -v -k \
# -H "Content-Type: application/json" \
# -H "X-Slab-Repository: ${{ github.repository }}" \
# -H "X-Slab-Command: store_data_v2" \
# -H "X-Hub-Signature-256: sha256=${SIGNATURE}" \
# -d @${{ env.RESULTS_FILENAME }} \
# ${{ secrets.SLAB_URL }}

View File

@@ -5,22 +5,21 @@ on:
workflow_dispatch:
inputs:
instance_id:
description: 'Instance ID'
description: "Instance ID"
type: string
instance_image_id:
description: 'Instance AMI ID'
description: "Instance AMI ID"
type: string
instance_type:
description: 'Instance product type'
description: "Instance product type"
type: string
runner_name:
description: 'Action runner name'
description: "Action runner name"
type: string
request_id:
description: 'Slab request ID'
description: "Slab request ID"
type: string
env:
CARGO_TERM_COLOR: always
RESULTS_FILENAME: parsed_benchmark_results_${{ github.sha }}.json
@@ -43,7 +42,7 @@ jobs:
echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}"
- name: Checkout tfhe-rs repo with tags
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
with:
fetch-depth: 0
@@ -58,9 +57,9 @@ jobs:
toolchain: nightly
override: true
- name: Run benchmarks
- name: Run benchmarks with AVX512
run: |
make bench_shortint
make AVX512_SUPPORT=ON bench_shortint
- name: Parse results
run: |
@@ -74,24 +73,8 @@ jobs:
--commit-date "${COMMIT_DATE}" \
--bench-date "${{ env.BENCH_DATE }}" \
--walk-subdirs \
--throughput
- name: Remove previous raw results
run: |
rm -rf target/criterion
- name: Run benchmarks with AVX512
run: |
make AVX512_SUPPORT=ON bench_shortint
- name: Parse AVX512 results
run: |
python3 ./ci/benchmark_parser.py target/criterion ${{ env.RESULTS_FILENAME }} \
--hardware ${{ inputs.instance_type }} \
--walk-subdirs \
--name-suffix avx512 \
--throughput \
--append-results
--throughput
- name: Measure key sizes
run: |
@@ -110,7 +93,7 @@ jobs:
path: ${{ env.RESULTS_FILENAME }}
- name: Checkout Slab repo
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
with:
repository: zama-ai/slab
path: slab

View File

@@ -4,18 +4,19 @@ name: Start all benchmarks
on:
push:
branches:
- 'main'
- "main"
workflow_dispatch:
jobs:
start-benchmarks:
if: ${{ (github.event_name == 'push' && github.repository == 'zama-ai/tfhe-rs') || github.event_name == 'workflow_dispatch' }}
strategy:
matrix:
command: [boolean_bench, shortint_bench, integer_bench, pbs_bench]
command: [pbs_bench]
runs-on: ubuntu-latest
steps:
- name: Checkout Slab repo
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
with:
repository: zama-ai/slab
path: slab

View File

@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
with:
fetch-depth: 0
- name: Save repo

View File

@@ -7,7 +7,7 @@ RS_BUILD_TOOLCHAIN:=$(shell \
( (echo $(TARGET_ARCH_FEATURE) | grep -q x86) && echo stable) || echo $(RS_CHECK_TOOLCHAIN))
CARGO_RS_BUILD_TOOLCHAIN:=+$(RS_BUILD_TOOLCHAIN)
CARGO_PROFILE?=release
MIN_RUST_VERSION:=1.65
MIN_RUST_VERSION:=$(shell grep rust-version tfhe/Cargo.toml | cut -d '=' -f 2 | xargs)
AVX512_SUPPORT?=OFF
WASM_RUSTFLAGS:=
BIG_TESTS_INSTANCE?=FALSE
@@ -52,6 +52,12 @@ install_cargo_nextest: install_rs_build_toolchain
cargo $(CARGO_RS_BUILD_TOOLCHAIN) install cargo-nextest --locked || \
( echo "Unable to install cargo nextest, unknown error." && exit 1 )
.PHONY: install_wasm_pack # Install wasm-pack to build JS packages
install_wasm_pack: install_rs_build_toolchain
@wasm-pack --version > /dev/null 2>&1 || \
cargo $(CARGO_RS_BUILD_TOOLCHAIN) install wasm-pack || \
( echo "Unable to install cargo wasm-pack, unknown error." && exit 1 )
.PHONY: fmt # Format rust code
fmt: install_rs_check_toolchain
cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" fmt
@@ -99,10 +105,10 @@ clippy_c_api: install_rs_check_toolchain
--features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api \
-p tfhe -- --no-deps -D warnings
.PHONY: clippy_js_wasm_api # Run clippy lints enabling the boolean, shortint and the js wasm API
.PHONY: clippy_js_wasm_api # Run clippy lints enabling the boolean, shortint, integer and the js wasm API
clippy_js_wasm_api: install_rs_check_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \
--features=boolean-client-js-wasm-api,shortint-client-js-wasm-api \
--features=boolean-client-js-wasm-api,shortint-client-js-wasm-api,integer-client-js-wasm-api \
-p tfhe -- --no-deps -D warnings
.PHONY: clippy_tasks # Run clippy lints on helper tasks crate.
@@ -174,18 +180,18 @@ build_c_api: install_rs_check_toolchain
-p tfhe
.PHONY: build_web_js_api # Build the js API targeting the web browser
build_web_js_api: install_rs_build_toolchain
build_web_js_api: install_rs_build_toolchain install_wasm_pack
cd tfhe && \
RUSTFLAGS="$(WASM_RUSTFLAGS)" rustup run "$(RS_BUILD_TOOLCHAIN)" \
wasm-pack build --release --target=web \
-- --features=boolean-client-js-wasm-api,shortint-client-js-wasm-api
-- --features=boolean-client-js-wasm-api,shortint-client-js-wasm-api,integer-client-js-wasm-api
.PHONY: build_node_js_api # Build the js API targeting nodejs
build_node_js_api: install_rs_build_toolchain
build_node_js_api: install_rs_build_toolchain install_wasm_pack
cd tfhe && \
RUSTFLAGS="$(WASM_RUSTFLAGS)" rustup run "$(RS_BUILD_TOOLCHAIN)" \
wasm-pack build --release --target=nodejs \
-- --features=boolean-client-js-wasm-api,shortint-client-js-wasm-api
-- --features=boolean-client-js-wasm-api,shortint-client-js-wasm-api,integer-client-js-wasm-api
.PHONY: test_core_crypto # Run the tests of the core_crypto module including experimental ones
test_core_crypto: install_rs_build_toolchain install_rs_check_toolchain

View File

@@ -58,7 +58,7 @@ tfhe = { version = "*", features = ["boolean", "shortint", "integer", "x86_64"]
Note: aarch64-based machines are not yet supported for Windows as it's currently missing an entropy source to be able to seed the [CSPRNGs](https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator) used in TFHE-rs
Note that when running code that uses `tfhe-rs`, it is highly recommended
to run in release mode with cargo`s `--release` flag to have the best performances possible,
to run in release mode with cargo's `--release` flag to have the best performances possible,
eg: `cargo run --release`.
Here is a full example evaluating a Boolean circuit:
@@ -68,7 +68,7 @@ use tfhe::boolean::prelude::*;
fn main() {
// We generate a set of client/server keys, using the default parameters:
let (mut client_key, mut server_key) = gen_keys();
let (client_key, server_key) = gen_keys();
// We use the client secret key to encrypt two messages:
let ct_1 = client_key.encrypt(true);
@@ -132,7 +132,7 @@ use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
// We create keys to create 16 bits integers
// using 8 blocks of 2 bits
let (cks, sks) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, 8);
let (cks, sks) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, 8);
let clear_a = 2382u16;
let clear_b = 29374u16;

View File

@@ -40,7 +40,7 @@ parser.add_argument('--walk-subdirs', dest='walk_subdirs', action='store_true',
parser.add_argument('--key-sizes', dest='key_sizes', action='store_true',
help='Parse only the results regarding keys size measurements')
parser.add_argument('--throughput', dest='throughput', action='store_true',
help='Compute and append number of operations per millisecond and'
help='Compute and append number of operations per second and'
'operations per dollar')
parser.add_argument('--backend', dest='backend', default='cpu',
help='Backend on which benchmarks have run')
@@ -55,7 +55,7 @@ def recursive_parse(directory, walk_subdirs=False, name_suffix="", compute_throu
:param directory: path to directory that contains raw results as :class:`pathlib.Path`
:param walk_subdirs: traverse results subdirectories if parameters changes for benchmark case.
:param name_suffix: a :class:`str` suffix to apply to each test name found
:param compute_throughput: compute number of operations per millisecond and operations per
:param compute_throughput: compute number of operations per second and operations per
dollar
:param hardware_hourly_cost: hourly cost of the hardware used in dollar
@@ -106,11 +106,11 @@ def recursive_parse(directory, walk_subdirs=False, name_suffix="", compute_throu
)
if stat_name == "mean" and compute_throughput:
test_suffix = "ops-per-ms"
test_suffix = "ops-per-sec"
test_name_parts.append(test_suffix)
result_values.append(
_create_point(
compute_ops_per_millisecond(value),
compute_ops_per_second(value),
"_".join(test_name_parts),
bench_class,
"throughput",
@@ -242,15 +242,15 @@ def compute_ops_per_dollar(data_point, product_hourly_cost):
return ONE_HOUR_IN_NANOSECONDS / (product_hourly_cost * data_point)
def compute_ops_per_millisecond(data_point):
def compute_ops_per_second(data_point):
"""
Compute numbers of operations per millisecond for a given ``data_point``.
Compute numbers of operations per second for a given ``data_point``.
:param data_point: timing value measured during benchmark in nanoseconds
:return: number of operations per millisecond
:return: number of operations per second
"""
return 1E6 / data_point
return 1E9 / data_point
def _parse_file_to_json(directory, filename):

View File

@@ -2,18 +2,20 @@
set -e
CURR_DIR="$(dirname "$0")"
REL_CARGO_TOML_PATH="${CURR_DIR}/../tfhe/Cargo.toml"
MIN_RUST_VERSION="$(grep rust-version "${REL_CARGO_TOML_PATH}" | cut -d '=' -f 2 | xargs)"
function usage() {
echo "$0: check minimum cargo version"
echo
echo "--help Print this message"
echo "--rust-toolchain The toolchain to check the version for with leading"
echo "--min-rust-version Check toolchain version is >= to this version, default is 1.65"
echo "--min-rust-version Check toolchain version is >= to this version, default is ${MIN_RUST_VERSION}"
echo
}
RUST_TOOLCHAIN=""
# We set the default rust version 1.65 which is the minimum version required for stable GATs
MIN_RUST_VERSION="1.65"
while [ -n "$1" ]
do

View File

@@ -6,7 +6,7 @@ THIS_SCRIPT_NAME="$(basename "$0")"
TMP_FILE="$(mktemp)"
COUNT="$(git grep -rniI "thfe" . | grep -v "${THIS_SCRIPT_NAME}" | \
COUNT="$(git grep -rniI "thfe\|tfhr\|thfr" . | grep -v "${THIS_SCRIPT_NAME}" | \
tee "${TMP_FILE}" | wc -l | tr -d '[:space:]')"
cat "${TMP_FILE}"

View File

@@ -1,6 +1,6 @@
[package]
name = "tfhe"
version = "0.2.0"
version = "0.3.0"
edition = "2021"
readme = "../README.md"
keywords = ["fully", "homomorphic", "encryption", "fhe", "cryptography"]
@@ -11,14 +11,13 @@ license = "BSD-3-Clause-Clear"
description = "TFHE-rs is a fully homomorphic encryption (FHE) library that implements Zama's variant of TFHE."
build = "build.rs"
exclude = ["/docs/", "/c_api_tests/", "/CMakeLists.txt", "/js_on_wasm_tests/"]
rust-version = "1.65"
rust-version = "1.67"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dev-dependencies]
rand = "0.8.5"
rand_distr = "0.4.3"
kolmogorov_smirnov = "1.1.0"
paste = "1.0.7"
lazy_static = { version = "1.4.0" }
criterion = "0.4.0"
@@ -29,6 +28,8 @@ bincode = "1.3.3"
fs2 = { version = "0.4.3" }
itertools = "0.10.5"
num_cpus = "1.15"
# For erf and normality test
libm = "0.2.6"
[build-dependencies]
cbindgen = { version = "0.24.3", optional = true }
@@ -89,6 +90,7 @@ __wasm_api = [
]
boolean-client-js-wasm-api = ["boolean", "__wasm_api"]
shortint-client-js-wasm-api = ["shortint", "__wasm_api"]
integer-client-js-wasm-api = ["integer", "__wasm_api"]
nightly-avx512 = ["concrete-fft/nightly", "pulp/nightly"]

View File

@@ -1,6 +1,6 @@
#[path = "../utilities.rs"]
mod utilities;
use crate::utilities::{write_to_json, OperatorType};
use crate::utilities::{write_to_json, CryptoParametersRecord, OperatorType};
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use tfhe::boolean::client_key::ClientKey;
@@ -16,6 +16,24 @@ criterion_group!(
criterion_main!(gates_benches);
/// Helper function to write boolean benchmarks parameters to disk in JSON format.
pub fn write_to_json_boolean<T: Into<CryptoParametersRecord>>(
bench_id: &str,
params: T,
params_alias: impl Into<String>,
display_name: impl Into<String>,
) {
write_to_json(
bench_id,
params,
params_alias,
display_name,
&OperatorType::Atomic,
1,
vec![1],
);
}
// Put all `bench_function` in one place
// so the keygen is only run once per parameters saving time.
fn benchs(c: &mut Criterion, params: BooleanParameters, parameter_name: &str) {
@@ -28,35 +46,33 @@ fn benchs(c: &mut Criterion, params: BooleanParameters, parameter_name: &str) {
let ct2 = cks.encrypt(false);
let ct3 = cks.encrypt(true);
let operator = OperatorType::Atomic;
let id = format!("AND::{parameter_name}");
bench_group.bench_function(&id, |b| b.iter(|| black_box(sks.and(&ct1, &ct2))));
write_to_json(&id, params, parameter_name, "and", &operator);
write_to_json_boolean(&id, params, parameter_name, "and");
let id = format!("NAND::{parameter_name}");
bench_group.bench_function(&id, |b| b.iter(|| black_box(sks.nand(&ct1, &ct2))));
write_to_json(&id, params, parameter_name, "nand", &operator);
write_to_json_boolean(&id, params, parameter_name, "nand");
let id = format!("OR::{parameter_name}");
bench_group.bench_function(&id, |b| b.iter(|| black_box(sks.or(&ct1, &ct2))));
write_to_json(&id, params, parameter_name, "or", &operator);
write_to_json_boolean(&id, params, parameter_name, "or");
let id = format!("XOR::{parameter_name}");
bench_group.bench_function(&id, |b| b.iter(|| black_box(sks.xor(&ct1, &ct2))));
write_to_json(&id, params, parameter_name, "xor", &operator);
write_to_json_boolean(&id, params, parameter_name, "xor");
let id = format!("XNOR::{parameter_name}");
bench_group.bench_function(&id, |b| b.iter(|| black_box(sks.xnor(&ct1, &ct2))));
write_to_json(&id, params, parameter_name, "xnor", &operator);
write_to_json_boolean(&id, params, parameter_name, "xnor");
let id = format!("NOT::{parameter_name}");
bench_group.bench_function(&id, |b| b.iter(|| black_box(sks.not(&ct1))));
write_to_json(&id, params, parameter_name, "not", &operator);
write_to_json_boolean(&id, params, parameter_name, "not");
let id = format!("MUX::{parameter_name}");
bench_group.bench_function(&id, |b| b.iter(|| black_box(sks.mux(&ct1, &ct2, &ct3))));
write_to_json(&id, params, parameter_name, "mux", &operator);
write_to_json_boolean(&id, params, parameter_name, "mux");
}
fn bench_default_parameters(c: &mut Criterion) {

File diff suppressed because it is too large Load Diff

View File

@@ -23,13 +23,13 @@ use tfhe::shortint::parameters::{
/// in radix decomposition
struct ParamsAndNumBlocksIter {
params_and_bit_sizes:
itertools::Product<IntoIter<tfhe::shortint::Parameters, 1>, IntoIter<usize, 7>>,
itertools::Product<IntoIter<tfhe::shortint::PBSParameters, 1>, IntoIter<usize, 7>>,
}
impl Default for ParamsAndNumBlocksIter {
fn default() -> Self {
// FIXME One set of parameter is tested since we want to benchmark only quickest operations.
const PARAMS: [tfhe::shortint::Parameters; 1] = [
const PARAMS: [tfhe::shortint::PBSParameters; 1] = [
PARAM_MESSAGE_2_CARRY_2,
// PARAM_MESSAGE_3_CARRY_3,
// PARAM_MESSAGE_4_CARRY_4,
@@ -42,7 +42,7 @@ impl Default for ParamsAndNumBlocksIter {
}
}
impl Iterator for ParamsAndNumBlocksIter {
type Item = (tfhe::shortint::Parameters, usize, usize);
type Item = (tfhe::shortint::PBSParameters, usize, usize);
fn next(&mut self) -> Option<Self::Item> {
let (param, bit_size) = self.params_and_bit_sizes.next()?;
@@ -119,6 +119,8 @@ fn bench_server_key_binary_function_dirty_inputs<F>(
param.name(),
display_name,
&OperatorType::Atomic,
bit_size as u32,
vec![param.message_modulus.0.ilog2(); num_block],
);
}
@@ -177,6 +179,8 @@ fn bench_server_key_binary_function_clean_inputs<F>(
param.name(),
display_name,
&OperatorType::Atomic,
bit_size as u32,
vec![param.message_modulus.0.ilog2(); num_block],
);
}
@@ -246,6 +250,8 @@ fn bench_server_key_unary_function_dirty_inputs<F>(
param.name(),
display_name,
&OperatorType::Atomic,
bit_size as u32,
vec![param.message_modulus.0.ilog2(); num_block],
);
}
@@ -300,6 +306,8 @@ fn bench_server_key_unary_function_clean_inputs<F>(
param.name(),
display_name,
&OperatorType::Atomic,
bit_size as u32,
vec![param.message_modulus.0.ilog2(); num_block],
);
}
@@ -367,6 +375,8 @@ fn bench_server_key_binary_scalar_function_dirty_inputs<F>(
param.name(),
display_name,
&OperatorType::Atomic,
bit_size as u32,
vec![param.message_modulus.0.ilog2(); num_block],
);
}
@@ -421,6 +431,8 @@ fn bench_server_key_binary_scalar_function_clean_inputs<F>(
param.name(),
display_name,
&OperatorType::Atomic,
bit_size as u32,
vec![param.message_modulus.0.ilog2(); num_block],
);
}
@@ -573,6 +585,14 @@ define_server_key_bench_scalar_fn!(
define_server_key_bench_scalar_default_fn!(method_name: scalar_add_parallelized, display_name: add);
define_server_key_bench_scalar_default_fn!(method_name: scalar_sub_parallelized, display_name: sub);
define_server_key_bench_scalar_default_fn!(method_name: scalar_mul_parallelized, display_name: mul);
define_server_key_bench_scalar_default_fn!(
method_name: scalar_left_shift_parallelized,
display_name: left_shift
);
define_server_key_bench_scalar_default_fn!(
method_name: scalar_right_shift_parallelized,
display_name: right_shift
);
define_server_key_bench_scalar_fn!(method_name: unchecked_scalar_add, display_name: add);
define_server_key_bench_scalar_fn!(method_name: unchecked_scalar_sub, display_name: sub);
@@ -719,6 +739,8 @@ criterion_group!(
scalar_add_parallelized,
scalar_sub_parallelized,
scalar_mul_parallelized,
scalar_left_shift_parallelized,
scalar_right_shift_parallelized,
);
criterion_group!(
@@ -778,6 +800,8 @@ criterion_group!(
scalar_add_parallelized,
scalar_sub_parallelized,
scalar_mul_parallelized,
scalar_left_shift_parallelized,
scalar_right_shift_parallelized,
);
criterion_main!(

View File

@@ -5,7 +5,7 @@ use crate::utilities::{write_to_json, OperatorType};
use criterion::{criterion_group, criterion_main, Criterion};
use tfhe::shortint::keycache::NamedParam;
use tfhe::shortint::parameters::*;
use tfhe::shortint::{CiphertextBig, Parameters, ServerKey};
use tfhe::shortint::{CiphertextBig, PBSParameters, ServerKey, ShortintParameterSet};
use rand::Rng;
use tfhe::shortint::keycache::KEY_CACHE;
@@ -13,14 +13,14 @@ use tfhe::shortint::keycache::KEY_CACHE;
use tfhe::shortint::keycache::KEY_CACHE_WOPBS;
use tfhe::shortint::parameters::parameters_wopbs::WOPBS_PARAM_MESSAGE_4_NORM2_6;
const SERVER_KEY_BENCH_PARAMS: [Parameters; 4] = [
const SERVER_KEY_BENCH_PARAMS: [PBSParameters; 4] = [
PARAM_MESSAGE_1_CARRY_1,
PARAM_MESSAGE_2_CARRY_2,
PARAM_MESSAGE_3_CARRY_3,
PARAM_MESSAGE_4_CARRY_4,
];
const SERVER_KEY_BENCH_PARAMS_EXTENDED: [Parameters; 15] = [
const SERVER_KEY_BENCH_PARAMS_EXTENDED: [PBSParameters; 15] = [
PARAM_MESSAGE_1_CARRY_0,
PARAM_MESSAGE_1_CARRY_1,
PARAM_MESSAGE_2_CARRY_0,
@@ -43,7 +43,7 @@ fn bench_server_key_unary_function<F>(
bench_name: &str,
display_name: &str,
unary_op: F,
params: &[Parameters],
params: &[PBSParameters],
) where
F: Fn(&ServerKey, &mut CiphertextBig),
{
@@ -55,7 +55,7 @@ fn bench_server_key_unary_function<F>(
let mut rng = rand::thread_rng();
let modulus = cks.parameters.message_modulus.0 as u64;
let modulus = cks.parameters.message_modulus().0 as u64;
let clear_text = rng.gen::<u64>() % modulus;
@@ -74,6 +74,8 @@ fn bench_server_key_unary_function<F>(
param.name(),
display_name,
&OperatorType::Atomic,
param.message_modulus.0.ilog2(),
vec![param.message_modulus.0.ilog2()],
);
}
@@ -85,7 +87,7 @@ fn bench_server_key_binary_function<F>(
bench_name: &str,
display_name: &str,
binary_op: F,
params: &[Parameters],
params: &[PBSParameters],
) where
F: Fn(&ServerKey, &mut CiphertextBig, &mut CiphertextBig),
{
@@ -97,7 +99,7 @@ fn bench_server_key_binary_function<F>(
let mut rng = rand::thread_rng();
let modulus = cks.parameters.message_modulus.0 as u64;
let modulus = cks.parameters.message_modulus().0 as u64;
let clear_0 = rng.gen::<u64>() % modulus;
let clear_1 = rng.gen::<u64>() % modulus;
@@ -118,6 +120,8 @@ fn bench_server_key_binary_function<F>(
param.name(),
display_name,
&OperatorType::Atomic,
param.message_modulus.0.ilog2(),
vec![param.message_modulus.0.ilog2()],
);
}
@@ -129,7 +133,7 @@ fn bench_server_key_binary_scalar_function<F>(
bench_name: &str,
display_name: &str,
binary_op: F,
params: &[Parameters],
params: &[PBSParameters],
) where
F: Fn(&ServerKey, &mut CiphertextBig, u8),
{
@@ -141,7 +145,7 @@ fn bench_server_key_binary_scalar_function<F>(
let mut rng = rand::thread_rng();
let modulus = cks.parameters.message_modulus.0 as u64;
let modulus = cks.parameters.message_modulus().0 as u64;
let clear_0 = rng.gen::<u64>() % modulus;
let clear_1 = rng.gen::<u64>() % modulus;
@@ -161,6 +165,8 @@ fn bench_server_key_binary_scalar_function<F>(
param.name(),
display_name,
&OperatorType::Atomic,
param.message_modulus.0.ilog2(),
vec![param.message_modulus.0.ilog2()],
);
}
@@ -172,7 +178,7 @@ fn bench_server_key_binary_scalar_division_function<F>(
bench_name: &str,
display_name: &str,
binary_op: F,
params: &[Parameters],
params: &[PBSParameters],
) where
F: Fn(&ServerKey, &mut CiphertextBig, u8),
{
@@ -184,7 +190,7 @@ fn bench_server_key_binary_scalar_division_function<F>(
let mut rng = rand::thread_rng();
let modulus = cks.parameters.message_modulus.0 as u64;
let modulus = cks.parameters.message_modulus().0 as u64;
assert_ne!(modulus, 1);
let clear_0 = rng.gen::<u64>() % modulus;
@@ -208,6 +214,8 @@ fn bench_server_key_binary_scalar_division_function<F>(
param.name(),
display_name,
&OperatorType::Atomic,
param.message_modulus.0.ilog2(),
vec![param.message_modulus.0.ilog2()],
);
}
@@ -223,7 +231,7 @@ fn carry_extract(c: &mut Criterion) {
let mut rng = rand::thread_rng();
let modulus = cks.parameters.message_modulus.0 as u64;
let modulus = cks.parameters.message_modulus().0 as u64;
let clear_0 = rng.gen::<u64>() % modulus;
@@ -242,6 +250,8 @@ fn carry_extract(c: &mut Criterion) {
param.name(),
"carry_extract",
&OperatorType::Atomic,
param.message_modulus.0.ilog2(),
vec![param.message_modulus.0.ilog2()],
);
}
@@ -257,7 +267,7 @@ fn programmable_bootstrapping(c: &mut Criterion) {
let mut rng = rand::thread_rng();
let modulus = cks.parameters.message_modulus.0 as u64;
let modulus = cks.parameters.message_modulus().0 as u64;
let acc = sks.generate_accumulator(|x| x);
@@ -273,18 +283,29 @@ fn programmable_bootstrapping(c: &mut Criterion) {
})
});
write_to_json(&bench_id, param, param.name(), "pbs", &OperatorType::Atomic);
write_to_json(
&bench_id,
param,
param.name(),
"pbs",
&OperatorType::Atomic,
param.message_modulus.0.ilog2(),
vec![param.message_modulus.0.ilog2()],
);
}
bench_group.finish();
}
// TODO: remove?
fn _bench_wopbs_param_message_8_norm2_5(c: &mut Criterion) {
let mut bench_group = c.benchmark_group("programmable_bootstrap");
let param = WOPBS_PARAM_MESSAGE_4_NORM2_6;
let param_set: ShortintParameterSet = param.try_into().unwrap();
let pbs_params = param_set.pbs_parameters().unwrap();
let keys = KEY_CACHE_WOPBS.get_from_param((param, param));
let keys = KEY_CACHE_WOPBS.get_from_param((pbs_params, param));
let (cks, _, wopbs_key) = (keys.client_key(), keys.server_key(), keys.wopbs_key());
let mut rng = rand::thread_rng();

View File

@@ -5,7 +5,7 @@ use std::path::PathBuf;
use tfhe::boolean::parameters::BooleanParameters;
use tfhe::core_crypto::prelude::*;
#[cfg(feature = "shortint")]
use tfhe::shortint::Parameters;
use tfhe::shortint::PBSParameters;
#[derive(Clone, Copy, Default, Serialize)]
pub struct CryptoParametersRecord {
@@ -52,8 +52,8 @@ impl From<BooleanParameters> for CryptoParametersRecord {
}
#[cfg(feature = "shortint")]
impl From<Parameters> for CryptoParametersRecord {
fn from(params: Parameters) -> Self {
impl From<PBSParameters> for CryptoParametersRecord {
fn from(params: PBSParameters) -> Self {
CryptoParametersRecord {
lwe_dimension: Some(params.lwe_dimension),
glwe_dimension: Some(params.glwe_dimension),
@@ -64,11 +64,11 @@ impl From<Parameters> for CryptoParametersRecord {
pbs_level: Some(params.pbs_level),
ks_base_log: Some(params.ks_base_log),
ks_level: Some(params.ks_level),
pfks_level: Some(params.pfks_level),
pfks_base_log: Some(params.pfks_base_log),
pfks_modular_std_dev: Some(params.pfks_modular_std_dev),
cbs_level: Some(params.cbs_level),
cbs_base_log: Some(params.cbs_base_log),
pfks_level: None,
pfks_base_log: None,
pfks_modular_std_dev: None,
cbs_level: None,
cbs_base_log: None,
message_modulus: Some(params.message_modulus.0),
carry_modulus: Some(params.carry_modulus.0),
}
@@ -120,11 +120,12 @@ struct BenchmarkParametersRecord {
message_modulus: Option<usize>,
carry_modulus: Option<usize>,
ciphertext_modulus: usize,
bit_size: u32,
polynomial_multiplication: PolynomialMultiplication,
precision: u32,
error_probability: f64,
integer_representation: IntegerRepresentation,
decomposition_basis: u32,
decomposition_basis: Vec<u32>,
pbs_algorithm: Option<String>,
execution_type: ExecutionType,
key_set_type: KeySetType,
@@ -139,6 +140,8 @@ pub fn write_to_json<T: Into<CryptoParametersRecord>>(
params_alias: impl Into<String>,
display_name: impl Into<String>,
operator_type: &OperatorType,
bit_size: u32,
decomposition_basis: Vec<u32>,
) {
let params = params.into();
@@ -158,11 +161,12 @@ pub fn write_to_json<T: Into<CryptoParametersRecord>>(
message_modulus: params.message_modulus,
carry_modulus: params.carry_modulus,
ciphertext_modulus: 64,
bit_size,
polynomial_multiplication: PolynomialMultiplication::Fft,
precision: (params.message_modulus.unwrap_or(2) as u32).ilog2(),
error_probability: 2f64.powf(-41.0),
integer_representation: IntegerRepresentation::Radix,
decomposition_basis: (params.message_modulus.unwrap_or(2) as u32).ilog2(),
decomposition_basis,
pbs_algorithm: None, // To be added in future version
execution_type,
key_set_type: KeySetType::Single,

View File

@@ -32,6 +32,34 @@ int uint128_client_key(const ClientKey *client_key) {
return ok;
}
int uint128_encrypt_trivial(const ClientKey *client_key) {
int ok;
FheUint128 *lhs = NULL;
FheUint128 *rhs = NULL;
FheUint128 *result = NULL;
ok = fhe_uint128_try_encrypt_trivial_u128(10, 20, &lhs);
assert(ok == 0);
ok = fhe_uint128_try_encrypt_trivial_u128(1, 2, &rhs);
assert(ok == 0);
ok = fhe_uint128_sub(lhs, rhs, &result);
assert(ok == 0);
uint64_t w0, w1;
ok = fhe_uint128_decrypt(result, client_key, &w0, &w1);
assert(ok == 0);
assert(w0 == 9);
assert(w1 == 18);
fhe_uint128_destroy(lhs);
fhe_uint128_destroy(rhs);
fhe_uint128_destroy(result);
return ok;
}
int uint128_public_key(const ClientKey *client_key, const PublicKey *public_key) {
int ok;
FheUint128 *lhs = NULL;
@@ -66,7 +94,7 @@ int main(void) {
Config *config;
config_builder_all_disabled(&builder);
config_builder_enable_default_uint128_small(&builder);
config_builder_enable_default_integers_small(&builder);
config_builder_build(builder, &config);
ClientKey *client_key = NULL;
@@ -79,6 +107,7 @@ int main(void) {
set_server_key(server_key);
uint128_client_key(client_key);
uint128_encrypt_trivial(client_key);
uint128_public_key(client_key, public_key);
client_key_destroy(client_key);

View File

@@ -4,12 +4,12 @@
#include <inttypes.h>
#include <stdio.h>
int uint256_client_key(const ClientKey *client_key) {
int ok;
FheUint256 *lhs = NULL;
FheUint256 *rhs = NULL;
FheUint256 *result = NULL;
FheUint64 *cast_result = NULL;
U256 *lhs_clear = NULL;
U256 *rhs_clear = NULL;
U256 *result_clear = NULL;
@@ -40,6 +40,59 @@ int uint256_client_key(const ClientKey *client_key) {
assert(w2 == 10);
assert(w3 == 12);
// try some casting
ok = fhe_uint256_cast_into_fhe_uint64(result, &cast_result);
assert(ok == 0);
uint64_t u64_clear;
ok = fhe_uint64_decrypt(cast_result, client_key, &u64_clear);
assert(ok == 0);
assert(u64_clear == 6);
u256_destroy(lhs_clear);
u256_destroy(rhs_clear);
u256_destroy(result_clear);
fhe_uint256_destroy(lhs);
fhe_uint256_destroy(rhs);
fhe_uint256_destroy(result);
fhe_uint64_destroy(cast_result);
return ok;
}
int uint256_encrypt_trivial(const ClientKey *client_key) {
int ok;
FheUint256 *lhs = NULL;
FheUint256 *rhs = NULL;
FheUint256 *result = NULL;
U256 *lhs_clear = NULL;
U256 *rhs_clear = NULL;
U256 *result_clear = NULL;
ok = u256_from_u64_words(1, 2, 3, 4, &lhs_clear);
assert(ok == 0);
ok = u256_from_u64_words(5, 6, 7, 8, &rhs_clear);
assert(ok == 0);
ok = fhe_uint256_try_encrypt_trivial_u256(lhs_clear, &lhs);
assert(ok == 0);
ok = fhe_uint256_try_encrypt_trivial_u256(rhs_clear, &rhs);
assert(ok == 0);
ok = fhe_uint256_add(lhs, rhs, &result);
assert(ok == 0);
ok = fhe_uint256_decrypt(result, client_key, &result_clear);
assert(ok == 0);
uint64_t w0, w1, w2, w3;
ok = u256_to_u64_words(result_clear, &w0, &w1, &w2, &w3);
assert(ok == 0);
assert(w0 == 6);
assert(w1 == 8);
assert(w2 == 10);
assert(w3 == 12);
u256_destroy(lhs_clear);
u256_destroy(rhs_clear);
u256_destroy(result_clear);
@@ -99,7 +152,7 @@ int main(void) {
Config *config;
config_builder_all_disabled(&builder);
config_builder_enable_default_uint256_small(&builder);
config_builder_enable_default_integers_small(&builder);
config_builder_build(builder, &config);
ClientKey *client_key = NULL;
@@ -112,6 +165,7 @@ int main(void) {
set_server_key(server_key);
uint256_client_key(client_key);
uint256_encrypt_trivial(client_key);
uint256_public_key(client_key, public_key);
client_key_destroy(client_key);

View File

@@ -67,6 +67,37 @@ int public_key_test(const ClientKey *client_key, const PublicKey *public_key) {
return ok;
}
int trivial_encrypt_test(const ClientKey *client_key) {
int ok;
FheBool *lhs = NULL;
FheBool *rhs = NULL;
FheBool *result = NULL;
bool lhs_clear = 0;
bool rhs_clear = 1;
ok = fhe_bool_try_encrypt_trivial_bool(lhs_clear, &lhs);
assert(ok == 0);
ok = fhe_bool_try_encrypt_trivial_bool(rhs_clear, &rhs);
assert(ok == 0);
ok = fhe_bool_bitand(lhs, rhs, &result);
assert(ok == 0);
bool clear;
ok = fhe_bool_decrypt(result, client_key, &clear);
assert(ok == 0);
assert(clear == (lhs_clear & rhs_clear));
fhe_bool_destroy(lhs);
fhe_bool_destroy(rhs);
fhe_bool_destroy(result);
return ok;
}
int main(void)
{
@@ -88,6 +119,7 @@ int main(void)
client_key_test(client_key);
public_key_test(client_key, public_key);
trivial_encrypt_test(client_key);
client_key_destroy(client_key);
public_key_destroy(public_key);

View File

@@ -145,7 +145,7 @@ int main(void) {
ok = config_builder_all_disabled(&builder);
assert(ok == 0);
ok = config_builder_enable_default_uint8(&builder);
ok = config_builder_enable_default_integers(&builder);
assert(ok == 0);
ok = config_builder_build(builder, &config);
assert(ok == 0);
@@ -182,7 +182,7 @@ int main(void) {
ok = config_builder_all_disabled(&builder);
assert(ok == 0);
ok = config_builder_enable_default_uint8_small(&builder);
ok = config_builder_enable_default_integers_small(&builder);
assert(ok == 0);
ok = config_builder_build(builder, &config);
assert(ok == 0);

View File

@@ -108,8 +108,8 @@ void test_custom_keygen(void) {
ShortintServerKey *sks = NULL;
ShortintParameters *params = NULL;
int params_ok = shortint_create_parameters(10, 1, 1024, 10e-100, 10e-100, 2, 3, 2, 3, 2, 3,
10e-100, 2, 3, 2, 2, SHORTINT_NATIVE_MODULUS, &params);
int params_ok = shortint_create_parameters(10, 1, 1024, 10e-100, 10e-100, 2, 3, 2, 3, 2, 2, 64,
ShortintEncryptionKeyChoiceBig, &params);
assert(params_ok == 0);
int gen_keys_ok = shortint_gen_keys_with_parameters(params, &cks, &sks);

View File

@@ -430,7 +430,7 @@ void test_server_key(void) {
int get_params_ok = shortint_get_parameters(message_bits, carry_bits, &params);
assert(get_params_ok == 0);
int get_params_small_ok = shortint_get_parameters(message_bits, carry_bits, &params_small);
int get_params_small_ok = shortint_get_parameters_small(message_bits, carry_bits, &params_small);
assert(get_params_small_ok == 0);
int gen_cks_ok = shortint_gen_client_key(params, &cks);

View File

@@ -9,7 +9,7 @@ Welcome to this tutorial about TFHE-rs `core_crypto` module.
To use `TFHE-rs`, first it has to be added as a dependency in the `Cargo.toml`:
```toml
tfhe = { version = "0.2.0", features = [ "x86_64-unix" ] }
tfhe = { version = "0.3.0", features = [ "x86_64-unix" ] }
```
This enables the `x86_64-unix` feature to have efficient implementations of various algorithms for `x86_64` CPUs on a Unix-like system. The 'unix' suffix indicates that the `UnixSeeder`, which uses `/dev/random` to generate random numbers, is activated as a fallback if no hardware number generator is available, like `rdseed` on `x86_64` or if the [`Randomization Services`](https://developer.apple.com/documentation/security/1399291-secrandomcopybytes?language=objc) on Apple platforms are not available. To avoid having the `UnixSeeder` as a potential fallback or to run on non-Unix systems (e.g., Windows), the `x86_64` feature is sufficient.
@@ -19,19 +19,19 @@ For Apple Silicon, the `aarch64-unix` or `aarch64` feature should be enabled. `a
In short: For x86\_64-based machines running Unix-like OSes:
```toml
tfhe = { version = "0.2.0", features = ["x86_64-unix"] }
tfhe = { version = "0.3.0", features = ["x86_64-unix"] }
```
For Apple Silicon or aarch64-based machines running Unix-like OSes:
```toml
tfhe = { version = "0.2.0", features = ["aarch64-unix"] }
tfhe = { version = "0.3.0", features = ["aarch64-unix"] }
```
For x86\_64-based machines with the [`rdseed instruction`](https://en.wikipedia.org/wiki/RDRAND) running Windows:
```toml
tfhe = { version = "0.2.0", features = ["x86_64"] }
tfhe = { version = "0.3.0", features = ["x86_64"] }
```
### Commented code to double a 2-bits message in a leveled fashion and using a PBS with the `core_crypto` module.

View File

@@ -58,11 +58,11 @@ All timings are related to parallelized Radix-based integer operations, where ea
To ensure predictable timings, the operation flavor is the `default` one: a carry propagation is computed after each operation. Operation cost could be reduced by using `unchecked`, `checked`, or `smart`.
| Plaintext size | add | mul | greater\_than (gt) | min |
| -------------------| -------------- | ------------------- | --------- | ------- |
| 8 bits | 129.0 ms | 178.2 ms | 111.9 ms | 287.7 ms |
| 16 bits | 256.3 ms | 328.0 ms | 145.3 ms | 437.4 ms |
| 32 bits | 469.4 ms | 645.5 ms | 192.0 ms | 776.4 ms |
| 40 bits | 608.0 ms | 849.3 ms | 228.4 ms | 953.5 ms |
| 64 bits | 959.9 ms | 1.49 s | 249.0 ms | 1.36 s |
| 128 bits | 1.88 s | 3.25 s | 294.7 ms | 2.37 s |
| 256 bits | 3.66 s | 8.38 s | 361.8 ms | 4.51 s |
| -------------------| ---------------| --------------------| ---------------------| -------------|
| 8 bits | 129.0 ms | 227 ms | 111.9 ms | 186.8 ms |
| 16 bits | 195 ms | 369 ms | 145.3 ms | 233.1 ms |
| 32 bits | 238 ms | 519 ms | 192.0 ms | 282.9 ms |
| 40 bits | 283 ms | 754 ms | 228.4 ms | 318.6 ms |
| 64 bits | 297 ms | 1.18 s | 249.0 ms | 336.5 ms |
| 128 bits | 424 ms | 3.13 s | 294.7 ms | 398.6 ms |
| 256 bits | 500 ms | 11 s | 361.8 ms | 509.1 ms |

View File

@@ -5,7 +5,7 @@
To use `TFHE-rs` in your project, you first need to add it as a dependency in your `Cargo.toml`:
```toml
tfhe = { version = "0.2.0", features = [ "boolean", "shortint", "integer", "x86_64-unix" ] }
tfhe = { version = "0.3.0", features = [ "boolean", "shortint", "integer", "x86_64-unix" ] }
```
{% hint style="info" %}

View File

@@ -41,7 +41,7 @@ use tfhe::prelude::*;
fn main() {
let config = ConfigBuilder::all_disabled()
.enable_default_uint8()
.enable_default_integers()
.build();
let (client_key, server_key) = generate_keys(config);
@@ -115,7 +115,7 @@ fn main() {
let msg1 = 1;
let msg2 = 0;
let modulus = client_key.parameters.message_modulus.0;
let modulus = client_key.parameters.message_modulus().0;
// We use the client key to encrypt two messages:
let ct_1 = client_key.encrypt(msg1);
@@ -143,7 +143,7 @@ use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
// We create keys for radix represention to create 16 bits integers
// using 8 blocks of 2 bits
let (cks, sks) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, 8);
let (cks, sks) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, 8);
let clear_a = 2382u16;
let clear_b = 29374u16;

View File

@@ -237,7 +237,7 @@ use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_uint8().build();
let config = ConfigBuilder::all_disabled().enable_default_integers().build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
@@ -285,7 +285,7 @@ use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_uint8().build();
let config = ConfigBuilder::all_disabled().enable_default_integers().build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
@@ -331,7 +331,7 @@ use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_uint8().build();
let config = ConfigBuilder::all_disabled().enable_default_integers().build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
@@ -380,7 +380,7 @@ use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled().enable_default_uint8().build();
let config = ConfigBuilder::all_disabled().enable_default_integers().build();
let (keys, server_keys) = generate_keys(config);
set_server_key(server_keys);
@@ -403,3 +403,56 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
```
### Casting.
Casting between integer types is possible via the `cast_from` associated function
of `cast_into` method.
```rust
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8, FheUint32, FheUint16};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ConfigBuilder::all_disabled()
.enable_default_integers()
.build();
let (client_key, server_key) = generate_keys(config);
// Casting requires server_key to set
// (encryptions/decryptions do not need server_key to be set)
set_server_key(server_key);
{
let clear = 12_837u16;
let a = FheUint16::encrypt(clear, &client_key);
// Downcasting
let a: FheUint8 = a.cast_into();
let da: u8 = a.decrypt(&client_key);
assert_eq!(da, clear as u8);
// Upcasting
let a: FheUint32 = a.cast_into();
let da: u32 = a.decrypt(&client_key);
assert_eq!(da, (clear as u8) as u32);
}
{
let clear = 12_837u16;
let a = FheUint16::encrypt(clear, &client_key);
// Upcasting
let a = FheUint32::cast_from(a);
let da: u32 = a.decrypt(&client_key);
assert_eq!(da, clear as u32);
// Downcasting
let a = FheUint8::cast_from(a);
let da: u8 = a.decrypt(&client_key);
assert_eq!(da, (clear as u32) as u8);
}
Ok(())
}
```

View File

@@ -11,7 +11,7 @@ To serialize our data, a [data format](https://serde.rs/#data-formats) should be
[dependencies]
# ...
tfhe = { version = "0.2.0", features = ["integer"]}
tfhe = { version = "0.3.0", features = ["integer","x86_64-unix"]}
bincode = "1.3.3"
```
@@ -27,7 +27,7 @@ use tfhe::prelude::*;
fn main() -> Result<(), Box<dyn std::error::Error>>{
let config = ConfigBuilder::all_disabled()
.enable_default_uint8()
.enable_default_integers()
.build();
let ( client_key, server_key) = generate_keys(config);

View File

@@ -9,7 +9,7 @@ The basic steps for using the high-level API of TFHE-rs are:
3. Client-side: Encrypting data;
4. Server-side: Setting the server key;
5. Server-side: Computing over encrypted data;
6. Client-side: Encrypting data.
6. Client-side: Decrypting data.
Here is the full example (mixing client and server parts):
@@ -19,7 +19,7 @@ use tfhe::prelude::*;
fn main() {
let config = ConfigBuilder::all_disabled()
.enable_default_uint8()
.enable_default_integers()
.build();
// Client-side
@@ -44,10 +44,13 @@ fn main() {
}
```
Default configuration for x86 Unix machines:
```toml
tfhe = { version = "0.2.0", features = ["integer"]}
tfhe = { version = "0.3.0", features = ["integer", "x86_64-unix"]}
```
Other configurations can be found [here](../getting_started/installation.md).
### Imports.
`tfhe` uses `traits` to have a consistent API for creating FHE types and enable users to write generic functions. To be able to use associated functions and methods of a trait, the trait has to be in scope.
@@ -64,13 +67,8 @@ The first step is the creation of the configuration. The configuration is used t
Creating a configuration is done using the ConfigBuilder type.
In this example, 8-bit unsigned integers with default parameters are used. The `integers` feature must also be enabled, as per the table on the Getting Started page.
{% hint style="info" %}
```toml
tfhe = { version = "0.2.0", features = ["integer"]}
```
{% endhint %}
In this example, 8-bit unsigned integers with default parameters are used. The `integers`
feature must also be enabled, as per the table on the [Getting Started page](../getting_started/installation.md).
The config is done by first creating a builder with all types deactivated. Then, the `uint8` type with default parameters is activated.
@@ -79,7 +77,7 @@ use tfhe::{ConfigBuilder, generate_keys};
fn main() {
let config = ConfigBuilder::all_disabled()
.enable_default_uint8()
.enable_default_integers()
.build();
let (client_key, server_key) = generate_keys(config);
@@ -101,7 +99,7 @@ use tfhe::{ConfigBuilder, generate_keys, set_server_key};
fn main() {
let config = ConfigBuilder::all_disabled()
.enable_default_uint8()
.enable_default_integers()
.build();
let (client_key, server_key) = generate_keys(config);
@@ -191,10 +189,13 @@ To use the `FheUint8` type, the `integer` feature must be activated:
# Cargo.toml
[dependencies]
# ...
tfhe = { version = "0.2.0", features = ["integer"]}
# Default configuration for x86 Unix machines:
tfhe = { version = "0.3.0", features = ["integer", "x86_64-unix"]}
```
Other configurations can be found [here](../getting_started/installation.md).
```rust
use tfhe::{FheUint8, ConfigBuilder, generate_keys, set_server_key, ClientKey};
use tfhe::prelude::*;
@@ -229,7 +230,7 @@ impl FheLatinString {
.bytes()
.map(|b| FheUint8::encrypt(b, client_key))
.collect::<Vec<FheUint8>>();
let cst = FheUint8::encrypt(32, client_key);
let cst = FheUint8::encrypt(32u8, client_key);
Self {
bytes: fhe_bytes,
@@ -272,7 +273,7 @@ impl FheLatinString {
fn main() {
let config = ConfigBuilder::all_disabled()
.enable_default_uint8()
.enable_default_integers()
.build();
let (client_key, server_key) = generate_keys(config);
@@ -317,11 +318,13 @@ To use Booleans, the `booleans` feature in our Cargo.toml must be enabled:
```toml
# Cargo.toml
[dependencies]
# ...
tfhe = { version = "0.2.0", features = ["booleans"]}
# Default configuration for x86 Unix machines:
tfhe = { version = "0.3.0", features = ["boolean", "x86_64-unix"]}
```
Other configurations can be found [here](../getting_started/installation.md).
#### function definition
First, the verification function is defined.
@@ -461,7 +464,7 @@ To make the `compute_parity_bit` function compatible with both `FheBool` and `bo
Writing a generic function that accepts `FHE` types as well as clear types can help test the function to see if it is correct. If the function is generic, it can run with clear data, allowing the use of print-debugging or a debugger to spot errors.
Writing generic functions that use operator overloading for our FHE types can be trickier than normal, since, as explained in [Generic Bounds How To](../how\_to/generic\_bounds.md), `FHE` types are not copy. So using the reference `&` is mandatory, even though this is not the case when using native types, which are all `Copy`.
Writing generic functions that use operator overloading for our FHE types can be trickier than normal, since `FHE` types are not copy. So using the reference `&` is mandatory, even though this is not the case when using native types, which are all `Copy`.
This will make the generic bounds trickier at first.
@@ -549,7 +552,7 @@ help: consider adding an explicit lifetime bound...
|
```
The way to fix this is to use `Higher-Rank Trait Bounds`, as shown in the [Generic Bounds How To](../how\_to/generic\_bounds.md):
The way to fix this is to use `Higher-Rank Trait Bounds`:
```Rust
where

View File

@@ -26,7 +26,7 @@ use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
// We generate a set of client/server keys, using the default parameters:
let num_block = 4;
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, num_block);
}
```
@@ -105,7 +105,7 @@ use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
let num_block = 4;
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, num_block);
let msg1 = 12u64;
let msg2 = 11u64;
@@ -143,7 +143,7 @@ use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
let num_block = 2;
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, num_block);
let msg1 = 12u64;
let msg2 = 11u64;
@@ -181,7 +181,7 @@ use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
let num_block = 4;
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, num_block);
let msg1 = 12;
let msg2 = 11;
@@ -220,7 +220,7 @@ use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
let num_block = 4;
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, num_block);
let msg1 = 12;
let msg2 = 11;

View File

@@ -27,7 +27,7 @@ use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// We generate a set of client/server keys, using the default parameters:
let num_block = 4;
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, num_block);
let msg1 = 201;
let msg2 = 12;

View File

@@ -34,7 +34,7 @@ use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
// We generate a set of client/server keys, using the default parameters:
let num_block = 4;
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, num_block);
}
```
@@ -49,7 +49,7 @@ use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
// We generate a set of client/server keys, using the default parameters:
let num_block = 4;
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, num_block);
let msg1 = 128u64;
let msg2 = 13u64;
@@ -72,7 +72,7 @@ use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
// We generate a set of client/server keys, using the default parameters:
let num_block = 4;
let (client_key, _) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
let (client_key, _) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, num_block);
//We generate the public key from the secret client key:
let public_key = PublicKeyBig::new(&client_key);
@@ -98,7 +98,7 @@ use tfhe::shortint::parameters::PARAM_MESSAGE_2_CARRY_2;
fn main() {
// We generate a set of client/server keys, using the default parameters:
let num_block = 4;
let (client_key, server_key) = gen_keys_radix(&PARAM_MESSAGE_2_CARRY_2, num_block);
let (client_key, server_key) = gen_keys_radix(PARAM_MESSAGE_2_CARRY_2, num_block);
let msg1 = 128;
let msg2 = 13;

View File

@@ -14,7 +14,7 @@ Some parameter sets lead to FHE keys that are too big to fit in the 2GB memory s
### Setting-up TFHE-rs JS on WASM API for use in nodejs programs.
To build the JS on WASM bindings for TFHE-rs, you need to install [`wasm-pack`](https://rustwasm.github.io/wasm-pack/) in addition to a compatible (>= 1.65) [`rust toolchain`](https://rustup.rs/).
To build the JS on WASM bindings for TFHE-rs, you need to install [`wasm-pack`](https://rustwasm.github.io/wasm-pack/) in addition to a compatible (>= 1.67) [`rust toolchain`](https://rustup.rs/).
In a shell, then run the following to clone the TFHE-rs repo (one may want to checkout a specific tag, here the default branch is used for the build):

View File

@@ -55,7 +55,7 @@ fn main() {
let msg2 = 3;
let scalar = 4;
let modulus = client_key.parameters.message_modulus.0;
let modulus = client_key.parameters.message_modulus().0;
// We use the client key to encrypt two messages:
let mut ct_1 = client_key.encrypt(msg1);
@@ -88,7 +88,7 @@ fn main() {
let msg2 = 3;
let scalar = 4;
let modulus = client_key.parameters.message_modulus.0;
let modulus = client_key.parameters.message_modulus().0;
// We use the client key to encrypt two messages:
let mut ct_1 = client_key.encrypt(msg1);
@@ -131,7 +131,7 @@ fn main() {
let msg2 = 3;
let scalar = 4;
let modulus = client_key.parameters.message_modulus.0;
let modulus = client_key.parameters.message_modulus().0;
// We use the client key to encrypt two messages:
let mut ct_1 = client_key.encrypt(msg1);
@@ -165,7 +165,7 @@ fn main() {
let msg2 = 3;
let scalar = 4;
let modulus = client_key.parameters.message_modulus.0;
let modulus = client_key.parameters.message_modulus().0;
// We use the client key to encrypt two messages:
let mut ct_1 = client_key.encrypt(msg1);
@@ -241,7 +241,7 @@ fn main() {
let msg1 = 2;
let msg2 = 1;
let modulus = client_key.parameters.message_modulus.0;
let modulus = client_key.parameters.message_modulus().0;
// We use the private client key to encrypt two messages:
let ct_1 = client_key.encrypt(msg1);
@@ -272,7 +272,7 @@ fn main() {
let msg1 = 2;
let msg2 = 1;
let modulus = client_key.parameters.message_modulus.0;
let modulus = client_key.parameters.message_modulus().0;
// We use the private client key to encrypt two messages:
let ct_1 = client_key.encrypt(msg1);
@@ -303,7 +303,7 @@ fn main() {
let msg1 = 2;
let msg2 = 1;
let modulus = client_key.parameters.message_modulus.0;
let modulus = client_key.parameters.message_modulus().0;
// We use the private client key to encrypt two messages:
let ct_1 = client_key.encrypt(msg1);
@@ -331,7 +331,7 @@ fn main() {
let msg1 = 3;
let modulus = client_key.parameters.message_modulus.0;
let modulus = client_key.parameters.message_modulus().0;
// We use the private client key to encrypt two messages:
let ct_1 = client_key.encrypt(msg1);
@@ -365,7 +365,7 @@ fn main() {
let msg1 = 3;
let msg2 = 2;
let modulus = client_key.parameters.message_modulus.0 as u64;
let modulus = client_key.parameters.message_modulus().0 as u64;
// We use the private client key to encrypt two messages:
let ct_1 = client_key.encrypt(msg1);

View File

@@ -44,7 +44,7 @@ In the case of multiplication, two algorithms are implemented: the first one rel
## User-defined parameter sets
It is possible to define new parameter sets. To do so, it is sufficient to use the function `unsecure_parameters()` or to manually fill the `Parameter` structure fields.
It is possible to define new parameter sets. To do so, it is sufficient to use the function `unsecure_parameters()` or to manually fill the `PBSParameters` structure fields.
For instance:
@@ -53,7 +53,7 @@ use tfhe::shortint::prelude::*;
fn main() {
let param = unsafe {
Parameters::new(
PBSParameters::new(
LweDimension(656),
GlweDimension(2),
PolynomialSize(512),
@@ -63,14 +63,10 @@ fn main() {
DecompositionLevelCount(2),
DecompositionBaseLog(3),
DecompositionLevelCount(4),
StandardDev(0.00000000037411618952047216),
DecompositionBaseLog(15),
DecompositionLevelCount(1),
DecompositionLevelCount(0),
DecompositionBaseLog(0),
MessageModulus(4),
CarryModulus(1),
CiphertextModulus::new_native(),
EncryptionKeyChoice::Big,
)
};
}

View File

@@ -82,7 +82,7 @@ fn main() {
let msg1 = 1;
let msg2 = 0;
let modulus = client_key.parameters.message_modulus.0;
let modulus = client_key.parameters.message_modulus().0;
// We use the client key to encrypt two messages:
let ct_1 = client_key.encrypt(msg1);

View File

@@ -42,7 +42,15 @@ fn client_server_key_sizes(results_file: &Path) {
let test_name = format!("boolean_key_sizes_{params_name}_ksk");
write_result(&mut file, &test_name, ksk_size);
write_to_json(&test_name, *params, *params_name, "KSK", &operator);
write_to_json(
&test_name,
*params,
*params_name,
"KSK",
&operator,
0,
vec![],
);
println!(
"Element in KSK: {}, size in bytes: {}",
@@ -54,7 +62,15 @@ fn client_server_key_sizes(results_file: &Path) {
let test_name = format!("boolean_key_sizes_{params_name}_bsk");
write_result(&mut file, &test_name, bsk_size);
write_to_json(&test_name, *params, *params_name, "BSK", &operator);
write_to_json(
&test_name,
*params,
*params_name,
"BSK",
&operator,
0,
vec![],
);
println!(
"Element in BSK: {}, size in bytes: {}",

View File

@@ -4,8 +4,8 @@ use tfhe::shortint::parameters::parameters_wopbs_message_carry::{
WOPBS_PARAM_MESSAGE_4_CARRY_4,
};
use tfhe::shortint::parameters::{
Parameters, ALL_PARAMETER_VEC, PARAM_MESSAGE_1_CARRY_1, PARAM_MESSAGE_2_CARRY_2,
PARAM_MESSAGE_3_CARRY_3, PARAM_MESSAGE_4_CARRY_4,
PBSParameters, WopbsParameters, ALL_PARAMETER_VEC, PARAM_MESSAGE_1_CARRY_1,
PARAM_MESSAGE_2_CARRY_2, PARAM_MESSAGE_3_CARRY_3, PARAM_MESSAGE_4_CARRY_4,
};
fn client_server_keys() {
@@ -30,7 +30,7 @@ fn client_server_keys() {
KEY_CACHE.clear_in_memory_cache()
}
const WOPBS_PARAMS: [(Parameters, Parameters); 4] = [
const WOPBS_PARAMS: [(PBSParameters, WopbsParameters); 4] = [
(PARAM_MESSAGE_1_CARRY_1, WOPBS_PARAM_MESSAGE_1_CARRY_1),
(PARAM_MESSAGE_2_CARRY_2, WOPBS_PARAM_MESSAGE_2_CARRY_2),
(PARAM_MESSAGE_3_CARRY_3, WOPBS_PARAM_MESSAGE_3_CARRY_3),

View File

@@ -50,7 +50,15 @@ fn client_server_key_sizes(results_file: &Path) {
let test_name = format!("shortint_key_sizes_{}_ksk", params.name());
write_result(&mut file, &test_name, ksk_size);
write_to_json(&test_name, *params, params.name(), "KSK", &operator);
write_to_json(
&test_name,
*params,
params.name(),
"KSK",
&operator,
0,
vec![],
);
println!(
"Element in KSK: {}, size in bytes: {}",
@@ -62,7 +70,15 @@ fn client_server_key_sizes(results_file: &Path) {
let test_name = format!("shortint_key_sizes_{}_bsk", params.name());
write_result(&mut file, &test_name, bsk_size);
write_to_json(&test_name, *params, params.name(), "BSK", &operator);
write_to_json(
&test_name,
*params,
params.name(),
"BSK",
&operator,
0,
vec![],
);
println!(
"Element in BSK: {}, size in bytes: {}",

View File

@@ -18,6 +18,9 @@ use crate::core_crypto::commons::math::random::{ActivatedRandomGenerator, Seeder
use crate::core_crypto::commons::parameters::*;
use crate::core_crypto::seeders::new_seeder;
#[cfg(test)]
mod tests;
pub(crate) trait BinaryGatesEngine<L, R, K> {
fn and(&mut self, ct_left: L, ct_right: R, server_key: &K) -> Ciphertext;
fn nand(&mut self, ct_left: L, ct_right: R, server_key: &K) -> Ciphertext;
@@ -257,6 +260,37 @@ impl Default for BooleanEngine {
}
impl BooleanEngine {
/// Replace the thread_local BooleanEngine
///
/// `new_engine` will replace the already_existing
/// `thread_local` engine.
///
/// # Example
///
/// ```rust
/// use tfhe::boolean::engine::BooleanEngine;
/// use tfhe::core_crypto::commons::generators::DeterministicSeeder;
/// use tfhe::core_crypto::commons::math::random::Seed;
/// use tfhe::core_crypto::prelude::ActivatedRandomGenerator;
///
/// // WARNING: Using a deterministic seed is not recommended
/// // as it renders the random generation insecure
///
/// let deterministic_seed = Seed(0);
///
/// let mut seeder = DeterministicSeeder::<ActivatedRandomGenerator>::new(deterministic_seed);
/// let boolean_engine = BooleanEngine::new_from_seeder(&mut seeder);
/// BooleanEngine::replace_thread_local(boolean_engine);
///
/// // This uses the engine create earlier
/// let (cks, sks) = tfhe::boolean::gen_keys();
/// ```
pub fn replace_thread_local(new_engine: Self) {
Self::with_thread_local_mut(|local_engine| {
let _ = std::mem::replace(local_engine, new_engine);
})
}
pub fn new() -> Self {
let mut root_seeder = new_seeder();

View File

@@ -0,0 +1,54 @@
#[test]
fn test_replacing_thread_local_engine() {
use crate::boolean::engine::BooleanEngine;
use crate::core_crypto::commons::generators::DeterministicSeeder;
use crate::core_crypto::commons::math::random::Seed;
use crate::core_crypto::prelude::ActivatedRandomGenerator;
let deterministic_seed = Seed(0);
// We change the engine in the main thread
// then generate a client key, and then encrypt
// a boolean value and serialize it to compare
// it with other ciphertext
let mut seeder = DeterministicSeeder::<ActivatedRandomGenerator>::new(deterministic_seed);
let boolean_engine = BooleanEngine::new_from_seeder(&mut seeder);
BooleanEngine::replace_thread_local(boolean_engine);
let (cks, _) = crate::boolean::gen_keys();
let ct = cks.encrypt(false);
let main_thread_data = bincode::serialize(&ct).unwrap();
// In this thread, we don't change the engine
// and so we expect the encrypted value to be
// different compared with the one from the main thread
//
// This also "proves" that a thread is not affected
// by engine changes from other thread as engines are
// thread_local
let second_thread_data = std::thread::spawn(|| {
let (cks, _) = crate::boolean::gen_keys();
let ct = cks.encrypt(false);
bincode::serialize(&ct).unwrap()
})
.join()
.unwrap();
assert_ne!(second_thread_data, main_thread_data);
// In this thread, we change the engine,
// with a new engine that has the same seed
// as the one in the main thread
// So we expect the encrypted value to be the same
// compared with the one from the main thread
let third_thread_data = std::thread::spawn(move || {
let mut seeder = DeterministicSeeder::<ActivatedRandomGenerator>::new(deterministic_seed);
let boolean_engine = BooleanEngine::new_from_seeder(&mut seeder);
BooleanEngine::replace_thread_local(boolean_engine);
let (cks, _) = crate::boolean::gen_keys();
let ct = cks.encrypt(false);
bincode::serialize(&ct).unwrap()
})
.join()
.unwrap();
assert_eq!(third_thread_data, main_thread_data);
}

View File

@@ -11,6 +11,7 @@ impl_binary_fn_on_type!(FheBool => bitand, bitor, bitxor);
impl_unary_fn_on_type!(FheBool => not);
impl_decrypt_on_type!(FheBool, bool);
impl_try_encrypt_trivial_on_type!(FheBool{crate::high_level_api::FheBool}, bool);
impl_try_encrypt_with_client_key_on_type!(FheBool{crate::high_level_api::FheBool}, bool);
impl_try_encrypt_with_public_key_on_type!(FheBool{crate::high_level_api::FheBool}, bool);

View File

@@ -68,41 +68,9 @@ macro_rules! define_enable_default_fn(
#[cfg(feature = "boolean")]
define_enable_default_fn!(bool);
#[cfg(feature = "integer")]
define_enable_default_fn!(uint8);
define_enable_default_fn!(integers);
#[cfg(feature = "integer")]
define_enable_default_fn!(uint8 @small);
#[cfg(feature = "integer")]
define_enable_default_fn!(uint10);
#[cfg(feature = "integer")]
define_enable_default_fn!(uint10 @small);
#[cfg(feature = "integer")]
define_enable_default_fn!(uint12);
#[cfg(feature = "integer")]
define_enable_default_fn!(uint12 @small);
#[cfg(feature = "integer")]
define_enable_default_fn!(uint14);
#[cfg(feature = "integer")]
define_enable_default_fn!(uint14 @small);
#[cfg(feature = "integer")]
define_enable_default_fn!(uint16);
#[cfg(feature = "integer")]
define_enable_default_fn!(uint16 @small);
#[cfg(feature = "integer")]
define_enable_default_fn!(uint32);
#[cfg(feature = "integer")]
define_enable_default_fn!(uint32 @small);
#[cfg(feature = "integer")]
define_enable_default_fn!(uint64);
#[cfg(feature = "integer")]
define_enable_default_fn!(uint64 @small);
#[cfg(feature = "integer")]
define_enable_default_fn!(uint128);
#[cfg(feature = "integer")]
define_enable_default_fn!(uint128 @small);
#[cfg(feature = "integer")]
define_enable_default_fn!(uint256);
#[cfg(feature = "integer")]
define_enable_default_fn!(uint256 @small);
define_enable_default_fn!(integers @small);
/// Takes ownership of the builder
#[no_mangle]

View File

@@ -16,12 +16,12 @@ macro_rules! impl_operations_for_integer_type {
name: $name:ident,
clear_scalar_type: $clear_scalar_type:ty
) => {
impl_binary_fn_on_type_mut!($name => add, sub, mul, bitand, bitor, bitxor, eq, ge, gt, le, lt, min, max);
impl_binary_assign_fn_on_type_mut!($name => add_assign, sub_assign, mul_assign, bitand_assign, bitor_assign, bitxor_assign);
impl_scalar_binary_fn_on_type_mut!($name, $clear_scalar_type => add, sub, mul, shl, shr);
impl_scalar_binary_assign_fn_on_type_mut!($name, $clear_scalar_type => add_assign, sub_assign, mul_assign, shl_assign, shr_assign);
impl_binary_fn_on_type!($name => add, sub, mul, bitand, bitor, bitxor, eq, ge, gt, le, lt, min, max);
impl_binary_assign_fn_on_type!($name => add_assign, sub_assign, mul_assign, bitand_assign, bitor_assign, bitxor_assign);
impl_scalar_binary_fn_on_type!($name, $clear_scalar_type => add, sub, mul, shl, shr);
impl_scalar_binary_assign_fn_on_type!($name, $clear_scalar_type => add_assign, sub_assign, mul_assign, shl_assign, shr_assign);
impl_unary_fn_on_type_mut!($name => neg);
impl_unary_fn_on_type!($name => neg);
};
}
@@ -81,40 +81,62 @@ create_integer_wrapper_type!(name: FheUint128, clear_scalar_type: u64);
create_integer_wrapper_type!(name: FheUint256, clear_scalar_type: u64);
impl_decrypt_on_type!(FheUint8, u8);
impl_try_encrypt_trivial_on_type!(FheUint8{crate::high_level_api::FheUint8}, u8);
impl_try_encrypt_with_client_key_on_type!(FheUint8{crate::high_level_api::FheUint8}, u8);
impl_try_encrypt_with_public_key_on_type!(FheUint8{crate::high_level_api::FheUint8}, u8);
impl_try_encrypt_with_client_key_on_type!(CompressedFheUint8{crate::high_level_api::CompressedFheUint8}, u8);
impl_decrypt_on_type!(FheUint10, u16);
impl_try_encrypt_trivial_on_type!(FheUint10{crate::high_level_api::FheUint10}, u16);
impl_try_encrypt_with_client_key_on_type!(FheUint10{crate::high_level_api::FheUint10}, u16);
impl_try_encrypt_with_public_key_on_type!(FheUint10{crate::high_level_api::FheUint10}, u16);
impl_try_encrypt_with_client_key_on_type!(CompressedFheUint10{crate::high_level_api::CompressedFheUint10}, u16);
impl_decrypt_on_type!(FheUint12, u16);
impl_try_encrypt_trivial_on_type!(FheUint12{crate::high_level_api::FheUint12}, u16);
impl_try_encrypt_with_client_key_on_type!(FheUint12{crate::high_level_api::FheUint12}, u16);
impl_try_encrypt_with_public_key_on_type!(FheUint12{crate::high_level_api::FheUint12}, u16);
impl_try_encrypt_with_client_key_on_type!(CompressedFheUint12{crate::high_level_api::CompressedFheUint12}, u16);
impl_decrypt_on_type!(FheUint14, u16);
impl_try_encrypt_trivial_on_type!(FheUint14{crate::high_level_api::FheUint14}, u16);
impl_try_encrypt_with_client_key_on_type!(FheUint14{crate::high_level_api::FheUint14}, u16);
impl_try_encrypt_with_public_key_on_type!(FheUint14{crate::high_level_api::FheUint14}, u16);
impl_try_encrypt_with_client_key_on_type!(CompressedFheUint14{crate::high_level_api::CompressedFheUint14}, u16);
impl_decrypt_on_type!(FheUint16, u16);
impl_try_encrypt_trivial_on_type!(FheUint16{crate::high_level_api::FheUint16}, u16);
impl_try_encrypt_with_client_key_on_type!(FheUint16{crate::high_level_api::FheUint16}, u16);
impl_try_encrypt_with_public_key_on_type!(FheUint16{crate::high_level_api::FheUint16}, u16);
impl_try_encrypt_with_client_key_on_type!(CompressedFheUint16{crate::high_level_api::CompressedFheUint16}, u16);
impl_decrypt_on_type!(FheUint32, u32);
impl_try_encrypt_trivial_on_type!(FheUint32{crate::high_level_api::FheUint32}, u32);
impl_try_encrypt_with_client_key_on_type!(FheUint32{crate::high_level_api::FheUint32}, u32);
impl_try_encrypt_with_public_key_on_type!(FheUint32{crate::high_level_api::FheUint32}, u32);
impl_try_encrypt_with_client_key_on_type!(CompressedFheUint32{crate::high_level_api::CompressedFheUint32}, u32);
impl_decrypt_on_type!(FheUint64, u64);
impl_try_encrypt_trivial_on_type!(FheUint64{crate::high_level_api::FheUint64}, u64);
impl_try_encrypt_with_client_key_on_type!(FheUint64{crate::high_level_api::FheUint64}, u64);
impl_try_encrypt_with_public_key_on_type!(FheUint64{crate::high_level_api::FheUint64}, u64);
impl_try_encrypt_with_client_key_on_type!(CompressedFheUint64{crate::high_level_api::CompressedFheUint64}, u64);
#[no_mangle]
pub unsafe extern "C" fn fhe_uint128_try_encrypt_trivial_u128(
low_word: u64,
high_word: u64,
result: *mut *mut FheUint128,
) -> c_int {
catch_panic(|| {
let value = ((high_word as u128) << 64u128) | low_word as u128;
let inner = <crate::high_level_api::FheUint128>::try_encrypt_trivial(value).unwrap();
*result = Box::into_raw(Box::new(FheUint128(inner)));
})
}
#[no_mangle]
pub unsafe extern "C" fn fhe_uint128_try_encrypt_with_client_key_u128(
low_word: u64,
@@ -189,6 +211,18 @@ pub unsafe extern "C" fn fhe_uint128_decrypt(
})
}
#[no_mangle]
pub unsafe extern "C" fn fhe_uint256_try_encrypt_trivial_u256(
value: *const U256,
result: *mut *mut FheUint256,
) -> c_int {
catch_panic(|| {
let inner = <crate::high_level_api::FheUint256>::try_encrypt_trivial((*value).0).unwrap();
*result = Box::into_raw(Box::new(FheUint256(inner)));
})
}
#[no_mangle]
pub unsafe extern "C" fn fhe_uint256_try_encrypt_with_client_key_u256(
value: *const U256,
@@ -252,3 +286,34 @@ pub unsafe extern "C" fn fhe_uint256_decrypt(
*result = Box::into_raw(Box::new(U256(inner)));
})
}
macro_rules! define_casting_operation(
($from:ty => $($to:ty),*) => {
$(
::paste::paste!{
#[no_mangle]
pub unsafe extern "C" fn [<$from:snake _cast_into_ $to:snake>](
sself: *const $from,
result: *mut *mut $to,
) -> ::std::os::raw::c_int {
$crate::c_api::utils::catch_panic(|| {
let from = $crate::c_api::utils::get_ref_checked(sself).unwrap();
let inner_to = from.0.clone().cast_into();
*result = Box::into_raw(Box::new($to(inner_to)));
})
}
}
)*
}
);
define_casting_operation!(FheUint8 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256);
define_casting_operation!(FheUint10 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256);
define_casting_operation!(FheUint12 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256);
define_casting_operation!(FheUint14 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256);
define_casting_operation!(FheUint16 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256);
define_casting_operation!(FheUint32 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256);
define_casting_operation!(FheUint64 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256);
define_casting_operation!(FheUint128 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256);
define_casting_operation!(FheUint256 => FheUint8, FheUint10, FheUint14, FheUint16, FheUint32, FheUint64, FheUint128, FheUint256);

View File

@@ -59,6 +59,24 @@ macro_rules! impl_try_encrypt_with_public_key_on_type {
};
}
macro_rules! impl_try_encrypt_trivial_on_type {
($wrapper_type:ty{$wrapped_type:ty}, $input_type:ty) => {
::paste::paste! {
#[no_mangle]
pub unsafe extern "C" fn [<$wrapper_type:snake _try_encrypt_trivial_ $input_type:snake>](
value: $input_type,
result: *mut *mut $wrapper_type,
) -> ::std::os::raw::c_int {
$crate::c_api::utils::catch_panic(|| {
let inner = <$wrapped_type>::try_encrypt_trivial(value).unwrap();
*result = Box::into_raw(Box::new($wrapper_type(inner)));
})
}
}
};
}
macro_rules! impl_decrypt_on_type {
($wrapper_type:ty, $output_type:ty) => {
::paste::paste! {
@@ -191,70 +209,19 @@ macro_rules! impl_unary_fn_on_type {
};
}
// Meant for types on which makes use of interior mutability
#[cfg(feature = "integer")]
macro_rules! impl_unary_fn_on_type_mut {
($wrapper_type:ty => $($unary_fn_name:ident),* $(,)?) => {
$(
::paste::paste! {
#[no_mangle]
pub unsafe extern "C" fn [<$wrapper_type:snake _ $unary_fn_name>](
lhs: *mut $wrapper_type,
result: *mut *mut $wrapper_type,
) -> c_int {
$crate::c_api::utils::catch_panic(|| {
let lhs = $crate::c_api::utils::get_mut_checked(lhs).unwrap();
let inner = (&lhs.0).$unary_fn_name();
*result = Box::into_raw(Box::new($wrapper_type(inner)));
})
}
}
)*
};
}
// Meant for types on which makes use of interior mutability
#[cfg(feature = "integer")]
macro_rules! impl_binary_fn_on_type_mut {
($wrapper_type:ty => $($binary_fn_name:ident),* $(,)?) => {
$(
::paste::paste! {
#[no_mangle]
pub unsafe extern "C" fn [<$wrapper_type:snake _ $binary_fn_name>](
lhs: *mut $wrapper_type,
rhs: *mut $wrapper_type,
result: *mut *mut $wrapper_type,
) -> c_int {
$crate::c_api::utils::catch_panic(|| {
let lhs = $crate::c_api::utils::get_mut_checked(lhs).unwrap();
let rhs = $crate::c_api::utils::get_mut_checked(rhs).unwrap();
let inner = (&lhs.0).$binary_fn_name(&rhs.0);
*result = Box::into_raw(Box::new($wrapper_type(inner)));
})
}
}
)*
};
}
// Meant for types on which makes use of interior mutability
#[cfg(feature = "integer")]
macro_rules! impl_binary_assign_fn_on_type_mut {
macro_rules! impl_binary_assign_fn_on_type {
($wrapper_type:ty => $($binary_assign_fn_name:ident),* $(,)?) => {
$(
::paste::paste! {
#[no_mangle]
pub unsafe extern "C" fn [<$wrapper_type:snake _ $binary_assign_fn_name>](
lhs: *mut $wrapper_type,
rhs: *mut $wrapper_type,
rhs: *const $wrapper_type,
) -> ::std::os::raw::c_int {
$crate::c_api::utils::catch_panic(|| {
let lhs = $crate::c_api::utils::get_mut_checked(lhs).unwrap();
let rhs = $crate::c_api::utils::get_mut_checked(rhs).unwrap();
let rhs = $crate::c_api::utils::get_ref_checked(rhs).unwrap();
lhs.0.$binary_assign_fn_name(&rhs.0);
})
@@ -264,20 +231,19 @@ macro_rules! impl_binary_assign_fn_on_type_mut {
};
}
// Meant for types on which makes use of interior mutability
#[cfg(feature = "integer")]
macro_rules! impl_scalar_binary_fn_on_type_mut {
macro_rules! impl_scalar_binary_fn_on_type {
($wrapper_type:ty, $scalar_type:ty => $($binary_fn_name:ident),* $(,)?) => {
$(
::paste::paste! {
#[no_mangle]
pub unsafe extern "C" fn [<$wrapper_type:snake _scalar_ $binary_fn_name>](
lhs: *mut $wrapper_type,
lhs: *const $wrapper_type,
rhs: $scalar_type,
result: *mut *mut $wrapper_type,
) -> c_int {
$crate::c_api::utils::catch_panic(|| {
let lhs = $crate::c_api::utils::get_mut_checked(lhs).unwrap();
let lhs = $crate::c_api::utils::get_ref_checked(lhs).unwrap();
let inner = (&lhs.0).$binary_fn_name(rhs);
@@ -289,9 +255,8 @@ macro_rules! impl_scalar_binary_fn_on_type_mut {
};
}
// Meant for types on which makes use of interior mutability
#[cfg(feature = "integer")]
macro_rules! impl_scalar_binary_assign_fn_on_type_mut {
macro_rules! impl_scalar_binary_assign_fn_on_type {
($wrapper_type:ty, $scalar_type:ty => $($binary_assign_fn_name:ident),* $(,)?) => {
$(
::paste::paste! {

View File

@@ -34,7 +34,7 @@ pub unsafe extern "C" fn shortint_gen_keys_with_parameters(
let shortint_parameters = get_ref_checked(shortint_parameters).unwrap();
let client_key = shortint::client_key::ClientKey::new(shortint_parameters.0.to_owned());
let client_key = shortint::client_key::ClientKey::new(shortint_parameters.0);
let server_key = shortint::server_key::ServerKey::new(&client_key);
let heap_allocated_client_key = Box::new(ShortintClientKey(client_key));

View File

@@ -7,9 +7,26 @@ use std::os::raw::c_int;
use crate::shortint;
pub const SHORTINT_NATIVE_MODULUS: u64 = 0;
#[repr(C)]
pub enum ShortintEncryptionKeyChoice {
ShortintEncryptionKeyChoiceBig,
ShortintEncryptionKeyChoiceSmall,
}
pub struct ShortintParameters(pub(in crate::c_api) shortint::parameters::Parameters);
impl From<ShortintEncryptionKeyChoice> for crate::shortint::parameters::EncryptionKeyChoice {
fn from(value: ShortintEncryptionKeyChoice) -> Self {
match value {
ShortintEncryptionKeyChoice::ShortintEncryptionKeyChoiceBig => {
shortint::parameters::EncryptionKeyChoice::Big
}
ShortintEncryptionKeyChoice::ShortintEncryptionKeyChoiceSmall => {
shortint::parameters::EncryptionKeyChoice::Small
}
}
}
}
pub struct ShortintParameters(pub(in crate::c_api) shortint::parameters::PBSParameters);
#[no_mangle]
pub unsafe extern "C" fn shortint_get_parameters(
@@ -106,14 +123,10 @@ pub unsafe extern "C" fn shortint_create_parameters(
pbs_level: usize,
ks_base_log: usize,
ks_level: usize,
pfks_level: usize,
pfks_base_log: usize,
pfks_modular_std_dev: f64,
cbs_level: usize,
cbs_base_log: usize,
message_modulus: usize,
carry_modulus: usize,
modulus_power_of_2_exponent: usize,
encryption_key_choice: ShortintEncryptionKeyChoice,
result: *mut *mut ShortintParameters,
) -> c_int {
catch_panic(|| {
@@ -124,7 +137,7 @@ pub unsafe extern "C" fn shortint_create_parameters(
*result = std::ptr::null_mut();
let heap_allocated_parameters =
Box::new(ShortintParameters(shortint::parameters::Parameters {
Box::new(ShortintParameters(shortint::parameters::PBSParameters {
lwe_dimension: LweDimension(lwe_dimension),
glwe_dimension: GlweDimension(glwe_dimension),
polynomial_size: PolynomialSize(polynomial_size),
@@ -134,11 +147,6 @@ pub unsafe extern "C" fn shortint_create_parameters(
pbs_level: DecompositionLevelCount(pbs_level),
ks_base_log: DecompositionBaseLog(ks_base_log),
ks_level: DecompositionLevelCount(ks_level),
pfks_level: DecompositionLevelCount(pfks_level),
pfks_base_log: DecompositionBaseLog(pfks_base_log),
pfks_modular_std_dev: StandardDev(pfks_modular_std_dev),
cbs_level: DecompositionLevelCount(cbs_level),
cbs_base_log: DecompositionBaseLog(cbs_base_log),
message_modulus: crate::shortint::parameters::MessageModulus(message_modulus),
carry_modulus: crate::shortint::parameters::CarryModulus(carry_modulus),
ciphertext_modulus:
@@ -146,6 +154,7 @@ pub unsafe extern "C" fn shortint_create_parameters(
modulus_power_of_2_exponent,
)
.unwrap(),
encryption_key_choice: encryption_key_choice.into(),
}));
*result = Box::into_raw(heap_allocated_parameters);

View File

@@ -0,0 +1,189 @@
//! Module containing primitives pertaining to [`GLWE ciphertext
//! keyswitch`](`GlweKeyswitchKey#glwe-keyswitch`).
use crate::core_crypto::algorithms::polynomial_algorithms::*;
use crate::core_crypto::commons::math::decomposition::SignedDecomposer;
use crate::core_crypto::commons::numeric::UnsignedInteger;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::entities::*;
/// Keyswitch an [`GLWE ciphertext`](`GlweCiphertext`) encrytped under an
/// [`GLWE secret key`](`GlweSecretKey`) to another [`GLWE secret key`](`GlweSecretKey`).
///
/// # Formal Definition
///
/// See [`GLWE keyswitch key`](`GlweKeyswitchKey#glwe-keyswitch`).
///
/// # Example
///
/// ```
/// use tfhe::core_crypto::prelude::*;
///
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
/// // computations
/// // Define parameters for LweKeyswitchKey creation
/// let input_glwe_dimension = GlweDimension(2);
/// let poly_size = PolynomialSize(512);
/// let glwe_modular_std_dev = StandardDev(0.000007069849454709433);
/// let output_glwe_dimension = GlweDimension(1);
/// let decomp_base_log = DecompositionBaseLog(3);
/// let decomp_level_count = DecompositionLevelCount(5);
/// let ciphertext_modulus = CiphertextModulus::new_native();
///
/// // Create the PRNG
/// let mut seeder = new_seeder();
/// let seeder = seeder.as_mut();
/// let mut encryption_generator =
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
/// let mut secret_generator =
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
///
/// // Create the LweSecretKey
/// let input_glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key(
/// input_glwe_dimension,
/// poly_size,
/// &mut secret_generator,
/// );
/// let output_glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key(
/// output_glwe_dimension,
/// poly_size,
/// &mut secret_generator,
/// );
///
/// let ksk = allocate_and_generate_new_glwe_keyswitch_key(
/// &input_glwe_secret_key,
/// &output_glwe_secret_key,
/// decomp_base_log,
/// decomp_level_count,
/// glwe_modular_std_dev,
/// ciphertext_modulus,
/// &mut encryption_generator,
/// );
///
/// // Create the plaintext
/// let msg = 3u64;
/// let plaintext_list = PlaintextList::new(msg << 60, PlaintextCount(poly_size.0));
///
/// // Create a new GlweCiphertext
/// let mut input_glwe = GlweCiphertext::new(
/// 0u64,
/// input_glwe_dimension.to_glwe_size(),
/// poly_size,
/// ciphertext_modulus,
/// );
///
/// encrypt_glwe_ciphertext(
/// &input_glwe_secret_key,
/// &mut input_glwe,
/// &plaintext_list,
/// glwe_modular_std_dev,
/// &mut encryption_generator,
/// );
///
/// let mut output_glwe = GlweCiphertext::new(
/// 0u64,
/// output_glwe_secret_key.glwe_dimension().to_glwe_size(),
/// output_glwe_secret_key.polynomial_size(),
/// ciphertext_modulus,
/// );
///
/// keyswitch_glwe_ciphertext(&ksk, &mut input_glwe, &mut output_glwe);
///
/// let mut output_plaintext_list = PlaintextList::new(0u64, plaintext_list.plaintext_count());
///
/// let decrypted_plaintext = decrypt_glwe_ciphertext(
/// &output_glwe_secret_key,
/// &output_glwe,
/// &mut output_plaintext_list,
/// );
///
/// // Round and remove encoding
/// // First create a decomposer working on the high 4 bits corresponding to our encoding.
/// let decomposer = SignedDecomposer::new(DecompositionBaseLog(4), DecompositionLevelCount(1));
///
/// output_plaintext_list
/// .iter_mut()
/// .for_each(|elt| *elt.0 = decomposer.closest_representable(*elt.0));
///
/// // Get the raw vector
/// let mut cleartext_list = output_plaintext_list.into_container();
/// // Remove the encoding
/// cleartext_list.iter_mut().for_each(|elt| *elt = *elt >> 60);
/// // Get the list immutably
/// let cleartext_list = cleartext_list;
///
/// // Check we recovered the original message for each plaintext we encrypted
/// cleartext_list.iter().for_each(|&elt| assert_eq!(elt, msg));
/// ```
pub fn keyswitch_glwe_ciphertext<Scalar, KSKCont, InputCont, OutputCont>(
glwe_keyswitch_key: &GlweKeyswitchKey<KSKCont>,
input_glwe_ciphertext: &mut GlweCiphertext<InputCont>,
output_glwe_ciphertext: &mut GlweCiphertext<OutputCont>,
) where
Scalar: UnsignedInteger,
KSKCont: Container<Element = Scalar>,
InputCont: ContainerMut<Element = Scalar>,
OutputCont: ContainerMut<Element = Scalar>,
{
assert!(
glwe_keyswitch_key.input_key_glwe_dimension()
== input_glwe_ciphertext.glwe_size().to_glwe_dimension(),
"Mismatched input GlweDimension. \
GlweKeyswitchKey input GlweDimension: {:?}, input GlweCiphertext GlweDimension {:?}.",
glwe_keyswitch_key.input_key_glwe_dimension(),
input_glwe_ciphertext.glwe_size().to_glwe_dimension(),
);
assert!(
glwe_keyswitch_key.output_key_glwe_dimension()
== output_glwe_ciphertext.glwe_size().to_glwe_dimension(),
"Mismatched output GlweDimension. \
GlweKeyswitchKey output GlweDimension: {:?}, output GlweCiphertext GlweDimension {:?}.",
glwe_keyswitch_key.output_key_glwe_dimension(),
output_glwe_ciphertext.glwe_size().to_glwe_dimension(),
);
assert!(
glwe_keyswitch_key.polynomial_size() == input_glwe_ciphertext.polynomial_size(),
"Mismatched input PolynomialSize. \
GlweKeyswithcKey input PolynomialSize: {:?}, input GlweCiphertext PolynomialSize {:?}.",
glwe_keyswitch_key.polynomial_size(),
input_glwe_ciphertext.polynomial_size(),
);
assert!(
glwe_keyswitch_key.polynomial_size() == output_glwe_ciphertext.polynomial_size(),
"Mismatched output PolynomialSize. \
GlweKeyswitchKey output PolynomialSize: {:?}, output GlweCiphertext PolynomialSize {:?}.",
glwe_keyswitch_key.polynomial_size(),
output_glwe_ciphertext.polynomial_size(),
);
// Clear the output ciphertext, as it will get updated gradually
output_glwe_ciphertext.as_mut().fill(Scalar::ZERO);
// Copy the input body to the output ciphertext
polynomial_wrapping_add_assign(
&mut output_glwe_ciphertext.get_mut_body().as_mut_polynomial(),
&input_glwe_ciphertext.get_body().as_polynomial(),
);
// We instantiate a decomposer
let decomposer = SignedDecomposer::new(
glwe_keyswitch_key.decomposition_base_log(),
glwe_keyswitch_key.decomposition_level_count(),
);
for (keyswitch_key_block, input_mask_element) in glwe_keyswitch_key
.iter()
.zip(input_glwe_ciphertext.get_mask().as_polynomial_list().iter())
{
let mut decomposition_iter = decomposer.decompose_slice(input_mask_element.as_ref());
// loop over the number of levels in reverse (from highest to lowest)
for level_key_ciphertext in keyswitch_key_block.iter().rev() {
let decomposed = decomposition_iter.next_term().unwrap();
polynomial_list_wrapping_sub_scalar_mul_assign(
&mut output_glwe_ciphertext.as_mut_polynomial_list(),
&level_key_ciphertext.as_polynomial_list(),
&Polynomial::from_container(decomposed.as_slice()),
);
}
}
}

View File

@@ -0,0 +1,207 @@
//! Module containing primitives pertaining to [`GLWE keyswitch key generation`](`GlweKeyswitchKey`)
use crate::core_crypto::algorithms::*;
use crate::core_crypto::commons::dispersion::DispersionParameter;
use crate::core_crypto::commons::generators::EncryptionRandomGenerator;
use crate::core_crypto::commons::math::decomposition::{DecompositionLevel, DecompositionTerm};
use crate::core_crypto::commons::parameters::*;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::entities::*;
/// Fill a [`GLWE keyswitch key`](`GlweKeyswitchKey`) with an actual keyswitching key constructed
/// from an input and an output key [`GLWE secret key`](`GlweSecretKey`).
///
/// ```
/// use tfhe::core_crypto::prelude::*;
///
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
/// // computations
/// // Define parameters for LweKeyswitchKey creation
/// let input_glwe_dimension = GlweDimension(2);
/// let polynomial_size = PolynomialSize(1024);
/// let glwe_modular_std_dev = StandardDev(0.000007069849454709433);
/// let output_glwe_dimension = GlweDimension(1);
/// let decomp_base_log = DecompositionBaseLog(3);
/// let decomp_level_count = DecompositionLevelCount(5);
/// let ciphertext_modulus = CiphertextModulus::new_native();
///
/// // Create the PRNG
/// let mut seeder = new_seeder();
/// let seeder = seeder.as_mut();
/// let mut encryption_generator =
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
/// let mut secret_generator =
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
///
/// // Create the GlweSecretKey
/// let input_glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key(
/// input_glwe_dimension,
/// polynomial_size,
/// &mut secret_generator,
/// );
/// let output_glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key(
/// output_glwe_dimension,
/// polynomial_size,
/// &mut secret_generator,
/// );
///
/// let mut ksk = GlweKeyswitchKey::new(
/// 0u64,
/// decomp_base_log,
/// decomp_level_count,
/// input_glwe_dimension,
/// output_glwe_dimension,
/// polynomial_size,
/// ciphertext_modulus,
/// );
///
/// generate_glwe_keyswitch_key(
/// &input_glwe_secret_key,
/// &output_glwe_secret_key,
/// &mut ksk,
/// glwe_modular_std_dev,
/// &mut encryption_generator,
/// );
///
/// assert!(ksk.as_ref().iter().all(|&x| x == 0) == false);
/// ```
pub fn generate_glwe_keyswitch_key<Scalar, InputKeyCont, OutputKeyCont, KSKeyCont, Gen>(
input_glwe_sk: &GlweSecretKey<InputKeyCont>,
output_glwe_sk: &GlweSecretKey<OutputKeyCont>,
glwe_keyswitch_key: &mut GlweKeyswitchKey<KSKeyCont>,
noise_parameters: impl DispersionParameter,
generator: &mut EncryptionRandomGenerator<Gen>,
) where
Scalar: UnsignedTorus,
InputKeyCont: Container<Element = Scalar>,
OutputKeyCont: Container<Element = Scalar>,
KSKeyCont: ContainerMut<Element = Scalar>,
Gen: ByteRandomGenerator,
{
assert!(
glwe_keyswitch_key.input_key_glwe_dimension() == input_glwe_sk.glwe_dimension(),
"The destination GlweKeyswitchKey input GlweDimension is not equal \
to the input GlweSecretKey GlweDimension. Destination: {:?}, input: {:?}",
glwe_keyswitch_key.input_key_glwe_dimension(),
input_glwe_sk.glwe_dimension()
);
assert!(
glwe_keyswitch_key.output_key_glwe_dimension() == output_glwe_sk.glwe_dimension(),
"The destination GlweKeyswitchKey output GlweDimension is not equal \
to the output GlweSecretKey GlweDimension. Destination: {:?}, output: {:?}",
glwe_keyswitch_key.output_key_glwe_dimension(),
input_glwe_sk.glwe_dimension()
);
assert!(
glwe_keyswitch_key.polynomial_size() == input_glwe_sk.polynomial_size(),
"The destination GlweKeyswitchKey input PolynomialSize is not equal \
to the input GlweSecretKey PolynomialSize. Destination: {:?}, input: {:?}",
glwe_keyswitch_key.polynomial_size(),
input_glwe_sk.polynomial_size(),
);
assert!(
glwe_keyswitch_key.polynomial_size() == output_glwe_sk.polynomial_size(),
"The distination GlweKeyswitchKey output PolynomialSize is not equal \
to the output GlweSecretKey PolynomialSize. Destination: {:?}, output: {:?}",
glwe_keyswitch_key.polynomial_size(),
output_glwe_sk.polynomial_size(),
);
let decomp_base_log = glwe_keyswitch_key.decomposition_base_log();
let decomp_level_count = glwe_keyswitch_key.decomposition_level_count();
/*
// forking the generator and using loop_generator works to generate glwe keyswitch keys but
// break lwe_trace_packing_keyswotch_key_generation
let gen_iter = generator
.fork_glweks_to_glweks_chunks::<Scalar>(
decomp_level_count,
input_glwe_sk.glwe_dimension(),
output_glwe_sk.glwe_dimension().to_glwe_size(),
input_glwe_sk.polynomial_size(),
)
.unwrap();
*/
// Iterate over the input key elements and the destination glwe_keyswitch_key memory
//for ((input_key_polynomial, mut keyswitch_key_block), mut _loop_generator) in input_glwe_sk
for (input_key_polynomial, mut keyswitch_key_block) in input_glwe_sk
.as_polynomial_list()
.iter()
.zip(glwe_keyswitch_key.iter_mut())
//.zip(gen_iter)
{
// The plaintexts used to encrypt a key element will be stored in this buffer
let mut decomposition_polynomials_buffer = PolynomialList::new(
Scalar::ZERO,
input_glwe_sk.polynomial_size(),
PolynomialCount(decomp_level_count.0),
);
// We fill the buffer with the powers of the key elmements
for (level, mut message_polynomial) in (1..=decomp_level_count.0)
.map(DecompositionLevel)
.zip(decomposition_polynomials_buffer.as_mut_view().iter_mut())
{
for (message, input_key_element) in message_polynomial
.iter_mut()
.zip(input_key_polynomial.iter())
{
*message = DecompositionTerm::new(level, decomp_base_log, *input_key_element)
.to_recomposition_summand();
}
}
let decomposition_plaintexts_buffer =
PlaintextList::from_container(decomposition_polynomials_buffer.into_container());
encrypt_glwe_ciphertext_list(
output_glwe_sk,
&mut keyswitch_key_block,
&decomposition_plaintexts_buffer,
noise_parameters,
//&mut loop_generator,
generator,
);
}
}
/// Allocate a new [`GLWE keyswitch key`](`GlweKeyswitchKey`) and fill it with an actual
/// keyswitching key constructed from an input and an output key
/// [`GLWE secret key`](`GlweSecretKey`).
///
/// See [`keyswitch_glwe_ciphertext`] for usage.
pub fn allocate_and_generate_new_glwe_keyswitch_key<Scalar, InputKeyCont, OutputKeyCont, Gen>(
input_glwe_sk: &GlweSecretKey<InputKeyCont>,
output_glwe_sk: &GlweSecretKey<OutputKeyCont>,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
noise_parameters: impl DispersionParameter,
ciphertext_modulus: CiphertextModulus<Scalar>,
generator: &mut EncryptionRandomGenerator<Gen>,
) -> GlweKeyswitchKeyOwned<Scalar>
where
Scalar: UnsignedTorus,
InputKeyCont: Container<Element = Scalar>,
OutputKeyCont: Container<Element = Scalar>,
Gen: ByteRandomGenerator,
{
let mut new_glwe_keyswitch_key = GlweKeyswitchKeyOwned::new(
Scalar::ZERO,
decomp_base_log,
decomp_level_count,
input_glwe_sk.glwe_dimension(),
output_glwe_sk.glwe_dimension(),
output_glwe_sk.polynomial_size(),
ciphertext_modulus,
);
generate_glwe_keyswitch_key(
input_glwe_sk,
output_glwe_sk,
&mut new_glwe_keyswitch_key,
noise_parameters,
generator,
);
new_glwe_keyswitch_key
}

View File

@@ -0,0 +1,156 @@
//! Module containing primitives pertaining to [`GLWE relinearisation key
//! generation`](`GlweRelinearisationKey`).
use crate::core_crypto::algorithms::polynomial_algorithms::*;
use crate::core_crypto::algorithms::*;
use crate::core_crypto::commons::dispersion::DispersionParameter;
use crate::core_crypto::commons::generators::EncryptionRandomGenerator;
use crate::core_crypto::commons::parameters::*;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::entities::*;
use crate::core_crypto::prelude::{DecompositionBaseLog, DecompositionLevelCount, PolynomialCount};
/// Fill a [`GLWE relinearisation key`](`GlweRelinearisationKey`)
/// with an actual key.
///
/// # Example
///
/// ```
/// use tfhe::core_crypto::prelude::*;
///
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
/// // computations
/// // Define parameters for GlweCiphertext creation
/// let glwe_size = GlweSize(3);
/// let polynomial_size = PolynomialSize(1024);
/// let decomp_base_log = DecompositionBaseLog(3);
/// let decomp_level_count = DecompositionLevelCount(7);
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
/// let ciphertext_modulus = CiphertextModulus::new_native();
///
/// // Create the PRNG
/// let mut seeder = new_seeder();
/// let seeder = seeder.as_mut();
/// let mut encryption_generator =
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
/// let mut secret_generator =
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
///
/// // Create the GlweSecretKey
/// let glwe_secret_key: GlweSecretKey<Vec<u64>> = allocate_and_generate_new_binary_glwe_secret_key(
/// glwe_size.to_glwe_dimension(),
/// polynomial_size,
/// &mut secret_generator,
/// );
///
/// allocate_and_generate_glwe_relinearisation_key(
/// &glwe_secret_key,
/// decomp_base_log,
/// decomp_level_count,
/// glwe_modular_std_dev,
/// ciphertext_modulus,
/// &mut encryption_generator,
/// );
/// ```
pub fn generate_glwe_relinearisation_key<Scalar, GlweKeyCont, RelinKeyCont, Gen>(
glwe_secret_key: &GlweSecretKey<GlweKeyCont>,
glwe_relinearisation_key: &mut GlweRelinearisationKey<RelinKeyCont>,
noise_parameters: impl DispersionParameter,
generator: &mut EncryptionRandomGenerator<Gen>,
) where
Scalar: UnsignedTorus,
GlweKeyCont: Container<Element = Scalar>,
RelinKeyCont: ContainerMut<Element = Scalar>,
Gen: ByteRandomGenerator,
{
assert_eq!(
glwe_secret_key.glwe_dimension(),
glwe_relinearisation_key.glwe_dimension()
);
assert_eq!(
glwe_secret_key.polynomial_size(),
glwe_relinearisation_key.polynomial_size()
);
// We retrieve decomposition arguments
let glwe_dimension = glwe_relinearisation_key.glwe_dimension();
let decomp_level_count = glwe_relinearisation_key.decomposition_level_count();
let decomp_base_log = glwe_relinearisation_key.decomposition_base_log();
let polynomial_size = glwe_relinearisation_key.polynomial_size();
let ciphertext_modulus = glwe_relinearisation_key.ciphertext_modulus();
// Construct the "glwe secret key" we want to keyswitch from, this is made up of the square
// and cross terms appearing when squaring glwe_secret_key
let mut input_sk_poly_list = PolynomialList::new(
Scalar::ZERO,
polynomial_size,
PolynomialCount(glwe_dimension.0 * (glwe_dimension.0 + 1) / 2),
);
let mut input_sk_poly_list_iter = input_sk_poly_list.iter_mut();
for i in 0..glwe_dimension.0 {
for j in 0..i + 1 {
let mut input_key_pol = input_sk_poly_list_iter.next().unwrap();
polynomial_wrapping_sub_mul_assign(
&mut input_key_pol,
&glwe_secret_key.as_polynomial_list().get(i),
&glwe_secret_key.as_polynomial_list().get(j),
);
}
}
let input_glwe_sk = GlweSecretKey::from_container(input_sk_poly_list.as_ref(), polynomial_size);
let mut glwe_ks_key = GlweKeyswitchKey::from_container(
glwe_relinearisation_key.as_mut(),
decomp_base_log,
decomp_level_count,
glwe_dimension.to_glwe_size(),
polynomial_size,
ciphertext_modulus,
);
generate_glwe_keyswitch_key(
&input_glwe_sk,
glwe_secret_key,
&mut glwe_ks_key,
noise_parameters,
generator,
);
}
pub fn allocate_and_generate_glwe_relinearisation_key<Scalar, KeyCont, Gen>(
glwe_secret_key: &GlweSecretKey<KeyCont>,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
noise_parameters: impl DispersionParameter,
ciphertext_modulus: CiphertextModulus<Scalar>,
generator: &mut EncryptionRandomGenerator<Gen>,
) -> GlweRelinearisationKeyOwned<Scalar>
where
Scalar: UnsignedTorus,
KeyCont: Container<Element = Scalar>,
Gen: ByteRandomGenerator,
{
let mut glwe_relinearisation_key = GlweRelinearisationKeyOwned::new(
Scalar::ZERO,
decomp_base_log,
decomp_level_count,
glwe_secret_key.glwe_dimension().to_glwe_size(),
glwe_secret_key.polynomial_size(),
ciphertext_modulus,
);
generate_glwe_relinearisation_key(
glwe_secret_key,
&mut glwe_relinearisation_key,
noise_parameters,
generator,
);
glwe_relinearisation_key
}
/*
* Parallel variant of [`generate_glwe_relinearisation_key`]. You may want to use this
* variant for better key generation times.
*/

View File

@@ -0,0 +1,600 @@
use crate::core_crypto::algorithms::polynomial_algorithms::*;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::entities::*;
use crate::core_crypto::prelude::*;
/// Compute the tensor product of the left-hand side [`GLWE ciphertext`](`GlweCiphertext`) with the
/// right-hand side [`GLWE ciphertext`](`GlweCiphertext`)
/// writing the result in the output [`GlweCiphertext<Vec<Scalar>>`](`GlweCiphertext<Vec<Scalar>>`).
///
/// # Example
///
/// ```
/// use tfhe::core_crypto::algorithms::polynomial_algorithms::*;
/// use tfhe::core_crypto::prelude::*;
///
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
/// // computations
/// // Define parameters for LweCiphertext creation
/// let glwe_size = GlweSize(3);
/// let polynomial_size = PolynomialSize(512);
/// let glwe_modular_std_dev = StandardDev(0.000000000000000000029403601535432533);
/// let decomp_base_log = DecompositionBaseLog(3);
/// let decomp_level_count = DecompositionLevelCount(7);
/// let ciphertext_modulus = CiphertextModulus::new_native();
///
/// let log_delta1 = 59;
/// let log_delta2 = 60;
/// let log_delta = std::cmp::min(log_delta1, log_delta2);
/// let output_log_delta = log_delta1 + log_delta2 - log_delta;
///
/// // Create the PRNG
/// let mut seeder = new_seeder();
/// let seeder = seeder.as_mut();
/// let mut encryption_generator =
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
/// let mut secret_generator =
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
///
/// // Create the GlweSecretKey
/// let glwe_secret_key = allocate_and_generate_new_binary_glwe_secret_key(
/// glwe_size.to_glwe_dimension(),
/// polynomial_size,
/// &mut secret_generator,
/// );
///
/// // Create the first plaintext, we encrypt a single integer rather than a general polynomial
/// let msg_1 = 3u64;
/// let encoded_msg_1 = msg_1 << log_delta1;
///
/// let mut plaintext_list_1 = PlaintextList::new(0u64, PlaintextCount(polynomial_size.0));
/// plaintext_list_1.as_mut()[0] = encoded_msg_1;
///
/// // Create the first GlweCiphertext
/// let mut glwe_1 = GlweCiphertext::new(0u64, glwe_size, polynomial_size, ciphertext_modulus);
///
/// encrypt_glwe_ciphertext(
/// &glwe_secret_key,
/// &mut glwe_1,
/// &plaintext_list_1,
/// glwe_modular_std_dev,
/// &mut encryption_generator,
/// );
///
/// // Create the second plaintext
/// let msg_2 = 2u64;
/// let encoded_msg_2 = msg_2 << log_delta2;
///
/// let mut plaintext_list_2 = PlaintextList::new(0u64, PlaintextCount(polynomial_size.0));
/// plaintext_list_2.as_mut()[0] = encoded_msg_2;
///
/// // Create the second GlweCiphertext
/// let mut glwe_2 = GlweCiphertext::new(0u64, glwe_size, polynomial_size, ciphertext_modulus);
///
/// encrypt_glwe_ciphertext(
/// &glwe_secret_key,
/// &mut glwe_2,
/// &plaintext_list_2,
/// glwe_modular_std_dev,
/// &mut encryption_generator,
/// );
///
/// // Perform the tensor product
/// let scale = 1u64 << log_delta;
/// let tensor_output = glwe_tensor_product(&glwe_1, &glwe_2, scale);
///
/// // Compute the tensor product key
/// let tensor_glwe_dim = GlweDimension((glwe_size.0 - 1) * (glwe_size.0 + 2) / 2);
/// let mut tensor_key_poly_list =
/// PolynomialList::new(0u64, polynomial_size, PolynomialCount(tensor_glwe_dim.0));
/// let mut key_iter = tensor_key_poly_list.iter_mut();
///
/// for i in 0..glwe_size.0 - 1 {
/// for j in 0..i + 1 {
/// let mut key_pol = key_iter.next().unwrap();
/// polynomial_wrapping_sub_mul_assign(
/// &mut key_pol,
/// &glwe_secret_key.as_polynomial_list().get(i),
/// &glwe_secret_key.as_polynomial_list().get(j),
/// );
/// }
/// let mut key_pol = key_iter.next().unwrap();
/// polynomial_wrapping_add_assign(&mut key_pol, &glwe_secret_key.as_polynomial_list().get(i));
/// }
///
/// let tensor_key = GlweSecretKey::from_container(tensor_key_poly_list.as_ref(), polynomial_size);
///
/// // Decrypt the tensor product ciphertext
/// let mut output_plaintext = PlaintextList::new(0u64, PlaintextCount(polynomial_size.0));
///
/// // First create a decomposer working on the high 4 bits corresponding to our encoding.
/// let decomposer = SignedDecomposer::new(DecompositionBaseLog(2), DecompositionLevelCount(4));
///
/// decrypt_glwe_ciphertext(&tensor_key, &tensor_output, &mut output_plaintext);
/// output_plaintext
/// .iter_mut()
/// .for_each(|elt| *elt.0 = decomposer.closest_representable(*elt.0));
///
/// // Get the raw vector
/// let mut cleartext = output_plaintext.into_container();
/// // Remove the encoding
/// cleartext
/// .iter_mut()
/// .for_each(|elt| *elt = *elt >> output_log_delta);
/// // Get the list immutably
/// let cleartext = cleartext;
///
/// // Compute what the product should be
/// let pt1 = Polynomial::from_container(
/// plaintext_list_1
/// .into_container()
/// .iter()
/// .map(|&x| <u64 as CastInto<u128>>::cast_into(x))
/// .collect::<Vec<_>>(),
/// );
/// let pt2 = Polynomial::from_container(
/// plaintext_list_2
/// .into_container()
/// .iter()
/// .map(|&x| <u64 as CastInto<u128>>::cast_into(x))
/// .collect::<Vec<_>>(),
/// );
///
/// let mut product = Polynomial::new(0u128, polynomial_size);
/// polynomial_wrapping_mul(&mut product, &pt1, &pt2);
///
/// let mut scaled_product = Polynomial::new(0u64, polynomial_size);
/// scaled_product
/// .as_mut()
/// .iter_mut()
/// .zip(product.as_ref().iter())
/// .for_each(|(dest, &source)| {
/// *dest = u64::cast_from(source / <u64 as CastInto<u128>>::cast_into(scale))
/// >> output_log_delta
/// });
///
/// // Check we recovered the correct message
/// cleartext
/// .iter()
/// .zip(scaled_product.iter())
/// .for_each(|(&elt, coeff)| assert_eq!(elt, *coeff));
///
/// let glwe_relin_key = allocate_and_generate_glwe_relinearisation_key(
/// &glwe_secret_key,
/// decomp_base_log,
/// decomp_level_count,
/// glwe_modular_std_dev,
/// ciphertext_modulus,
/// &mut encryption_generator,
/// );
///
/// let mut output_glwe_ciphertext =
/// GlweCiphertext::new(0u64, glwe_size, polynomial_size, ciphertext_modulus);
///
/// glwe_relinearisation(&tensor_output, &glwe_relin_key, &mut output_glwe_ciphertext);
///
/// // Decrypt the output glwe ciphertext
/// let mut output_plaintext = PlaintextList::new(0u64, PlaintextCount(polynomial_size.0));
///
/// decrypt_glwe_ciphertext(
/// &glwe_secret_key,
/// &output_glwe_ciphertext,
/// &mut output_plaintext,
/// );
/// output_plaintext
/// .iter_mut()
/// .for_each(|elt| *elt.0 = decomposer.closest_representable(*elt.0));
///
/// // Get the raw vector
/// let mut cleartext = output_plaintext.into_container();
/// // Remove the encoding
/// cleartext
/// .iter_mut()
/// .for_each(|elt| *elt = *elt >> output_log_delta);
/// // Get the list immutably
/// let cleartext = cleartext;
///
/// // Check we recovered the correct message
/// cleartext
/// .iter()
/// .zip(scaled_product.iter())
/// .for_each(|(&elt, coeff)| assert_eq!(elt, *coeff));
/// ```
/// based on algorithm 1 of `<https://eprint.iacr.org/2021/729.pdf>` in the eprint paper the result
/// of the division is rounded,
/// here the division in u128 performs a floor hence the induced error might be twice as large
pub fn glwe_tensor_product<InputCont, Scalar>(
input_glwe_ciphertext_lhs: &GlweCiphertext<InputCont>,
input_glwe_ciphertext_rhs: &GlweCiphertext<InputCont>,
scale: Scalar,
) -> GlweCiphertext<Vec<Scalar>>
where
Scalar: UnsignedTorus + CastInto<u128> + CastFrom<u128>,
InputCont: Container<Element = Scalar>,
{
assert!(
input_glwe_ciphertext_lhs.polynomial_size().0
== input_glwe_ciphertext_rhs.polynomial_size().0,
"The input glwe ciphertexts do not have the same polynomial size. The polynomial size of \
the lhs is {}, while for the rhs it is {}.",
input_glwe_ciphertext_lhs.polynomial_size().0,
input_glwe_ciphertext_rhs.polynomial_size().0
);
assert!(
input_glwe_ciphertext_lhs.glwe_size().0 == input_glwe_ciphertext_rhs.glwe_size().0,
"The input glwe ciphertexts do not have the same glwe size. The glwe size of the lhs is \
{}, while for the rhs it is {}.",
input_glwe_ciphertext_lhs.glwe_size().0,
input_glwe_ciphertext_rhs.glwe_size().0
);
assert_eq!(
input_glwe_ciphertext_lhs.ciphertext_modulus(),
input_glwe_ciphertext_rhs.ciphertext_modulus()
);
let k = input_glwe_ciphertext_lhs.glwe_size().to_glwe_dimension().0;
// This is k + k*(k-1)/2 + k: k square terms, k*(k-1)/2 cross terms, k linear terms
let new_k = GlweDimension(k * (k + 3) / 2);
let mut output_glwe_ciphertext = GlweCiphertextOwned::new(
Scalar::ZERO,
new_k.to_glwe_size(),
input_glwe_ciphertext_lhs.polynomial_size(),
input_glwe_ciphertext_lhs.ciphertext_modulus(),
);
let mut output_mask = output_glwe_ciphertext.get_mut_mask();
let mut output_mask_poly_list = output_mask.as_mut_polynomial_list();
let mut iter_output_mask = output_mask_poly_list.iter_mut();
let input_lhs = PolynomialList::from_container(
input_glwe_ciphertext_lhs
.get_mask()
.as_ref()
.iter()
.map(|&x| <Scalar as CastInto<u128>>::cast_into(x))
.collect::<Vec<_>>(),
input_glwe_ciphertext_lhs.polynomial_size(),
);
let input_rhs = PolynomialList::from_container(
input_glwe_ciphertext_rhs
.get_mask()
.as_ref()
.iter()
.map(|&x| <Scalar as CastInto<u128>>::cast_into(x))
.collect::<Vec<_>>(),
input_glwe_ciphertext_rhs.polynomial_size(),
);
for (i, a_lhs_i) in input_lhs.iter().enumerate() {
for (j, a_rhs_j) in input_rhs.iter().enumerate() {
if i == j {
//tensor elements corresponding to key -s_i^2
let mut temp_poly_sq = Polynomial::new(0u128, a_lhs_i.polynomial_size());
polynomial_wrapping_add_mul_assign(&mut temp_poly_sq, &a_lhs_i, &a_rhs_j);
let mut output_poly_sq = iter_output_mask.next().unwrap();
output_poly_sq
.as_mut()
.iter_mut()
.zip(temp_poly_sq.as_ref().iter())
.for_each(|(dest, &source)| {
*dest =
Scalar::cast_from(source / <Scalar as CastInto<u128>>::cast_into(scale))
});
//tensor elements corresponding to key s_i
let mut temp_poly_s1 = Polynomial::new(0u128, a_lhs_i.polynomial_size());
polynomial_wrapping_add_mul_assign(
&mut temp_poly_s1,
&a_lhs_i,
&Polynomial::from_container(
input_glwe_ciphertext_rhs
.get_body()
.as_ref()
.iter()
.map(|&x| <Scalar as CastInto<u128>>::cast_into(x))
.collect::<Vec<_>>(),
),
);
let mut temp_poly_s2 = Polynomial::new(0u128, a_lhs_i.polynomial_size());
polynomial_wrapping_add_mul_assign(
&mut temp_poly_s2,
&Polynomial::from_container(
input_glwe_ciphertext_lhs
.get_body()
.as_ref()
.iter()
.map(|&x| <Scalar as CastInto<u128>>::cast_into(x))
.collect::<Vec<_>>(),
),
&a_rhs_j,
);
polynomial_wrapping_add_assign(&mut temp_poly_s1, &temp_poly_s2);
let mut output_poly_s = iter_output_mask.next().unwrap();
output_poly_s
.as_mut()
.iter_mut()
.zip(temp_poly_s1.as_ref().iter())
.for_each(|(dest, &source)| {
*dest =
Scalar::cast_from(source / <Scalar as CastInto<u128>>::cast_into(scale))
});
} else {
//when i and j are different we only compute the terms where j < i
if j < i {
//tensor element corresponding to key -s_i*s_j
let mut temp_poly = Polynomial::new(0u128, a_lhs_i.polynomial_size());
polynomial_wrapping_add_mul_assign(&mut temp_poly, &a_lhs_i, &a_rhs_j);
polynomial_wrapping_add_mul_assign(
&mut temp_poly,
&input_lhs.get(j),
&input_rhs.get(i),
);
let mut output_poly = iter_output_mask.next().unwrap();
output_poly
.as_mut()
.iter_mut()
.zip(temp_poly.as_ref().iter())
.for_each(|(dest, &source)| {
*dest = Scalar::cast_from(
source / <Scalar as CastInto<u128>>::cast_into(scale),
)
});
}
}
}
}
//tensor element corresponding to the body
let mut temp_poly_body = Polynomial::new(0u128, input_glwe_ciphertext_lhs.polynomial_size());
polynomial_wrapping_add_mul_assign(
&mut temp_poly_body,
&Polynomial::from_container(
input_glwe_ciphertext_lhs
.get_body()
.as_ref()
.iter()
.map(|&x| <Scalar as CastInto<u128>>::cast_into(x))
.collect::<Vec<_>>(),
),
&Polynomial::from_container(
input_glwe_ciphertext_rhs
.get_body()
.as_ref()
.iter()
.map(|&x| <Scalar as CastInto<u128>>::cast_into(x))
.collect::<Vec<_>>(),
),
);
let mut output_body = output_glwe_ciphertext.get_mut_body();
let mut output_poly_body = output_body.as_mut_polynomial();
output_poly_body
.as_mut()
.iter_mut()
.zip(temp_poly_body.as_ref().iter())
.for_each(|(dest, &source)| {
*dest = Scalar::cast_from(source / <Scalar as CastInto<u128>>::cast_into(scale))
});
output_glwe_ciphertext
}
/// Relinearise the [`GLWE ciphertext`](`GlweCiphertext`) that is output by the
/// glwe_tensor_product operation using a [`GLWE relinearisation key`](`GlweRelinearisationKey`).
pub fn glwe_relinearisation<InputCont, KeyCont, OutputCont, Scalar>(
input_glwe_ciphertext: &GlweCiphertext<InputCont>,
relinearisation_key: &GlweRelinearisationKey<KeyCont>,
output_glwe_ciphertext: &mut GlweCiphertext<OutputCont>,
) where
Scalar: UnsignedTorus,
InputCont: Container<Element = Scalar>,
KeyCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = Scalar>,
{
assert_eq!(
relinearisation_key.glwe_dimension().0 * (relinearisation_key.glwe_dimension().0 + 3) / 2,
input_glwe_ciphertext.glwe_size().to_glwe_dimension().0
);
assert_eq!(
relinearisation_key.glwe_size(),
output_glwe_ciphertext.glwe_size()
);
assert_eq!(
relinearisation_key.polynomial_size(),
input_glwe_ciphertext.polynomial_size()
);
assert_eq!(
relinearisation_key.polynomial_size(),
output_glwe_ciphertext.polynomial_size()
);
// Clear the output ciphertext, as it will get updated gradually
output_glwe_ciphertext.as_mut().fill(Scalar::ZERO);
// Copy the input body to the output ciphertext
polynomial_wrapping_add_assign(
&mut output_glwe_ciphertext.get_mut_body().as_mut_polynomial(),
&input_glwe_ciphertext.get_body().as_polynomial(),
);
// We instantiate a decomposer
let decomposer = SignedDecomposer::new(
relinearisation_key.decomposition_base_log(),
relinearisation_key.decomposition_level_count(),
);
let mut relin_key_iter = relinearisation_key.iter();
let input_glwe_mask = input_glwe_ciphertext.get_mask();
let input_glwe_mask_poly_list = input_glwe_mask.as_polynomial_list();
let mut input_poly_iter = input_glwe_mask_poly_list.iter();
for i in 0..relinearisation_key.glwe_size().0 - 1 {
for _ in 0..i + 1 {
let ksk = relin_key_iter.next().unwrap();
let pol = input_poly_iter.next().unwrap();
let mut decomposition_iter = decomposer.decompose_slice(pol.as_ref());
// loop over the number of levels in reverse (from highest to lowest)
for level_key_ciphertext in ksk.iter().rev() {
let decomposed = decomposition_iter.next_term().unwrap();
polynomial_list_wrapping_sub_scalar_mul_assign(
&mut output_glwe_ciphertext.as_mut_polynomial_list(),
&level_key_ciphertext.as_polynomial_list(),
&Polynomial::from_container(decomposed.as_slice()),
);
}
}
let pol = input_poly_iter.next().unwrap();
polynomial_wrapping_add_assign(
&mut output_glwe_ciphertext.as_mut_polynomial_list().get_mut(i),
&pol,
)
}
}
pub fn tensor_mult_with_relin<InputCont, KeyCont, OutputCont, Scalar>(
input_glwe_ciphertext_lhs: &GlweCiphertext<InputCont>,
input_glwe_ciphertext_rhs: &GlweCiphertext<InputCont>,
scale: Scalar,
relinearisation_key: &GlweRelinearisationKey<KeyCont>,
output_glwe_ciphertext: &mut GlweCiphertext<OutputCont>,
) where
Scalar: UnsignedTorus + CastInto<u128> + CastFrom<u128>,
InputCont: Container<Element = Scalar>,
KeyCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = Scalar>,
{
let tensor_output = glwe_tensor_product(
&input_glwe_ciphertext_lhs,
&input_glwe_ciphertext_rhs,
scale,
);
glwe_relinearisation(&tensor_output, &relinearisation_key, output_glwe_ciphertext);
}
pub fn packed_mult<InputCont, KeyCont, OutputCont, Scalar>(
input_lwe_ciphertext_list_1: &LweCiphertextList<InputCont>,
input_lwe_ciphertext_list_2: &LweCiphertextList<InputCont>,
lwe_pubfpksk: &LwePublicFunctionalPackingKeyswitchKey<KeyCont>,
relinearisation_key: &GlweRelinearisationKey<KeyCont>,
scale: Scalar,
output_lwe_ciphertext_list: &mut LweCiphertextList<OutputCont>,
) where
Scalar: UnsignedTorus + CastInto<u128> + CastFrom<u128>,
KeyCont: Container<Element = Scalar>,
InputCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = Scalar>,
{
let mut packed_glwe_1 =
GlweCiphertext::new(Scalar::ZERO,
lwe_pubfpksk.output_glwe_size(),
lwe_pubfpksk.output_polynomial_size(),
lwe_pubfpksk.ciphertext_modulus());
public_functional_keyswitch_lwe_ciphertexts_into_glwe_ciphertext(
&lwe_pubfpksk,
&mut packed_glwe_1,
&input_lwe_ciphertext_list_1,
| x: Vec<Scalar>| {
let mut packed1: Vec<Scalar> = vec![Scalar::ZERO;lwe_pubfpksk.output_polynomial_size().0];
x.iter().enumerate().for_each(|(iter,y)| packed1[iter] = *y);
Polynomial::from_container(packed1)
}
);
let mut packed_glwe_2 =
GlweCiphertext::new(Scalar::ZERO,
lwe_pubfpksk.output_glwe_size(),
lwe_pubfpksk.output_polynomial_size(),
lwe_pubfpksk.ciphertext_modulus());
public_functional_keyswitch_lwe_ciphertexts_into_glwe_ciphertext(
&lwe_pubfpksk,
&mut packed_glwe_2,
&input_lwe_ciphertext_list_2,
| x| {
let mut packed2: Vec<Scalar> = vec![Scalar::ZERO;lwe_pubfpksk.output_polynomial_size().0];
x.iter().enumerate().for_each(|(iter,y)| packed2[input_lwe_ciphertext_list_1.lwe_ciphertext_count().0*iter] = *y);
Polynomial::from_container(packed2)
},
);
let mut relin_glwe_ciphertext =
GlweCiphertext::new(Scalar::ZERO, lwe_pubfpksk.output_glwe_size(),
lwe_pubfpksk.output_polynomial_size(),
lwe_pubfpksk.ciphertext_modulus());
tensor_mult_with_relin(
&packed_glwe_1,
&packed_glwe_2,
scale,
&relinearisation_key,
&mut relin_glwe_ciphertext,
);
output_lwe_ciphertext_list.iter_mut().enumerate().
for_each(|(iter, mut el)| extract_lwe_sample_from_glwe_ciphertext(&relin_glwe_ciphertext,
&mut el,
MonomialDegree(iter*(input_lwe_ciphertext_list_1.lwe_ciphertext_count().0+1))));
}
pub fn packed_sum_product<InputCont, KeyCont, OutputCont, Scalar>(
input_lwe_ciphertext_list_1: &LweCiphertextList<InputCont>,
input_lwe_ciphertext_list_2: &LweCiphertextList<InputCont>,
lwe_pubfpksk: &LwePublicFunctionalPackingKeyswitchKey<KeyCont>,
relinearisation_key: &GlweRelinearisationKey<KeyCont>,
scale: Scalar,
output_lwe_ciphertext: &mut LweCiphertext<OutputCont>,
) where
Scalar: UnsignedTorus + CastInto<u128> + CastFrom<u128>,
KeyCont: Container<Element = Scalar>,
InputCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = Scalar>,
{
let mut packed_glwe_1 =
GlweCiphertext::new(Scalar::ZERO,
lwe_pubfpksk.output_glwe_size(),
lwe_pubfpksk.output_polynomial_size(),
lwe_pubfpksk.ciphertext_modulus());
public_functional_keyswitch_lwe_ciphertexts_into_glwe_ciphertext(
&lwe_pubfpksk,
&mut packed_glwe_1,
&input_lwe_ciphertext_list_1,
| x: Vec<Scalar>| {
let mut packed1: Vec<Scalar> = vec![Scalar::ZERO;lwe_pubfpksk.output_polynomial_size().0];
x.iter().enumerate().for_each(|(iter,y)| packed1[iter] = *y);
Polynomial::from_container(packed1)
}
);
let mut packed_glwe_2 =
GlweCiphertext::new(Scalar::ZERO,
lwe_pubfpksk.output_glwe_size(),
lwe_pubfpksk.output_polynomial_size(),
lwe_pubfpksk.ciphertext_modulus());
public_functional_keyswitch_lwe_ciphertexts_into_glwe_ciphertext(
&lwe_pubfpksk,
&mut packed_glwe_2,
&input_lwe_ciphertext_list_2,
| x| {
let mut packed2: Vec<Scalar> = vec![Scalar::ZERO;lwe_pubfpksk.output_polynomial_size().0];
x.iter().enumerate().for_each(|(iter,y)|
packed2[input_lwe_ciphertext_list_1.lwe_ciphertext_count().0-1-iter] = *y);
Polynomial::from_container(packed2)
},
);
let mut relin_glwe_ciphertext =
GlweCiphertext::new(Scalar::ZERO, lwe_pubfpksk.output_glwe_size(),
lwe_pubfpksk.output_polynomial_size(),
lwe_pubfpksk.ciphertext_modulus());
tensor_mult_with_relin(
&packed_glwe_1,
&packed_glwe_2,
scale,
&relinearisation_key,
&mut relin_glwe_ciphertext,
);
extract_lwe_sample_from_glwe_ciphertext(&relin_glwe_ciphertext,
output_lwe_ciphertext,
MonomialDegree(input_lwe_ciphertext_list_1.lwe_ciphertext_count().0-1));
}

View File

@@ -779,16 +779,23 @@ pub fn encrypt_lwe_ciphertext_with_public_key<Scalar, KeyCont, OutputCont, Gen>(
output.as_mut().fill(Scalar::ZERO);
let mut tmp_zero_encryption =
LweCiphertext::new(Scalar::ZERO, output.lwe_size(), output.ciphertext_modulus());
let mut ct_choice = vec![Scalar::ZERO; lwe_public_key.zero_encryption_count().0];
generator.fill_slice_with_random_uniform_binary(&mut ct_choice);
// Add the public encryption of zeros to get the zero encryption
for (&chosen, public_encryption_of_zero) in ct_choice.iter().zip(lwe_public_key.iter()) {
// TODO: can leak choice of ciphertexts
if chosen == Scalar::ONE {
lwe_ciphertext_add_assign(output, &public_encryption_of_zero);
}
// chosen is 1 if chosen, 0 otherwise, so use a multiplication to avoid having a branch
// depending on a value that's supposed to remain secret
lwe_ciphertext_cleartext_mul(
&mut tmp_zero_encryption,
&public_encryption_of_zero,
Cleartext(chosen),
);
lwe_ciphertext_add_assign(output, &tmp_zero_encryption);
}
let body = output.get_mut_body();
@@ -905,7 +912,7 @@ pub fn encrypt_lwe_ciphertext_with_seeded_public_key<Scalar, KeyCont, OutputCont
let ciphertext_modulus = output.ciphertext_modulus();
let mut tmp_ciphertext =
let mut tmp_zero_encryption =
LweCiphertext::new(Scalar::ZERO, lwe_public_key.lwe_size(), ciphertext_modulus);
let mut random_generator =
@@ -913,7 +920,7 @@ pub fn encrypt_lwe_ciphertext_with_seeded_public_key<Scalar, KeyCont, OutputCont
// Add the public encryption of zeros to get the zero encryption
for (&chosen, public_encryption_of_zero_body) in ct_choice.iter().zip(lwe_public_key.iter()) {
let (mut mask, body) = tmp_ciphertext.get_mut_mask_and_body();
let (mut mask, body) = tmp_zero_encryption.get_mut_mask_and_body();
random_generator
.fill_slice_with_random_uniform_custom_mod(mask.as_mut(), ciphertext_modulus);
if !ciphertext_modulus.is_native_modulus() {
@@ -924,10 +931,10 @@ pub fn encrypt_lwe_ciphertext_with_seeded_public_key<Scalar, KeyCont, OutputCont
}
*body.data = *public_encryption_of_zero_body.data;
// TODO: can leak choice of ciphertexts
if chosen == Scalar::ONE {
lwe_ciphertext_add_assign(output, &tmp_ciphertext);
}
// chosen is 1 if chosen, 0 otherwise, so use a multiplication to avoid having a branch
// depending on a value that's supposed to remain secret
lwe_ciphertext_cleartext_mul_assign(&mut tmp_zero_encryption, Cleartext(chosen));
lwe_ciphertext_add_assign(output, &tmp_zero_encryption);
}
// Add encoded plaintext

View File

@@ -512,7 +512,7 @@ pub fn multi_bit_blind_rotate_assign<Scalar, InputCont, OutputCont, KeyCont>(
// fill the MSBs and leave the LSBs empty, this usage of the signed decomposer allows to
// round while keeping the data in the MSBs
let signed_decomposer = SignedDecomposer::new(
DecompositionBaseLog(ciphertext_modulus.get().ilog2() as usize),
DecompositionBaseLog(ciphertext_modulus.get_custom_modulus().ilog2() as usize),
DecompositionLevelCount(1),
);
ct0.as_mut()

View File

@@ -25,7 +25,8 @@ use dyn_stack::{PodStack, SizeOverflow, StackReq};
/// Perform a blind rotation given an input [`LWE ciphertext`](`LweCiphertext`), modifying a look-up
/// table passed as a [`GLWE ciphertext`](`GlweCiphertext`) and an [`LWE bootstrap
/// key`](`LweBootstrapKey`) in the fourier domain.
/// key`](`LweBootstrapKey`) in the fourier domain see [`fourier LWE bootstrap
/// key`](`FourierLweBootstrapKey`).
///
/// If you want to manage the computation memory manually you can use
/// [`blind_rotate_assign_mem_optimized`].
@@ -348,8 +349,6 @@ pub fn add_external_product_assign<Scalar, OutputGlweCont, InputGlweCont, GgswCo
///
/// # Example
///
/// # Example
///
/// ```
/// use tfhe::core_crypto::prelude::*;
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
@@ -502,7 +501,7 @@ pub fn add_external_product_assign_mem_optimized<Scalar, OutputGlweCont, InputGl
// fill the MSBs and leave the LSBs empty, this usage of the signed decomposer allows to
// round while keeping the data in the MSBs
let signed_decomposer = SignedDecomposer::new(
DecompositionBaseLog(ciphertext_modulus.get().ilog2() as usize),
DecompositionBaseLog(ciphertext_modulus.get_custom_modulus().ilog2() as usize),
DecompositionLevelCount(1),
);
out.as_mut()
@@ -791,7 +790,7 @@ pub fn cmux_assign_mem_optimized<Scalar, Cont0, Cont1, GgswCont>(
// fill the MSBs and leave the LSBs empty, this usage of the signed decomposer allows to
// round while keeping the data in the MSBs
let signed_decomposer = SignedDecomposer::new(
DecompositionBaseLog(ciphertext_modulus.get().ilog2() as usize),
DecompositionBaseLog(ciphertext_modulus.get_custom_modulus().ilog2() as usize),
DecompositionLevelCount(1),
);
ct0.as_mut()
@@ -811,7 +810,8 @@ pub fn cmux_assign_mem_optimized_requirement<Scalar>(
/// Perform a programmable bootstrap given an input [`LWE ciphertext`](`LweCiphertext`), a
/// look-up table passed as a [`GLWE ciphertext`](`GlweCiphertext`) and an [`LWE bootstrap
/// key`](`LweBootstrapKey`) in the fourier domain. The result is written in the provided output
/// key`](`LweBootstrapKey`) in the fourier domain see [`fourier LWE bootstrap
/// key`](`FourierLweBootstrapKey`). The result is written in the provided output
/// [`LWE ciphertext`](`LweCiphertext`).
///
/// If you want to manage the computation memory manually you can use
@@ -1119,11 +1119,12 @@ pub fn programmable_bootstrap_lwe_ciphertext_mem_optimized_requirement<Scalar>(
/// Perform a programmable bootstrap given an input [`LWE ciphertext`](`LweCiphertext`), a
/// look-up table passed as a [`GLWE ciphertext`](`GlweCiphertext`) and an [`LWE bootstrap
/// key`](`LweBootstrapKey`) in the fourier domain using f128. The result is written in the provided
/// key`](`LweBootstrapKey`) in the fourier domain using f128 see [`fourier LWE bootstrap
/// key`](`Fourier128LweBootstrapKey`). The result is written in the provided
/// output [`LWE ciphertext`](`LweCiphertext`).
///
/// If you want to manage the computation memory manually you can use
/// [`programmable_bootstrap_lwe_ciphertext_mem_optimized`].
/// [`programmable_bootstrap_f128_lwe_ciphertext_mem_optimized`].
///
/// # Example
///

View File

@@ -0,0 +1,202 @@
//! Module containing primitives pertaining to LWE ciphertext private functional keyswitch and
//! packing keyswitch.
//!
//! Formal description can be found in: \
//! &nbsp;&nbsp;&nbsp;&nbsp; Chillotti, I., Gama, N., Georgieva, M. et al. \
//! &nbsp;&nbsp;&nbsp;&nbsp; TFHE: Fast Fully Homomorphic Encryption Over the Torus. \
//! &nbsp;&nbsp;&nbsp;&nbsp; J. Cryptol 33, 3491 (2020). \
//! &nbsp;&nbsp;&nbsp;&nbsp; <https://doi.org/10.1007/s00145-019-09319-x>
use crate::core_crypto::algorithms::polynomial_algorithms::*;
//use crate::core_crypto::algorithms::slice_algorithms::*;
use crate::core_crypto::commons::math::decomposition::SignedDecomposer;
//use crate::core_crypto::commons::parameters::*;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::entities::*;
/// Apply a public functional packing keyswitch on an input
/// [`LWE ciphertext list`](`LweCiphertextList`) and write
/// the result in an output [`GLWE ciphertext`](`GlweCiphertext`).
/// # Example
/// ```
/// //define the inputs for the public functional key switching
/// use tfhe::core_crypto::algorithms::polynomial_algorithms::*;
/// use tfhe::core_crypto::prelude::*;
///
/// let lwe_dimension = LweDimension(742);
///
/// // Create the PRNG
/// let mut seeder = new_seeder();
/// let seeder = seeder.as_mut();
/// let mut secret_generator =
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
///
/// let lwe_secret_key: LweSecretKeyOwned<u64> =
/// LweSecretKey::generate_new_binary(lwe_dimension, &mut secret_generator);
/// let glwe_size = GlweSize(2);
/// let polynomial_size = PolynomialSize(1024);
/// let glwe_secret_key: GlweSecretKeyOwned<u64> = GlweSecretKey::generate_new_binary(
/// glwe_size.to_glwe_dimension(),
/// polynomial_size,
/// &mut secret_generator,
/// );
/// let decomp_base_log = DecompositionBaseLog(8);
/// let decomp_level_count = DecompositionLevelCount(3);
/// let ciphertext_modulus = CiphertextModulus::new_native();
/// let mut lwe_pubfpksk = LwePublicFunctionalPackingKeyswitchKey::new(
/// 0u64,
/// decomp_base_log,
/// decomp_level_count,
/// lwe_dimension,
/// glwe_size,
/// polynomial_size,
/// ciphertext_modulus,
/// );
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
/// let mut encryption_generator =
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
/// generate_lwe_public_functional_packing_keyswitch_key(
/// &lwe_secret_key,
/// &glwe_secret_key,
/// &mut lwe_pubfpksk,
/// glwe_modular_std_dev,
/// &mut encryption_generator,
/// );
///
/// let lwe_ciphertext_count = LweCiphertextCount(20);
/// let mut lwe_list = LweCiphertextList::new(
/// 0u64,
/// lwe_dimension.to_lwe_size(),
/// lwe_ciphertext_count,
/// ciphertext_modulus,
/// );
/// let lwe_plaintext_list = PlaintextList::new(1u64 << 59, PlaintextCount(20));
/// encrypt_lwe_ciphertext_list(
/// &lwe_secret_key,
/// &mut lwe_list,
/// &lwe_plaintext_list,
/// glwe_modular_std_dev,
/// &mut encryption_generator,
/// );
///
/// let mut output_glwe_ciphertext =
/// GlweCiphertext::new(0u64, glwe_size, polynomial_size, ciphertext_modulus);
/// public_functional_keyswitch_lwe_ciphertexts_into_glwe_ciphertext(
/// &mut lwe_pubfpksk,
/// &mut output_glwe_ciphertext,
/// &lwe_list,
/// |mut x| {
/// let mut sum = 0u64;
/// x.iter().for_each(|y| sum = sum.wrapping_add(*y));
/// let mut temp = vec![sum];
/// temp.resize(polynomial_size.0, 0u64);
/// Polynomial::from_container(temp)
/// },
/// );
///
/// let mut output_plaintext_list = PlaintextList::new(0u64, PlaintextCount(polynomial_size.0));
/// let decomposer = SignedDecomposer::new(DecompositionBaseLog(1), DecompositionLevelCount(4));
///
/// decrypt_glwe_ciphertext(
/// &glwe_secret_key,
/// &output_glwe_ciphertext,
/// &mut output_plaintext_list,
/// );
/// output_plaintext_list
/// .iter_mut()
/// .for_each(|x| *x.0 = decomposer.closest_representable(*x.0));
///
/// // Get the raw vecor
/// let mut cleartext = output_plaintext_list.into_container();
/// // Remove the encoding
/// cleartext.iter_mut().for_each(|x| *x = *x >> 59);
/// // Get the list immutably
/// let cleartext = cleartext;
///
/// // Check we get the correct result
/// for (index, clear) in cleartext.iter().enumerate() {
/// if index == 0 {
/// assert_eq!(20, *clear);
/// } else {
/// assert_eq!(0, *clear);
/// }
/// }
/// ```
pub fn public_functional_keyswitch_lwe_ciphertexts_into_glwe_ciphertext<
KeyCont,
InputCont,
OutputCont,
Func,
Scalar,
>(
lwe_pubfpksk: &LwePublicFunctionalPackingKeyswitchKey<KeyCont>,
output_glwe_ciphertext: &mut GlweCiphertext<OutputCont>,
input_lwe_ciphertext_list: &LweCiphertextList<InputCont>,
f: Func,
) where
Scalar: UnsignedTorus,
KeyCont: Container<Element = Scalar>,
InputCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = Scalar>,
Func: Fn(Vec<Scalar>) -> Polynomial<Vec<Scalar>>,
{
assert_eq!(
output_glwe_ciphertext.polynomial_size(),
lwe_pubfpksk.output_polynomial_size()
);
assert_eq!(
output_glwe_ciphertext.glwe_size(),
lwe_pubfpksk.output_glwe_size()
);
assert_eq!(
input_lwe_ciphertext_list.lwe_size().to_lwe_dimension(),
lwe_pubfpksk.input_lwe_key_dimension()
);
//evaluate the function on this list of first elements
let mut list_output_function =
Vec::with_capacity(input_lwe_ciphertext_list.lwe_ciphertext_count().0);
for i in 0..input_lwe_ciphertext_list.lwe_size().to_lwe_dimension().0 {
//get list of ith elements of the input lwes
let vec_of_ai: Vec<Scalar> = input_lwe_ciphertext_list
.iter()
.map(|lwe| lwe.get_mask().as_ref()[i])
.collect();
list_output_function.push(f(vec_of_ai));
}
// We reset the output
output_glwe_ciphertext.as_mut().fill(Scalar::ZERO);
let vec_of_b: Vec<Scalar> = input_lwe_ciphertext_list
.iter()
.map(|lwe| *lwe.get_body().data)
.collect();
assert!(f(vec_of_b.clone()).polynomial_size() == output_glwe_ciphertext.polynomial_size(),
"the polynomial size of the output_glwe_ciphertext value needs to be equal to the polynomial size of the output of the function f");
//initiate the body of the output glwe ciphertext
output_glwe_ciphertext
.get_mut_body()
.as_mut()
.copy_from_slice(f(vec_of_b).as_ref());
//decompose the result of the function
// We instantiate a decomposer
let decomposer = SignedDecomposer::new(
lwe_pubfpksk.decomposition_base_log(),
lwe_pubfpksk.decomposition_level_count(),
);
for (keyswitch_key_block, output_function) in
lwe_pubfpksk.iter().zip(list_output_function.iter_mut())
{
let mut decomposition_iter = decomposer.decompose_slice(output_function.as_ref());
// loop over the number of levels in reverse (from highest to lowest)
for level_key_ciphertext in keyswitch_key_block.iter().rev() {
let decomposed = decomposition_iter.next_term().unwrap();
polynomial_list_wrapping_sub_scalar_mul_assign(
&mut output_glwe_ciphertext.as_mut_polynomial_list(),
&level_key_ciphertext.as_polynomial_list(),
&Polynomial::from_container(decomposed.as_slice()),
);
}
}
}

View File

@@ -0,0 +1,359 @@
//! Module containing primitives pertaining to [`LWE public functional packing keyswitch key
//! generation`](`LwePublicFunctionalPackingKeyswitchKey`).
use crate::core_crypto::algorithms::*;
use crate::core_crypto::commons::dispersion::DispersionParameter;
use crate::core_crypto::commons::generators::EncryptionRandomGenerator;
use crate::core_crypto::commons::math::decomposition::{DecompositionLevel, DecompositionTerm};
use crate::core_crypto::commons::parameters::*;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::entities::*;
use rayon::prelude::*;
/// Fill an [`LWE public functional packing keyswitch
/// key`](`LwePublicFunctionalPackingKeyswitchKey`) with an actual key.
///
/// # Example
/// ```
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
/// // computations
/// use tfhe::core_crypto::prelude::*;
/// let lwe_dimension = LweDimension(8);
/// let ciphertext_modulus = CiphertextModulus::new_native();
///
/// // Create the PRNG
/// let mut seeder = new_seeder();
/// let seeder = seeder.as_mut();
/// let mut secret_generator =
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
///
/// let input_lwe_secret_key =
/// LweSecretKey::generate_new_binary(lwe_dimension, &mut secret_generator);
/// let glwe_size = GlweSize(2);
/// let polynomial_size = PolynomialSize(1024);
/// let output_glwe_secret_key = GlweSecretKey::generate_new_binary(
/// glwe_size.to_glwe_dimension(),
/// polynomial_size,
/// &mut secret_generator,
/// );
///
/// let decomp_base_log = DecompositionBaseLog(8);
/// let decomp_level_count = DecompositionLevelCount(3);
/// let mut lwe_pubfpksk = LwePublicFunctionalPackingKeyswitchKey::new(
/// 0u64,
/// decomp_base_log,
/// decomp_level_count,
/// lwe_dimension,
/// glwe_size,
/// polynomial_size,
/// ciphertext_modulus,
/// );
/// let glwe_modular_std_dev = StandardDev(0.00000000000000029403601535432533);
/// let mut encryption_generator =
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
/// generate_lwe_public_functional_packing_keyswitch_key(
/// &input_lwe_secret_key,
/// &output_glwe_secret_key,
/// &mut lwe_pubfpksk,
/// glwe_modular_std_dev,
/// &mut encryption_generator,
/// );
/// ```
pub fn generate_lwe_public_functional_packing_keyswitch_key<
Scalar,
InputKeyCont,
OutputKeyCont,
KSKeyCont,
Gen,
>(
input_lwe_secret_key: &LweSecretKey<InputKeyCont>,
output_glwe_secret_key: &GlweSecretKey<OutputKeyCont>,
lwe_pubfpksk: &mut LwePublicFunctionalPackingKeyswitchKey<KSKeyCont>,
noise_parameters: impl DispersionParameter + Sync,
generator: &mut EncryptionRandomGenerator<Gen>,
) where
Scalar: UnsignedTorus,
InputKeyCont: Container<Element = Scalar>,
OutputKeyCont: Container<Element = Scalar>,
KSKeyCont: ContainerMut<Element = Scalar>,
Gen: ByteRandomGenerator,
{
assert!(
input_lwe_secret_key.lwe_dimension() == lwe_pubfpksk.input_lwe_key_dimension(),
"Mismatched LweDimension between input_lwe_secret_key {:?} and lwe_pfpksk input dimension \
{:?}.",
input_lwe_secret_key.lwe_dimension(),
lwe_pubfpksk.input_lwe_key_dimension()
);
assert!(
output_glwe_secret_key.glwe_dimension() == lwe_pubfpksk.output_glwe_key_dimension(),
"Mismatched GlweDimension between output_glwe_secret_key {:?} and lwe_pfpksk output \
dimension {:?}.",
output_glwe_secret_key.glwe_dimension(),
lwe_pubfpksk.output_glwe_key_dimension()
);
assert!(
output_glwe_secret_key.polynomial_size() == lwe_pubfpksk.output_polynomial_size(),
"Mismatched PolynomialSize between output_glwe_secret_key {:?} and lwe_pfpksk output \
polynomial size {:?}.",
output_glwe_secret_key.polynomial_size(),
lwe_pubfpksk.output_polynomial_size()
);
// We instantiate a buffer
let mut messages = PlaintextListOwned::new(
Scalar::ZERO,
PlaintextCount(
lwe_pubfpksk.decomposition_level_count().0 * lwe_pubfpksk.output_polynomial_size().0,
),
);
// We retrieve decomposition arguments
let decomp_level_count = lwe_pubfpksk.decomposition_level_count();
let decomp_base_log = lwe_pubfpksk.decomposition_base_log();
let polynomial_size = lwe_pubfpksk.output_polynomial_size();
let last_key_iter_bit = [Scalar::MAX];
// add minus one for the function which will be applied to the decomposed body
// ( Scalar::MAX = -Scalar::ONE )
let input_key_bit_iter = input_lwe_secret_key
.as_ref()
.iter()
.chain(last_key_iter_bit.iter());
//TODO should we rename fork_pfpksk_to_pfpksk_chunks?
let gen_iter = generator
.fork_pfpksk_to_pfpksk_chunks::<Scalar>(
decomp_level_count,
output_glwe_secret_key.glwe_dimension().to_glwe_size(),
output_glwe_secret_key.polynomial_size(),
input_lwe_secret_key.lwe_dimension().to_lwe_size(),
)
.unwrap();
// loop over the before key blocks
for ((&input_key_bit, mut keyswitch_key_block), mut loop_generator) in input_key_bit_iter
.zip(lwe_pubfpksk.iter_mut())
.zip(gen_iter)
{
//we reset the buffer
// We fill the buffer with the powers of the decomposition scale times the key bits
for (level, mut message) in (1..=decomp_level_count.0)
.map(DecompositionLevel)
.zip(messages.chunks_exact_mut(polynomial_size.0))
{
message.as_mut()[0] = DecompositionTerm::new(level, decomp_base_log, input_key_bit)
.to_recomposition_summand();
}
// We encrypt the buffer
encrypt_glwe_ciphertext_list(
output_glwe_secret_key,
&mut keyswitch_key_block,
&messages,
noise_parameters,
&mut loop_generator,
)
}
}
/// Parallel variant of [`generate_lwe_public_functional_packing_keyswitch_key`]. You may want to
/// use this variant for better key generation times.
pub fn par_generate_lwe_public_functional_packing_keyswitch_key<
Scalar,
InputKeyCont,
OutputKeyCont,
KSKeyCont,
Gen,
>(
input_lwe_secret_key: &LweSecretKey<InputKeyCont>,
output_glwe_secret_key: &GlweSecretKey<OutputKeyCont>,
lwe_pubfpksk: &mut LwePublicFunctionalPackingKeyswitchKey<KSKeyCont>,
noise_parameters: impl DispersionParameter + Sync,
generator: &mut EncryptionRandomGenerator<Gen>,
) where
Scalar: UnsignedTorus + Sync + Send,
InputKeyCont: Container<Element = Scalar>,
OutputKeyCont: Container<Element = Scalar> + Sync,
KSKeyCont: ContainerMut<Element = Scalar> + Sync,
Gen: ParallelByteRandomGenerator,
{
assert!(
input_lwe_secret_key.lwe_dimension() == lwe_pubfpksk.input_lwe_key_dimension(),
"Mismatched LweDimension between input_lwe_secret_key {:?} and lwe_pfpksk input dimension \
{:?}.",
input_lwe_secret_key.lwe_dimension(),
lwe_pubfpksk.input_lwe_key_dimension()
);
assert!(
output_glwe_secret_key.glwe_dimension() == lwe_pubfpksk.output_glwe_key_dimension(),
"Mismatched GlweDimension between output_glwe_secret_key {:?} and lwe_pfpksk output \
dimension {:?}.",
output_glwe_secret_key.glwe_dimension(),
lwe_pubfpksk.output_glwe_key_dimension()
);
assert!(
output_glwe_secret_key.polynomial_size() == lwe_pubfpksk.output_polynomial_size(),
"Mismatched PolynomialSize between output_glwe_secret_key {:?} and lwe_pfpksk output \
polynomial size {:?}.",
output_glwe_secret_key.polynomial_size(),
lwe_pubfpksk.output_polynomial_size()
);
// We retrieve decomposition arguments
let decomp_level_count = lwe_pubfpksk.decomposition_level_count();
let decomp_base_log = lwe_pubfpksk.decomposition_base_log();
let polynomial_size = lwe_pubfpksk.output_polynomial_size();
let last_key_iter_bit = [Scalar::MAX];
// add minus one for the function which will be applied to the decomposed body
// ( Scalar::MAX = -Scalar::ONE )
let input_key_bit_iter = input_lwe_secret_key
.as_ref()
.par_iter()
.chain(last_key_iter_bit.par_iter());
let gen_iter = generator
.par_fork_pfpksk_to_pfpksk_chunks::<Scalar>(
decomp_level_count,
output_glwe_secret_key.glwe_dimension().to_glwe_size(),
output_glwe_secret_key.polynomial_size(),
input_lwe_secret_key.lwe_dimension().to_lwe_size(),
)
.unwrap();
let plaintext_count = PlaintextCount(
lwe_pubfpksk.decomposition_level_count().0 * lwe_pubfpksk.output_polynomial_size().0,
);
// loop over the before key blocks
input_key_bit_iter
.zip(lwe_pubfpksk.par_iter_mut())
.zip(gen_iter)
.for_each(
|((&input_key_bit, mut keyswitch_key_block), mut loop_generator)| {
// We instantiate a buffer
let mut messages = PlaintextListOwned::new(Scalar::ZERO, plaintext_count);
// We fill the buffer with the powers of the key bits
for (level, mut message) in (1..=decomp_level_count.0)
.map(DecompositionLevel)
.zip(messages.chunks_exact_mut(polynomial_size.0))
{
message.as_mut()[0] =
DecompositionTerm::new(level, decomp_base_log, input_key_bit)
.to_recomposition_summand();
}
// We encrypt the buffer
encrypt_glwe_ciphertext_list(
output_glwe_secret_key,
&mut keyswitch_key_block,
&messages,
noise_parameters,
&mut loop_generator,
)
},
);
}
#[cfg(test)]
mod test {
use crate::core_crypto::commons::generators::DeterministicSeeder;
use crate::core_crypto::commons::math::random::Seed;
use crate::core_crypto::prelude::*;
#[test]
fn test_pubfpksk_list_gen_equivalence() {
const NB_TESTS: usize = 10;
for _ in 0..NB_TESTS {
// DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield
// correct computations
let glwe_dimension =
GlweDimension(crate::core_crypto::commons::test_tools::random_usize_between(5..10));
let polynomial_size = PolynomialSize(
crate::core_crypto::commons::test_tools::random_usize_between(5..10),
);
let pubfpksk_level_count = DecompositionLevelCount(
crate::core_crypto::commons::test_tools::random_usize_between(2..5),
);
let pubfpksk_base_log = DecompositionBaseLog(
crate::core_crypto::commons::test_tools::random_usize_between(2..5),
);
let ciphertext_modulus = CiphertextModulus::new_native();
let common_encryption_seed =
Seed(crate::core_crypto::commons::test_tools::random_uint_between(0..u128::MAX));
let var_small = Variance::from_variance(2f64.powf(-80.0));
// Create the PRNG
let mut seeder = new_seeder();
let mut secret_generator =
SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
let glwe_sk: GlweSecretKeyOwned<u64> = allocate_and_generate_new_binary_glwe_secret_key(
glwe_dimension,
polynomial_size,
&mut secret_generator,
);
let lwe_big_sk = glwe_sk.clone().into_lwe_secret_key();
let mut par_pubfpksk = LwePublicFunctionalPackingKeyswitchKey::new(
0u64,
pubfpksk_base_log,
pubfpksk_level_count,
lwe_big_sk.lwe_dimension(),
glwe_dimension.to_glwe_size(),
polynomial_size,
ciphertext_modulus,
);
let mut ser_pubfpksk = LwePublicFunctionalPackingKeyswitchKey::new(
0u64,
pubfpksk_base_log,
pubfpksk_level_count,
lwe_big_sk.lwe_dimension(),
glwe_dimension.to_glwe_size(),
polynomial_size,
ciphertext_modulus,
);
let mut seeder =
DeterministicSeeder::<ActivatedRandomGenerator>::new(common_encryption_seed);
let mut encryption_generator =
EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(
seeder.seed(),
&mut seeder,
);
par_generate_lwe_public_functional_packing_keyswitch_key(
&lwe_big_sk,
&glwe_sk,
&mut par_pubfpksk,
var_small,
&mut encryption_generator,
);
let mut seeder =
DeterministicSeeder::<ActivatedRandomGenerator>::new(common_encryption_seed);
let mut encryption_generator =
EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(
seeder.seed(),
&mut seeder,
);
generate_lwe_public_functional_packing_keyswitch_key(
&lwe_big_sk,
&glwe_sk,
&mut ser_pubfpksk,
var_small,
&mut encryption_generator,
);
assert_eq!(par_pubfpksk, ser_pubfpksk)
}
}
}

View File

@@ -0,0 +1,330 @@
//! Module containing primitives pertaining to LWE trace pacling keyswitch.
use crate::core_crypto::algorithms::glwe_keyswitch::*;
use crate::core_crypto::algorithms::polynomial_algorithms::*;
use crate::core_crypto::commons::parameters::*;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::entities::*;
/// Apply a trace packing keyswitch on an input [`LWE ciphertext list`](`LweCiphertextList`) and
/// pack the result in an output [`GLWE ciphertext`](`GlweCiphertext`).
///
/// ```
/// use tfhe::core_crypto::prelude::*;
///
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
/// // computations
/// // Define parameters for LweTracePackingKeyswitchKey creation
/// let lwe_dimension = LweDimension(60);
/// let lwe_count = LweCiphertextCount(25);
/// let polynomial_size = PolynomialSize(32);
/// let glwe_dimension = GlweDimension(2);
/// let lwe_modular_std_dev = StandardDev(0.00000000000000000000001);
/// let ciphertext_modulus = CiphertextModulus::new_native();
///
/// let mut seeder = new_seeder();
/// let mut secret_generator =
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
/// let lwe_secret_key =
/// allocate_and_generate_new_binary_lwe_secret_key(lwe_dimension, &mut secret_generator);
///
/// let mut glwe_secret_key = GlweSecretKey::new_empty_key(0u64, glwe_dimension, polynomial_size);
///
/// generate_tpksk_output_glwe_secret_key(&lwe_secret_key, &mut glwe_secret_key);
///
/// let decomp_base_log = DecompositionBaseLog(2);
/// let decomp_level_count = DecompositionLevelCount(8);
/// let var_small = Variance::from_variance(2f64.powf(-90.0));
/// let mut seeder = new_seeder();
/// let seeder = seeder.as_mut();
/// let mut encryption_generator =
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
///
/// let mut lwe_tpksk = LweTracePackingKeyswitchKey::new(
/// 0u64,
/// decomp_base_log,
/// decomp_level_count,
/// lwe_dimension.to_lwe_size(),
/// glwe_dimension.to_glwe_size(),
/// polynomial_size,
/// ciphertext_modulus,
/// );
///
/// generate_lwe_trace_packing_keyswitch_key(
/// &glwe_secret_key,
/// &mut lwe_tpksk,
/// var_small,
/// &mut encryption_generator,
/// );
///
/// let mut lwe_ctxt_list = LweCiphertextList::new(
/// 0u64,
/// lwe_dimension.to_lwe_size(),
/// lwe_count,
/// ciphertext_modulus,
/// );
///
/// let msg = 1u64;
/// let plaintext_list = PlaintextList::new(msg << 56, PlaintextCount(lwe_count.0));
///
/// encrypt_lwe_ciphertext_list(
/// &lwe_secret_key,
/// &mut lwe_ctxt_list,
/// &plaintext_list,
/// lwe_modular_std_dev,
/// &mut encryption_generator,
/// );
///
/// let mut output_glwe_ciphertext = GlweCiphertext::new(
/// 0u64,
/// glwe_dimension.to_glwe_size(),
/// polynomial_size,
/// ciphertext_modulus,
/// );
///
/// let mut indices = vec![0_usize; lwe_count.0];
/// for (index, item) in indices.iter_mut().enumerate() {
/// *item = index;
/// }
///
/// trace_packing_keyswitch_lwe_ciphertext_list_into_glwe_ciphertext(
/// &lwe_tpksk,
/// &mut output_glwe_ciphertext,
/// &lwe_ctxt_list,
/// indices,
/// );
///
/// let mut output_plaintext_list = PlaintextList::new(0u64, PlaintextCount(polynomial_size.0));
///
/// decrypt_glwe_ciphertext(
/// &glwe_secret_key,
/// &output_glwe_ciphertext,
/// &mut output_plaintext_list,
/// );
///
/// // Round and remove encoding
/// // First create a decomposer working on the high 8 bits corresponding to our encoding.
/// let decomposer = SignedDecomposer::new(DecompositionBaseLog(8), DecompositionLevelCount(1));
///
/// output_plaintext_list
/// .iter_mut()
/// .for_each(|elt| *elt.0 = decomposer.closest_representable(*elt.0));
///
/// // Get the raw vector
/// let mut cleartext_list = output_plaintext_list.into_container();
/// // Remove the encoding
/// // Due to not having an inverse of 2 the packing multiplies each value by polynomial_size
/// // Hence the encoding delta is multiplied by polynomial_size
/// cleartext_list
/// .iter_mut()
/// .for_each(|elt| *elt = *elt >> 56 + polynomial_size.log2().0);
/// // Get the list immutably
/// let cleartext_list = cleartext_list;
///
/// // Check we recovered the original message for each plaintext we encrypted
/// for (index, elt) in cleartext_list.iter().enumerate() {
/// if index < lwe_count.0 {
/// assert_eq!(*elt, msg);
/// } else {
/// assert_eq!(*elt, 0);
/// }
/// }
/// ```
pub fn trace_packing_keyswitch_lwe_ciphertext_list_into_glwe_ciphertext<
Scalar,
KeyCont,
InputCont,
OutputCont,
>(
lwe_tpksk: &LweTracePackingKeyswitchKey<KeyCont>,
output_glwe_ciphertext: &mut GlweCiphertext<OutputCont>,
input_lwe_ciphertext_list: &LweCiphertextList<InputCont>,
indices: Vec<usize>,
) where
Scalar: UnsignedTorus,
KeyCont: Container<Element = Scalar>,
InputCont: Container<Element = Scalar>,
OutputCont: ContainerMut<Element = Scalar>,
{
assert!(
input_lwe_ciphertext_list.lwe_ciphertext_count().0
<= output_glwe_ciphertext.polynomial_size().0
);
assert_eq!(
input_lwe_ciphertext_list.lwe_ciphertext_count().0,
indices.len()
);
assert_eq!(
input_lwe_ciphertext_list.lwe_size(),
lwe_tpksk.input_lwe_size()
);
assert!(indices
.iter()
.all(|&x| x < output_glwe_ciphertext.polynomial_size().0));
assert_eq!(
output_glwe_ciphertext.polynomial_size(),
lwe_tpksk.polynomial_size()
);
assert_eq!(
output_glwe_ciphertext.glwe_size(),
lwe_tpksk.output_glwe_size()
);
assert_eq!(
input_lwe_ciphertext_list.ciphertext_modulus(),
lwe_tpksk.ciphertext_modulus()
);
assert_eq!(
output_glwe_ciphertext.ciphertext_modulus(),
lwe_tpksk.ciphertext_modulus()
);
// We reset the output
output_glwe_ciphertext.as_mut().fill(Scalar::ZERO);
let poly_size = output_glwe_ciphertext.polynomial_size();
let glwe_count = GlweCiphertextCount(poly_size.0);
let ciphertext_modulus = output_glwe_ciphertext.ciphertext_modulus();
let mut glwe_list = GlweCiphertextList::new(
Scalar::ZERO,
output_glwe_ciphertext.glwe_size(),
poly_size,
glwe_count,
ciphertext_modulus,
);
// Construct the initial Glwe Ciphertexts
for (index1, mut glwe_ct) in glwe_list.iter_mut().enumerate() {
for (index2, index) in indices.iter().enumerate() {
if index1 == *index {
let lwe_ct = input_lwe_ciphertext_list.get(index2);
let lwe_body = lwe_ct.as_ref().last().unwrap();
let lwe_mask = lwe_ct.get_mask();
for (index3, mut ring_element) in glwe_ct
.get_mut_mask()
.as_mut_polynomial_list()
.iter_mut()
.enumerate()
{
for (index4, coef) in ring_element.iter_mut().enumerate() {
if index3 * poly_size.0 + index4 < lwe_mask.lwe_dimension().0 {
*coef =
coef.wrapping_add(lwe_mask.as_ref()[index3 * poly_size.0 + index4]);
}
}
}
let mut poly_to_add = Polynomial::new(Scalar::ZERO, poly_size);
poly_to_add[0] = poly_to_add[0].wrapping_add(*lwe_body);
polynomial_wrapping_add_assign(
&mut glwe_ct.get_mut_body().as_mut_polynomial(),
&poly_to_add,
);
}
}
}
for l in 0..poly_size.log2().0 {
for i in 0..(poly_size.0 / 2_usize.pow(l as u32 + 1)) {
let ct_0 = glwe_list.get(i);
let glwe_size = ct_0.glwe_size();
let j = (poly_size.0 / 2_usize.pow(l as u32 + 1)) + i;
let ct_1 = glwe_list.get(j);
if ct_0.as_ref().iter().any(|&x| x != Scalar::ZERO)
|| ct_1.as_ref().iter().any(|&x| x != Scalar::ZERO)
{
// Rotate ct_1 by N/2^(l+1)
for mut pol in glwe_list.get_mut(j).as_mut_polynomial_list().iter_mut() {
polynomial_wrapping_monic_monomial_mul_assign(
&mut pol,
MonomialDegree(poly_size.0 / 2_usize.pow(l as u32 + 1)),
);
}
let mut ct_plus =
GlweCiphertext::new(Scalar::ZERO, glwe_size, poly_size, ciphertext_modulus);
let mut ct_minus =
GlweCiphertext::new(Scalar::ZERO, glwe_size, poly_size, ciphertext_modulus);
for ((mut pol_plus, pol_0), pol_1) in ct_plus
.as_mut_polynomial_list()
.iter_mut()
.zip(glwe_list.get(i).as_polynomial_list().iter())
.zip(glwe_list.get(j).as_polynomial_list().iter())
{
polynomial_wrapping_add_assign(&mut pol_plus, &pol_0);
polynomial_wrapping_add_assign(&mut pol_plus, &pol_1);
}
for ((mut pol_minus, pol_0), pol_1) in ct_minus
.as_mut_polynomial_list()
.iter_mut()
.zip(glwe_list.get(i).as_polynomial_list().iter())
.zip(glwe_list.get(j).as_polynomial_list().iter())
{
polynomial_wrapping_add_assign(&mut pol_minus, &pol_0);
polynomial_wrapping_sub_assign(&mut pol_minus, &pol_1);
}
// Now we should scale both ct_plus and ct_minus by 2^-1 mod q = (q+1) / 2 for odd q
let scalar = Scalar::ONE; // change to (q + 1)/2 for odd q
if !ciphertext_modulus.is_power_of_two() {
// Set scalar to (q + 1)/2
}
for mut pol in ct_plus.as_mut_polynomial_list().iter_mut() {
polynomial_wrapping_scalar_mul_assign(&mut pol, scalar);
}
for mut pol in ct_minus.as_mut_polynomial_list().iter_mut() {
polynomial_wrapping_scalar_mul_assign(&mut pol, scalar);
}
// Apply the automorphism sending X to X^(2^(l+1) + 1) to ct_minus
for mut pol in ct_minus.as_mut_polynomial_list().iter_mut() {
apply_automorphism_assign(&mut pol, 2_usize.pow(l as u32 + 1) + 1)
}
let mut ks_out = GlweCiphertext::new(
Scalar::ZERO,
ct_minus.glwe_size(),
poly_size,
ciphertext_modulus,
);
let glwe_ksk = GlweKeyswitchKey::from_container(
lwe_tpksk.get(l).into_container(),
lwe_tpksk.decomposition_base_log(),
lwe_tpksk.decomposition_level_count(),
lwe_tpksk.output_glwe_size(),
lwe_tpksk.polynomial_size(),
lwe_tpksk.ciphertext_modulus(),
);
// Perform a Glwe keyswitch on ct_minus
keyswitch_glwe_ciphertext(&glwe_ksk, &mut ct_minus, &mut ks_out);
// Set ct_0 to zero
glwe_list.get_mut(i).as_mut().fill(Scalar::ZERO);
// Add the result to ct_plus and add this to ct_0
for ((mut pol_plus, pol_ks), mut pol_0) in ct_plus
.as_mut_polynomial_list()
.iter_mut()
.zip(ks_out.as_polynomial_list().iter())
.zip(glwe_list.get_mut(i).as_mut_polynomial_list().iter_mut())
{
polynomial_wrapping_add_assign(&mut pol_plus, &pol_ks);
polynomial_wrapping_add_assign(&mut pol_0, &pol_plus);
}
}
}
}
let res = glwe_list.get(0);
output_glwe_ciphertext.as_mut().fill(Scalar::ZERO);
for (mut pol_out, pol_res) in output_glwe_ciphertext
.as_mut_polynomial_list()
.iter_mut()
.zip(res.as_polynomial_list().iter())
{
polynomial_wrapping_add_assign(&mut pol_out, &pol_res);
}
}

View File

@@ -0,0 +1,176 @@
//! Module containing primitives pertaining to [`LWE trace packing keyswitch key
//! generation`](`LweTracePackingKeyswitchKey`).
use crate::core_crypto::algorithms::*;
use crate::core_crypto::commons::dispersion::DispersionParameter;
use crate::core_crypto::commons::generators::EncryptionRandomGenerator;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::entities::*;
use crate::core_crypto::prelude::polynomial_algorithms::apply_automorphism_wrapping_add_assign;
/// Fill a [`GLWE secret key`](`GlweSecretKey`) with an actual key derived from an
/// [`LWE secret key`](`LweSecretKey`) for use in the [`LWE trace packing keyswitch key`]
/// (`LweTracePackingKeyswitchKey`)
/// # Example
///
/// ```
/// use tfhe::core_crypto::prelude::*;
///
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
/// // computations
/// // Define parameters for GlweCiphertext creation
/// let glwe_size = GlweSize(3);
/// let polynomial_size = PolynomialSize(1024);
/// let lwe_dimension = LweDimension(900);
/// let ciphertext_modulus = CiphertextModulus::new_native();
///
/// let mut seeder = new_seeder();
/// let mut secret_generator =
/// SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
/// let lwe_secret_key =
/// allocate_and_generate_new_binary_lwe_secret_key(lwe_dimension, &mut secret_generator);
///
/// let mut glwe_secret_key =
/// GlweSecretKey::new_empty_key(0u64, glwe_size.to_glwe_dimension(), polynomial_size);
///
/// generate_tpksk_output_glwe_secret_key(&lwe_secret_key, &mut glwe_secret_key);
///
/// let decomp_base_log = DecompositionBaseLog(2);
/// let decomp_level_count = DecompositionLevelCount(8);
/// let var_small = Variance::from_variance(2f64.powf(-80.0));
/// let mut seeder = new_seeder();
/// let seeder = seeder.as_mut();
/// let mut encryption_generator =
/// EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
///
/// let mut lwe_tpksk = LweTracePackingKeyswitchKey::new(
/// 0u64,
/// decomp_base_log,
/// decomp_level_count,
/// lwe_dimension.to_lwe_size(),
/// glwe_size,
/// polynomial_size,
/// ciphertext_modulus,
/// );
///
/// generate_lwe_trace_packing_keyswitch_key(
/// &glwe_secret_key,
/// &mut lwe_tpksk,
/// var_small,
/// &mut encryption_generator,
/// );
/// ```
pub fn generate_tpksk_output_glwe_secret_key<Scalar, InputKeyCont, OutputKeyCont>(
input_lwe_secret_key: &LweSecretKey<InputKeyCont>,
output_glwe_secret_key: &mut GlweSecretKey<OutputKeyCont>,
) where
Scalar: UnsignedTorus,
InputKeyCont: Container<Element = Scalar>,
OutputKeyCont: ContainerMut<Element = Scalar>,
{
let lwe_dimension = input_lwe_secret_key.lwe_dimension();
let glwe_dimension = output_glwe_secret_key.glwe_dimension();
let glwe_poly_size = output_glwe_secret_key.polynomial_size();
assert!(
lwe_dimension.0 <= glwe_dimension.0 * glwe_poly_size.0,
"Mismatched between input_lwe_secret_key dimension {:?} and number of coefficients of \
output_glwe_secret_key {:?}.",
lwe_dimension.0,
glwe_dimension.0 * glwe_poly_size.0
);
let glwe_key_container = output_glwe_secret_key.as_mut();
for (index, lwe_key_bit) in input_lwe_secret_key.as_ref().iter().enumerate() {
if index % glwe_poly_size.0 == 0 {
glwe_key_container[index] = *lwe_key_bit;
} else {
let rem = index % glwe_poly_size.0;
let quo = index / glwe_poly_size.0;
let new_index = (quo + 1) * glwe_poly_size.0 - rem;
glwe_key_container[new_index] = Scalar::ZERO.wrapping_sub(*lwe_key_bit);
}
}
}
/// Fill an [`LWE trace packing keyswitch key`](`LweTracePackingKeyswitchKey`)
/// with an actual key.
pub fn generate_lwe_trace_packing_keyswitch_key<Scalar, InputKeyCont, KSKeyCont, Gen>(
input_glwe_secret_key: &GlweSecretKey<InputKeyCont>,
lwe_tpksk: &mut LweTracePackingKeyswitchKey<KSKeyCont>,
noise_parameters: impl DispersionParameter,
generator: &mut EncryptionRandomGenerator<Gen>,
) where
Scalar: UnsignedTorus,
InputKeyCont: Container<Element = Scalar>,
KSKeyCont: ContainerMut<Element = Scalar>,
Gen: ByteRandomGenerator,
{
assert_eq!(
input_glwe_secret_key.glwe_dimension(),
lwe_tpksk.output_glwe_key_dimension()
);
assert_eq!(
input_glwe_secret_key.polynomial_size(),
lwe_tpksk.polynomial_size()
);
// We retrieve decomposition arguments
let glwe_dimension = lwe_tpksk.output_glwe_key_dimension();
let decomp_level_count = lwe_tpksk.decomposition_level_count();
let decomp_base_log = lwe_tpksk.decomposition_base_log();
let polynomial_size = lwe_tpksk.polynomial_size();
let ciphertext_modulus = lwe_tpksk.ciphertext_modulus();
let automorphism_index_iter = 1..=polynomial_size.log2().0;
let gen_iter = generator
.fork_tpksk_to_tpksk_chunks::<Scalar>(
decomp_level_count,
glwe_dimension.to_glwe_size(),
polynomial_size,
)
.unwrap();
// loop over the before key blocks
for ((auto_index, glwe_keyswitch_block), mut loop_generator) in automorphism_index_iter
.zip(lwe_tpksk.iter_mut())
.zip(gen_iter)
{
let mut auto_glwe_sk = GlweSecretKey::new_empty_key(
Scalar::ZERO,
input_glwe_secret_key.glwe_dimension(),
input_glwe_secret_key.polynomial_size(),
);
let input_key_block_iter = input_glwe_secret_key
.as_ref()
.chunks_exact(polynomial_size.0);
let auto_key_block_iter = auto_glwe_sk.as_mut().chunks_exact_mut(polynomial_size.0);
for (auto_key_block, input_key_block) in auto_key_block_iter.zip(input_key_block_iter) {
let mut output_poly = Polynomial::from_container(auto_key_block);
let input_poly = Polynomial::from_container(input_key_block);
apply_automorphism_wrapping_add_assign(
&mut output_poly,
&input_poly,
2_usize.pow(auto_index as u32) + 1,
);
}
let mut glwe_ksk = GlweKeyswitchKey::from_container(
glwe_keyswitch_block.into_container(),
decomp_base_log,
decomp_level_count,
glwe_dimension.to_glwe_size(),
polynomial_size,
ciphertext_modulus,
);
generate_glwe_keyswitch_key(
&auto_glwe_sk,
input_glwe_secret_key,
&mut glwe_ksk,
noise_parameters,
&mut loop_generator,
);
}
}

View File

@@ -5,8 +5,12 @@
pub mod ggsw_conversion;
pub mod ggsw_encryption;
pub mod glwe_encryption;
pub mod glwe_keyswitch;
pub mod glwe_keyswitch_key_generation;
pub mod glwe_relinearisation_key_generation;
pub mod glwe_sample_extraction;
pub mod glwe_secret_key_generation;
pub mod glwe_tensor_product;
pub mod lwe_bootstrap_key_conversion;
pub mod lwe_bootstrap_key_generation;
pub mod lwe_encryption;
@@ -19,8 +23,12 @@ pub mod lwe_multi_bit_programmable_bootstrapping;
pub mod lwe_private_functional_packing_keyswitch;
pub mod lwe_private_functional_packing_keyswitch_key_generation;
pub mod lwe_programmable_bootstrapping;
pub mod lwe_public_functional_packing_keyswitch;
pub mod lwe_public_functional_packing_keyswitch_key_generation;
pub mod lwe_public_key_generation;
pub mod lwe_secret_key_generation;
pub mod lwe_trace_packing_keyswitch;
pub mod lwe_trace_packing_keyswitch_key_generation;
pub mod lwe_wopbs;
pub mod polynomial_algorithms;
pub mod seeded_ggsw_ciphertext_decompression;
@@ -42,8 +50,12 @@ mod test;
pub use ggsw_conversion::*;
pub use ggsw_encryption::*;
pub use glwe_encryption::*;
pub use glwe_keyswitch::*;
pub use glwe_keyswitch_key_generation::*;
pub use glwe_relinearisation_key_generation::*;
pub use glwe_sample_extraction::*;
pub use glwe_secret_key_generation::*;
pub use glwe_tensor_product::*;
pub use lwe_bootstrap_key_conversion::*;
pub use lwe_bootstrap_key_generation::*;
pub use lwe_encryption::*;
@@ -56,8 +68,12 @@ pub use lwe_multi_bit_programmable_bootstrapping::*;
pub use lwe_private_functional_packing_keyswitch::*;
pub use lwe_private_functional_packing_keyswitch_key_generation::*;
pub use lwe_programmable_bootstrapping::*;
pub use lwe_public_functional_packing_keyswitch::*;
pub use lwe_public_functional_packing_keyswitch_key_generation::*;
pub use lwe_public_key_generation::*;
pub use lwe_secret_key_generation::*;
pub use lwe_trace_packing_keyswitch::*;
pub use lwe_trace_packing_keyswitch_key_generation::*;
pub use lwe_wopbs::*;
pub use seeded_ggsw_ciphertext_decompression::*;
pub use seeded_ggsw_ciphertext_list_decompression::*;

View File

@@ -406,6 +406,33 @@ pub fn polynomial_wrapping_mul<Scalar, OutputCont, LhsCont, RhsCont>(
polynomial_wrapping_add_mul_assign(output, lhs, rhs);
}
/// Multiply a polynomial by a scalar.
///
/// # Note
///
/// Computations wrap around (similar to computing modulo $2^{n\_{bits}}$) when exceeding the
/// unsigned integer capacity.
///
/// # Example
///
/// ```
/// use tfhe::core_crypto::algorithms::polynomial_algorithms::*;
/// use tfhe::core_crypto::entities::*;
/// let mut pol = Polynomial::from_container(vec![1u8, 2, 3, 4, 5, 6]);
/// let scalar = 127u8;
/// polynomial_wrapping_scalar_mul_assign(&mut pol, scalar);
/// assert_eq!(pol.as_ref(), &[127u8, 254, 125, 252, 123, 250]);
/// ```
pub fn polynomial_wrapping_scalar_mul_assign<Scalar, PolyCont>(
output: &mut Polynomial<PolyCont>,
scalar: Scalar,
) where
Scalar: UnsignedInteger,
PolyCont: ContainerMut<Element = Scalar>,
{
slice_wrapping_scalar_mul_assign(output, scalar)
}
/// Fill the output polynomial, with the result of the product of two polynomials, reduced modulo
/// $(X^{N} + 1)$ with the Karatsuba algorithm Complexity: $O(N^{1.58})$
///
@@ -530,6 +557,70 @@ where
}
}
///
pub fn apply_automorphism_wrapping_add_assign<Scalar, OutputCont, PolyCont>(
output: &mut Polynomial<OutputCont>,
input: &Polynomial<PolyCont>,
automorphism_exponent: usize,
) where
Scalar: UnsignedInteger,
OutputCont: ContainerMut<Element = Scalar>,
PolyCont: Container<Element = Scalar>,
{
// check input and output polynomials have the same size
assert_eq!(input.polynomial_size(), output.polynomial_size());
// check the automorphism exponent is odd so the function X -> X^automorphism_exponent is an
// automorphism (assumes polysize is a power of 2)
assert_eq!(automorphism_exponent % 2, 1);
let poly_size = input.polynomial_size().0;
for (index, coef) in input.iter().enumerate() {
let new_index = (index * automorphism_exponent) % poly_size;
if (index * automorphism_exponent) % (2 * poly_size) == new_index {
output[new_index] = output[new_index].wrapping_add(*coef);
} else {
output[new_index] = output[new_index].wrapping_sub(*coef);
}
}
}
pub fn apply_automorphism_assign<Scalar, PolyCont>(
input: &mut Polynomial<PolyCont>,
automorphism_exponent: usize,
) where
Scalar: UnsignedInteger,
PolyCont: ContainerMut<Element = Scalar>,
{
let mut temp = Polynomial::new(Scalar::ZERO, input.polynomial_size());
apply_automorphism_wrapping_add_assign(&mut temp, input, automorphism_exponent);
input.fill(Scalar::ZERO);
polynomial_wrapping_add_assign(input, &temp);
}
pub fn polynomial_list_wrapping_sub_scalar_mul_assign<Scalar, InputCont, OutputCont, PolyCont>(
output_poly_list: &mut PolynomialList<OutputCont>,
input_poly_list: &PolynomialList<InputCont>,
scalar_poly: &Polynomial<PolyCont>,
) where
Scalar: UnsignedInteger,
OutputCont: ContainerMut<Element = Scalar>,
InputCont: Container<Element = Scalar>,
PolyCont: Container<Element = Scalar>,
{
assert_eq!(
output_poly_list.polynomial_size(),
input_poly_list.polynomial_size()
);
assert_eq!(
output_poly_list.polynomial_count(),
input_poly_list.polynomial_count()
);
for (mut output_poly, input_poly) in output_poly_list.iter_mut().zip(input_poly_list.iter()) {
polynomial_wrapping_sub_mul_assign(&mut output_poly, &input_poly, scalar_poly)
}
}
#[cfg(test)]
mod test {
use rand::Rng;

View File

@@ -164,7 +164,7 @@ pub const TEST_PARAMS_3_BITS_127_U128: TestParams<u128> = TestParams {
cbs_level: DecompositionLevelCount(0),
cbs_base_log: DecompositionBaseLog(0),
message_modulus_log: CiphertextModulusLog(3),
ciphertext_modulus: CiphertextModulus::new_unchecked(1 << 127),
ciphertext_modulus: unsafe { CiphertextModulus::new_unchecked(1 << 127) },
};
fn lwe_encrypt_pbs_f128_decrypt_custom_mod<

View File

@@ -87,7 +87,7 @@ pub const TEST_PARAMS_3_BITS_63_U64: TestParams<u64> = TestParams {
cbs_level: DecompositionLevelCount(0),
cbs_base_log: DecompositionBaseLog(0),
message_modulus_log: CiphertextModulusLog(3),
ciphertext_modulus: CiphertextModulus::new_unchecked(1 << 63),
ciphertext_modulus: unsafe { CiphertextModulus::new_unchecked(1 << 63) },
};
pub const DUMMY_NATIVE_U32: TestParams<u32> = TestParams {
@@ -125,7 +125,7 @@ pub const DUMMY_31_U32: TestParams<u32> = TestParams {
cbs_level: DecompositionLevelCount(0),
cbs_base_log: DecompositionBaseLog(0),
message_modulus_log: CiphertextModulusLog(3),
ciphertext_modulus: CiphertextModulus::new_unchecked(1 << 31),
ciphertext_modulus: unsafe { CiphertextModulus::new_unchecked(1 << 31) },
};
// Our representation of non native power of 2 moduli puts the information in the MSBs and leaves
@@ -166,7 +166,7 @@ pub fn get_encoding_with_padding<Scalar: UnsignedInteger>(
if ciphertext_modulus.is_native_modulus() {
Scalar::ONE << (Scalar::BITS - 1)
} else {
Scalar::cast_from(ciphertext_modulus.get() / 2)
Scalar::cast_from(ciphertext_modulus.get_custom_modulus() / 2)
}
}

View File

@@ -1,15 +1,25 @@
//! Module containing the definition of the [`CiphertextModulus`].
use crate::core_crypto::commons::traits::UnsignedInteger;
use core::num::NonZeroU128;
use std::marker::PhantomData;
#[derive(Clone, Copy, PartialEq, Eq)]
/// A value of 0 is always interpreted as a native modulus, this is useful to work with u128 using
/// the native modulus as $2^{128}$ cannot be stored in a u128 value.
/// Private enum to avoid end user mis-instantiating a CiphertextModulus
///
/// This also allows to not rely on an enum which would require a discriminant field adding 8 bytes
/// to store 1 bit of information (native variant vs custom variant with u128 payload).
pub struct CiphertextModulus<Scalar: UnsignedInteger>(u128, PhantomData<Scalar>);
/// NonZeroU128 allows to always have a correct modulus and to have an enum that is no bigger than a
/// u128 with the 0 optimization as the tag then corresponds to the Native variant.
enum CiphertextModulusInner {
Native,
Custom(NonZeroU128),
}
#[derive(Clone, Copy, PartialEq, Eq)]
/// Structure representing a [`CiphertextModulus`] often noted $q$.
pub struct CiphertextModulus<Scalar: UnsignedInteger> {
inner: CiphertextModulusInner,
_scalar: PhantomData<Scalar>,
}
#[derive(serde::Serialize, serde::Deserialize)]
struct SerialiazableLweCiphertextModulus {
@@ -23,8 +33,13 @@ impl<Scalar: UnsignedInteger> serde::Serialize for CiphertextModulus<Scalar> {
where
S: serde::Serializer,
{
let modulus = match self.inner {
CiphertextModulusInner::Native => 0,
CiphertextModulusInner::Custom(modulus) => modulus.get(),
};
SerialiazableLweCiphertextModulus {
modulus: self.get(),
modulus,
scalar_bits: Scalar::BITS,
}
.serialize(serializer)
@@ -49,52 +64,99 @@ impl<'de, Scalar: UnsignedInteger> serde::Deserialize<'de> for CiphertextModulus
)));
}
Ok(CiphertextModulus(thing.modulus, PhantomData))
let res = if thing.modulus == 0 {
CiphertextModulus {
inner: CiphertextModulusInner::Native,
_scalar: PhantomData,
}
} else {
CiphertextModulus {
inner: CiphertextModulusInner::Custom(NonZeroU128::new(thing.modulus).ok_or(
serde::de::Error::custom(
"Got zero modulus for CiphertextModulusInner::Custom variant",
),
)?),
_scalar: PhantomData,
}
};
Ok(res.canonicalize())
}
}
impl<Scalar: UnsignedInteger> CiphertextModulus<Scalar> {
pub const fn new_native() -> Self {
Self(0, PhantomData)
Self {
inner: CiphertextModulusInner::Native,
_scalar: PhantomData,
}
}
pub const fn try_new_power_of_2(exponent: usize) -> Result<Self, &'static str> {
if exponent > Scalar::BITS {
Err("Modulus is bigger than the maximum value of the associated Scalar type")
} else {
let modulus = match 1u128.checked_shl(exponent as u32) {
Some(modulus) => modulus,
None => 0,
let res = match 1u128.checked_shl(exponent as u32) {
Some(modulus) => {
let non_zero_modulus = match NonZeroU128::new(modulus) {
Some(val) => val,
None => {
panic!("Got zero modulus for CiphertextModulusInner::Custom variant",)
}
};
Self {
inner: CiphertextModulusInner::Custom(non_zero_modulus),
_scalar: PhantomData,
}
}
None => {
assert!(exponent == 128);
assert!(Scalar::BITS == 128);
Self {
inner: CiphertextModulusInner::Native,
_scalar: PhantomData,
}
}
};
Ok(Self(modulus, PhantomData).canonicalize())
Ok(res.canonicalize())
}
}
pub const fn canonicalize(self) -> Self {
match self.inner {
CiphertextModulusInner::Native => self,
CiphertextModulusInner::Custom(modulus) => {
if Scalar::BITS < 128 && modulus.get() == (1 << Scalar::BITS) {
CiphertextModulus::new_native()
} else {
self
}
}
}
}
#[cfg(test)]
pub const fn new_unchecked(modulus: u128) -> Self {
Self(modulus, PhantomData)
}
#[inline]
/// Return the u128 value storing the modulus. This returns 0 if the modulus is the native
/// modulus of the associated scalar type.
pub const fn get(&self) -> u128 {
self.0
}
pub const fn canonicalize(self) -> Self {
if self.is_native_modulus() {
Self(0, PhantomData)
} else {
self
}
/// # Safety
/// modulus needs to be able to fit in the associated Scalar type
pub const unsafe fn new_unchecked(modulus: u128) -> Self {
let res = match modulus {
0 => Self {
inner: CiphertextModulusInner::Native,
_scalar: PhantomData,
},
_ => Self {
inner: CiphertextModulusInner::Custom(NonZeroU128::new_unchecked(modulus)),
_scalar: PhantomData,
},
};
res.canonicalize()
}
pub fn get_scaling_to_native_torus(&self) -> Scalar {
if !self.is_native_modulus() {
Scalar::ONE.wrapping_shl(Scalar::BITS as u32 - self.0.ilog2())
} else {
Scalar::ONE
match self.inner {
CiphertextModulusInner::Native => Scalar::ONE,
CiphertextModulusInner::Custom(modulus) => {
Scalar::ONE.wrapping_shl(Scalar::BITS as u32 - modulus.ilog2())
}
}
}
@@ -103,15 +165,17 @@ impl<Scalar: UnsignedInteger> CiphertextModulus<Scalar> {
/// implementations than can rely on wrapping arithmetic operations behavior to compute the
/// modulus.
pub const fn is_native_modulus(&self) -> bool {
if self.0 == 0 {
return true;
}
matches!(self.inner, CiphertextModulusInner::Native)
}
if Scalar::BITS < 128 {
return self.0 == (1 << Scalar::BITS);
/// Panics if the modulus is not a custom modulus
pub const fn get_custom_modulus(&self) -> u128 {
match self.inner {
CiphertextModulusInner::Native => {
panic!("Tried getting custom modulus from native modulus")
}
CiphertextModulusInner::Custom(modulus) => modulus.get(),
}
false
}
pub const fn is_compatible_with_native_modulus(&self) -> bool {
@@ -119,16 +183,20 @@ impl<Scalar: UnsignedInteger> CiphertextModulus<Scalar> {
}
pub const fn is_power_of_two(&self) -> bool {
self.0.is_power_of_two()
match self.inner {
CiphertextModulusInner::Native => true,
CiphertextModulusInner::Custom(modulus) => modulus.is_power_of_two(),
}
}
}
impl<Scalar: UnsignedInteger> std::fmt::Display for CiphertextModulus<Scalar> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.is_native_modulus() {
write!(f, "CiphertextModulus(2^{})", Scalar::BITS)
} else {
write!(f, "CiphertextModulus({})", self.0)
match self.inner {
CiphertextModulusInner::Native => write!(f, "CiphertextModulus(2^{})", Scalar::BITS),
CiphertextModulusInner::Custom(modulus) => {
write!(f, "CiphertextModulus({})", modulus.get())
}
}
}
}
@@ -145,6 +213,13 @@ mod tests {
#[test]
fn test_modulus_struct() {
assert!(std::mem::size_of::<CiphertextModulus<u32>>() == std::mem::size_of::<u128>());
assert!(std::mem::size_of::<CiphertextModulus<u64>>() == std::mem::size_of::<u128>());
assert!(std::mem::size_of::<CiphertextModulus<u128>>() == std::mem::size_of::<u128>());
assert!(std::mem::align_of::<CiphertextModulus<u32>>() == std::mem::align_of::<u128>());
assert!(std::mem::align_of::<CiphertextModulus<u64>>() == std::mem::align_of::<u128>());
assert!(std::mem::align_of::<CiphertextModulus<u128>>() == std::mem::align_of::<u128>());
{
let mod_32_res = CiphertextModulus::<u32>::try_new_power_of_2(32);
assert!(mod_32_res.is_ok());
@@ -174,6 +249,28 @@ mod tests {
{
let native_mod_128 = CiphertextModulus::<u128>::new_native();
assert!(native_mod_128.is_native_modulus());
let ser = bincode::serialize(&native_mod_128).unwrap();
let deser: CiphertextModulus<u128> = bincode::deserialize(&ser).unwrap();
assert_eq!(native_mod_128, deser);
let deser_error: Result<CiphertextModulus<u32>, _> = bincode::deserialize(&ser);
assert!(deser_error.is_err());
match deser_error {
Ok(_) => unreachable!(),
Err(e) => match *e {
bincode::ErrorKind::Custom(err) => {
assert_eq!(
err.as_str(),
"Expected an unsigned integer with 32 bits, \
found 128 bits during deserialization of CiphertextModulus, \
have you mixed types during deserialization?",
);
}
_ => unreachable!(),
},
}
}
{
@@ -181,7 +278,29 @@ mod tests {
assert!(mod_128_res.is_ok());
let mod_128 = mod_128_res.unwrap();
assert_eq!(mod_128.get(), 1 << 64);
assert_eq!(mod_128.get_custom_modulus(), 1 << 64);
let ser = bincode::serialize(&mod_128).unwrap();
let deser: CiphertextModulus<u128> = bincode::deserialize(&ser).unwrap();
assert_eq!(mod_128, deser);
let deser_error: Result<CiphertextModulus<u32>, _> = bincode::deserialize(&ser);
assert!(deser_error.is_err());
match deser_error {
Ok(_) => unreachable!(),
Err(e) => match *e {
bincode::ErrorKind::Custom(err) => {
assert_eq!(
err.as_str(),
"Expected an unsigned integer with 32 bits, \
found 128 bits during deserialization of CiphertextModulus, \
have you mixed types during deserialization?",
);
}
_ => unreachable!(),
},
}
}
}
}

View File

@@ -169,6 +169,31 @@ impl<G: ByteRandomGenerator> EncryptionRandomGenerator<G> {
self.try_fork(lwe_size.0, mask_bytes, noise_bytes)
}
// Forks the generator, when splitting a tpksk into chunks
pub(crate) fn fork_tpksk_to_tpksk_chunks<T: UnsignedInteger>(
&mut self,
level: DecompositionLevelCount,
glwe_size: GlweSize,
poly_size: PolynomialSize,
) -> Result<impl Iterator<Item = EncryptionRandomGenerator<G>>, ForkError> {
let mask_bytes = mask_bytes_per_tpksk_chunk::<T>(level, glwe_size, poly_size);
let noise_bytes = noise_bytes_per_tpksk_chunk(level, poly_size);
self.try_fork(poly_size.log2().0, mask_bytes, noise_bytes)
}
// Forks the generator, when splitting a glwe keyswitch into chunks
pub(crate) fn fork_glweks_to_glweks_chunks<T: UnsignedInteger>(
&mut self,
level: DecompositionLevelCount,
input_glwe_dimension: GlweDimension,
output_glwe_size: GlweSize,
poly_size: PolynomialSize,
) -> Result<impl Iterator<Item = EncryptionRandomGenerator<G>>, ForkError> {
let mask_bytes = mask_bytes_per_glweks_chunk::<T>(level, output_glwe_size, poly_size);
let noise_bytes = noise_bytes_per_glweks_chunk(level, poly_size);
self.try_fork(input_glwe_dimension.0, mask_bytes, noise_bytes)
}
// Forks both generators into an iterator
fn try_fork(
&mut self,
@@ -229,7 +254,11 @@ impl<G: ByteRandomGenerator> EncryptionRandomGenerator<G> {
where
Scalar: UnsignedInteger + RandomGenerable<Gaussian<f64>, CustomModulus = f64>,
{
let custom_modulus_f64: f64 = custom_modulus.get().cast_into();
if custom_modulus.is_native_modulus() {
return self.random_noise(std);
}
let custom_modulus_f64: f64 = custom_modulus.get_custom_modulus().cast_into();
Scalar::generate_one_custom_modulus(
&mut self.noise,
Gaussian {
@@ -427,6 +456,22 @@ impl<G: ParallelByteRandomGenerator> EncryptionRandomGenerator<G> {
self.par_try_fork(lwe_size.0, mask_bytes, noise_bytes)
}
// Forks the generator, when splitting a tpksk into chunks
pub(crate) fn par_fork_tpksk_to_tpksk_chunks<T: UnsignedInteger>(
&mut self,
level: DecompositionLevelCount,
glwe_size: GlweSize,
poly_size: PolynomialSize,
) -> Result<impl IndexedParallelIterator<Item = EncryptionRandomGenerator<G>>, ForkError> {
let mask_bytes = mask_bytes_per_tpksk_chunk::<T>(level, glwe_size, poly_size);
let noise_bytes = noise_bytes_per_tpksk_chunk(level, poly_size);
self.par_try_fork(
poly_size.log2().0 * glwe_size.to_glwe_dimension().0,
mask_bytes,
noise_bytes,
)
}
// Forks both generators into a parallel iterator.
fn par_try_fork(
&mut self,
@@ -500,6 +545,32 @@ fn mask_bytes_per_pfpksk<T: UnsignedInteger>(
lwe_size.0 * mask_bytes_per_pfpksk_chunk::<T>(level, glwe_size, poly_size)
}
fn mask_bytes_per_tpksk_chunk<T: UnsignedInteger>(
level: DecompositionLevelCount,
glwe_size: GlweSize,
poly_size: PolynomialSize,
) -> usize {
glwe_size.to_glwe_dimension().0 * mask_bytes_per_glweks_chunk::<T>(level, glwe_size, poly_size)
}
fn mask_bytes_per_tpksk<T: UnsignedInteger>(
level: DecompositionLevelCount,
glwe_size: GlweSize,
poly_size: PolynomialSize,
) -> usize {
poly_size.log2().0 * mask_bytes_per_tpksk_chunk::<T>(level, glwe_size, poly_size)
}
fn mask_bytes_per_glweks_chunk<T: UnsignedInteger>(
level: DecompositionLevelCount,
glwe_size: GlweSize,
poly_size: PolynomialSize,
) -> usize {
glwe_size.to_glwe_dimension().0
* level.0
* mask_bytes_per_glwe::<T>(glwe_size.to_glwe_dimension(), poly_size)
}
fn noise_bytes_per_coef() -> usize {
// We use f64 to sample the noise for every precision, and we need 4/pi inputs to generate
// such an output (here we take 32 to keep a safety margin).
@@ -549,17 +620,39 @@ fn noise_bytes_per_pfpksk(
lwe_size.0 * noise_bytes_per_pfpksk_chunk(level, poly_size)
}
fn noise_bytes_per_tpksk_chunk(level: DecompositionLevelCount, poly_size: PolynomialSize) -> usize {
level.0 * noise_bytes_per_glwe(poly_size)
}
fn noise_bytes_per_tpksk(
level: DecompositionLevelCount,
poly_size: PolynomialSize,
glwe_size: GlweSize,
) -> usize {
glwe_size.to_glwe_dimension().0
* poly_size.log2().0
* noise_bytes_per_tpksk_chunk(level, poly_size)
}
fn noise_bytes_per_glweks_chunk(
level: DecompositionLevelCount,
poly_size: PolynomialSize,
) -> usize {
level.0 * noise_bytes_per_glwe(poly_size)
}
#[cfg(test)]
mod test {
use crate::core_crypto::algorithms::*;
use crate::core_crypto::commons::dispersion::Variance;
use crate::core_crypto::commons::dispersion::{StandardDev, Variance};
use crate::core_crypto::commons::parameters::{
CiphertextModulus, DecompositionBaseLog, DecompositionLevelCount, GlweSize, LweDimension,
PolynomialSize,
};
use crate::core_crypto::commons::test_tools::{
new_encryption_random_generator, new_secret_random_generator,
new_encryption_random_generator, new_secret_random_generator, normality_test_f64,
};
use crate::core_crypto::commons::traits::UnsignedTorus;
#[test]
fn test_gaussian_sampling_margin_factor_does_not_panic() {
@@ -598,4 +691,541 @@ mod test {
&mut enc_generator,
);
}
fn noise_gen_native<Scalar: UnsignedTorus>() {
let mut gen = new_encryption_random_generator();
let bits = (Scalar::BITS / 2) as i32;
for _ in 0..1000 {
let mut retries = 100;
let mut val = Scalar::ZERO;
while retries >= 0 {
val = gen.random_noise(StandardDev(2.0f64.powi(-bits)));
if val != Scalar::ZERO {
break;
}
retries -= 1;
}
assert!(retries != 0);
assert!(val != Scalar::ZERO);
}
}
#[test]
fn noise_gen_native_u32() {
noise_gen_native::<u32>();
}
#[test]
fn noise_gen_native_u64() {
noise_gen_native::<u64>();
}
#[test]
fn noise_gen_native_u128() {
noise_gen_native::<u128>();
}
fn noise_gen_custom_mod<Scalar: UnsignedTorus>(ciphertext_modulus: CiphertextModulus<Scalar>) {
let mut gen = new_encryption_random_generator();
let bits = (Scalar::BITS / 2) as i32;
for _ in 0..1000 {
let mut retries = 100;
let mut val = Scalar::ZERO;
while retries >= 0 {
val = gen
.random_noise_custom_mod(StandardDev(2.0f64.powi(-bits)), ciphertext_modulus);
if val != Scalar::ZERO {
break;
}
retries -= 1;
}
assert!(retries != 0);
assert!(val != Scalar::ZERO);
}
}
#[test]
fn noise_gen_custom_mod_u32() {
noise_gen_custom_mod::<u32>(CiphertextModulus::try_new_power_of_2(31).unwrap());
}
#[test]
fn noise_gen_custom_mod_u64() {
noise_gen_custom_mod::<u64>(CiphertextModulus::try_new_power_of_2(63).unwrap());
}
#[test]
fn noise_gen_custom_mod_u128() {
noise_gen_custom_mod::<u128>(CiphertextModulus::try_new_power_of_2(127).unwrap());
}
#[test]
fn noise_gen_native_custom_mod_u32() {
noise_gen_custom_mod::<u32>(CiphertextModulus::new_native());
}
#[test]
fn noise_gen_native_custom_mod_u64() {
noise_gen_custom_mod::<u64>(CiphertextModulus::new_native());
}
#[test]
fn noise_gen_native_custom_mod_u128() {
noise_gen_custom_mod::<u128>(CiphertextModulus::new_native());
}
fn noise_gen_slice_native<Scalar: UnsignedTorus>() {
let mut gen = new_encryption_random_generator();
let bits = (Scalar::BITS / 2) as i32;
let mut vec = vec![Scalar::ZERO; 1000];
let mut retries = 100;
while retries >= 0 {
gen.fill_slice_with_random_noise(&mut vec, StandardDev(2.0f64.powi(-bits)));
if vec.iter().all(|&x| x != Scalar::ZERO) {
break;
}
retries -= 1;
}
assert!(retries != 0);
assert!(vec.iter().all(|&x| x != Scalar::ZERO));
}
#[test]
fn noise_gen_slice_native_u32() {
noise_gen_slice_native::<u32>();
}
#[test]
fn noise_gen_slice_native_u64() {
noise_gen_slice_native::<u64>();
}
#[test]
fn noise_gen_slice_native_u128() {
noise_gen_slice_native::<u128>();
}
fn test_normal_random_encryption_native<Scalar: UnsignedTorus>() {
const RUNS: usize = 10000;
const SAMPLES_PER_RUN: usize = 1000;
let mut rng = new_encryption_random_generator();
let failures: f64 = (0..RUNS)
.map(|_| {
let mut samples = vec![Scalar::ZERO; SAMPLES_PER_RUN];
rng.fill_slice_with_random_noise(&mut samples, StandardDev(f64::powi(2., -20)));
let samples: Vec<f64> = samples
.iter()
.copied()
.map(|x| {
let torus = x.into_torus();
// The upper half of the torus corresponds to the negative domain when
// mapping unsigned integer back to float (MSB or
// sign bit is set)
if torus > 0.5 {
torus - 1.0
} else {
torus
}
})
.collect();
if normality_test_f64(&samples, 0.05).null_hypothesis_is_valid {
// If we are normal return 0, it's not a failure
0.0
} else {
1.0
}
})
.sum::<f64>();
let failure_rate = failures / (RUNS as f64);
println!("failure_rate: {failure_rate}");
// The expected failure rate even on proper gaussian is 5%, so we take a small safety margin
assert!(failure_rate <= 0.065);
}
#[test]
fn test_normal_random_encryption_native_u32() {
test_normal_random_encryption_native::<u32>();
}
#[test]
fn test_normal_random_encryption_native_u64() {
test_normal_random_encryption_native::<u64>();
}
#[test]
fn test_normal_random_encryption_native_u128() {
test_normal_random_encryption_native::<u128>();
}
fn test_normal_random_encryption_add_assign_native<Scalar: UnsignedTorus>() {
const RUNS: usize = 10000;
const SAMPLES_PER_RUN: usize = 1000;
let mut rng = new_encryption_random_generator();
let failures: f64 = (0..RUNS)
.map(|_| {
let mut samples = vec![Scalar::ZERO; SAMPLES_PER_RUN];
rng.unsigned_torus_slice_wrapping_add_random_noise_assign(
&mut samples,
StandardDev(f64::powi(2., -20)),
);
let samples: Vec<f64> = samples
.iter()
.copied()
.map(|x| {
let torus = x.into_torus();
// The upper half of the torus corresponds to the negative domain when
// mapping unsigned integer back to float (MSB or
// sign bit is set)
if torus > 0.5 {
torus - 1.0
} else {
torus
}
})
.collect();
if normality_test_f64(&samples, 0.05).null_hypothesis_is_valid {
// If we are normal return 0, it's not a failure
0.0
} else {
1.0
}
})
.sum::<f64>();
let failure_rate = failures / (RUNS as f64);
println!("failure_rate: {failure_rate}");
// The expected failure rate even on proper gaussian is 5%, so we take a small safety margin
assert!(failure_rate <= 0.065);
}
#[test]
fn test_normal_random_encryption_add_assign_native_u32() {
test_normal_random_encryption_add_assign_native::<u32>();
}
#[test]
fn test_normal_random_encryption_add_assign_native_u64() {
test_normal_random_encryption_add_assign_native::<u64>();
}
#[test]
fn test_normal_random_encryption_add_assign_native_u128() {
test_normal_random_encryption_add_assign_native::<u128>();
}
fn noise_gen_slice_custom_mod<Scalar: UnsignedTorus>(
ciphertext_modulus: CiphertextModulus<Scalar>,
) {
let mut gen = new_encryption_random_generator();
let bits = (Scalar::BITS / 2) as i32;
let mut vec = vec![Scalar::ZERO; 1000];
let mut retries = 100;
while retries >= 0 {
gen.fill_slice_with_random_noise_custom_mod(
&mut vec,
StandardDev(2.0f64.powi(-bits)),
ciphertext_modulus,
);
if vec.iter().all(|&x| x != Scalar::ZERO) {
break;
}
retries -= 1;
}
assert!(retries != 0);
assert!(vec.iter().all(|&x| x != Scalar::ZERO));
}
#[test]
fn noise_gen_slice_custom_mod_u32() {
noise_gen_slice_custom_mod::<u32>(CiphertextModulus::try_new_power_of_2(31).unwrap());
}
#[test]
fn noise_gen_slice_custom_mod_u64() {
noise_gen_slice_custom_mod::<u64>(CiphertextModulus::try_new_power_of_2(63).unwrap());
}
#[test]
fn noise_gen_slice_custom_mod_u128() {
noise_gen_slice_custom_mod::<u128>(CiphertextModulus::try_new_power_of_2(127).unwrap());
}
#[test]
fn noise_gen_slice_native_custom_mod_u32() {
noise_gen_slice_custom_mod::<u32>(CiphertextModulus::new_native());
}
#[test]
fn noise_gen_slice_native_custom_mod_u64() {
noise_gen_slice_custom_mod::<u64>(CiphertextModulus::new_native());
}
#[test]
fn noise_gen_slice_native_custom_mod_u128() {
noise_gen_slice_custom_mod::<u128>(CiphertextModulus::new_native());
}
fn test_normal_random_encryption_custom_mod<Scalar: UnsignedTorus>(
ciphertext_modulus: CiphertextModulus<Scalar>,
) {
const RUNS: usize = 10000;
const SAMPLES_PER_RUN: usize = 1000;
let mut rng = new_encryption_random_generator();
let failures: f64 = (0..RUNS)
.map(|_| {
let mut samples = vec![Scalar::ZERO; SAMPLES_PER_RUN];
rng.fill_slice_with_random_noise_custom_mod(
&mut samples,
StandardDev(f64::powi(2., -20)),
ciphertext_modulus,
);
let samples: Vec<f64> = samples
.iter()
.copied()
.map(|x| {
let torus = x.into_torus();
// The upper half of the torus corresponds to the negative domain when
// mapping unsigned integer back to float (MSB or
// sign bit is set)
if torus > 0.5 {
torus - 1.0
} else {
torus
}
})
.collect();
if normality_test_f64(&samples, 0.05).null_hypothesis_is_valid {
// If we are normal return 0, it's not a failure
0.0
} else {
1.0
}
})
.sum::<f64>();
let failure_rate = failures / (RUNS as f64);
println!("failure_rate: {failure_rate}");
// The expected failure rate even on proper gaussian is 5%, so we take a small safety margin
assert!(failure_rate <= 0.065);
}
#[test]
fn test_normal_random_encryption_custom_mod_u32() {
test_normal_random_encryption_custom_mod::<u32>(
CiphertextModulus::try_new_power_of_2(31).unwrap(),
);
}
#[test]
fn test_normal_random_encryption_custom_mod_u64() {
test_normal_random_encryption_custom_mod::<u64>(
CiphertextModulus::try_new_power_of_2(63).unwrap(),
);
}
#[test]
fn test_normal_random_encryption_custom_mod_u128() {
test_normal_random_encryption_custom_mod::<u128>(
CiphertextModulus::try_new_power_of_2(127).unwrap(),
);
}
#[test]
fn test_normal_random_encryption_native_custom_mod_u32() {
test_normal_random_encryption_custom_mod::<u32>(CiphertextModulus::new_native());
}
#[test]
fn test_normal_random_encryption_native_custom_mod_u64() {
test_normal_random_encryption_custom_mod::<u64>(CiphertextModulus::new_native());
}
#[test]
fn test_normal_random_encryption_native_custom_mod_u128() {
test_normal_random_encryption_custom_mod::<u128>(CiphertextModulus::new_native());
}
fn test_normal_random_encryption_add_assign_custom_mod<Scalar: UnsignedTorus>(
ciphertext_modulus: CiphertextModulus<Scalar>,
) {
const RUNS: usize = 10000;
const SAMPLES_PER_RUN: usize = 1000;
let mut rng = new_encryption_random_generator();
let failures: f64 = (0..RUNS)
.map(|_| {
let mut samples = vec![Scalar::ZERO; SAMPLES_PER_RUN];
rng.unsigned_torus_slice_wrapping_add_random_noise_custom_mod_assign(
&mut samples,
StandardDev(f64::powi(2., -20)),
ciphertext_modulus,
);
let samples: Vec<f64> = samples
.iter()
.copied()
.map(|x| {
let torus = x.into_torus();
// The upper half of the torus corresponds to the negative domain when
// mapping unsigned integer back to float (MSB or
// sign bit is set)
if torus > 0.5 {
torus - 1.0
} else {
torus
}
})
.collect();
if normality_test_f64(&samples, 0.05).null_hypothesis_is_valid {
// If we are normal return 0, it's not a failure
0.0
} else {
1.0
}
})
.sum::<f64>();
let failure_rate = failures / (RUNS as f64);
println!("failure_rate: {failure_rate}");
// The expected failure rate even on proper gaussian is 5%, so we take a small safety margin
assert!(failure_rate <= 0.065);
}
#[test]
fn test_normal_random_encryption_add_assign_custom_mod_u32() {
test_normal_random_encryption_add_assign_custom_mod::<u32>(
CiphertextModulus::try_new_power_of_2(31).unwrap(),
);
}
#[test]
fn test_normal_random_encryption_add_assign_custom_mod_u64() {
test_normal_random_encryption_add_assign_custom_mod::<u64>(
CiphertextModulus::try_new_power_of_2(63).unwrap(),
);
}
#[test]
fn test_normal_random_encryption_add_assign_custom_mod_u128() {
test_normal_random_encryption_add_assign_custom_mod::<u128>(
CiphertextModulus::try_new_power_of_2(127).unwrap(),
);
}
#[test]
fn test_normal_random_encryption_add_assign_native_custom_mod_u32() {
test_normal_random_encryption_add_assign_custom_mod::<u32>(CiphertextModulus::new_native());
}
#[test]
fn test_normal_random_encryption_add_assign_native_custom_mod_u64() {
test_normal_random_encryption_add_assign_custom_mod::<u64>(CiphertextModulus::new_native());
}
#[test]
fn test_normal_random_encryption_add_assign_native_custom_mod_u128() {
test_normal_random_encryption_add_assign_custom_mod::<u128>(CiphertextModulus::new_native());
}
fn mask_gen_slice_native<Scalar: UnsignedTorus>() {
let mut gen = new_encryption_random_generator();
let mut vec = vec![Scalar::ZERO; 1000];
let mut retries = 100;
while retries >= 0 {
gen.fill_slice_with_random_mask(&mut vec);
if vec.iter().all(|&x| x != Scalar::ZERO) {
break;
}
retries -= 1;
}
assert!(retries != 0);
assert!(vec.iter().all(|&x| x != Scalar::ZERO));
}
#[test]
fn mask_gen_native_u32() {
mask_gen_slice_native::<u32>();
}
#[test]
fn mask_gen_native_u64() {
mask_gen_slice_native::<u64>();
}
#[test]
fn mask_gen_native_u128() {
mask_gen_slice_native::<u128>();
}
fn mask_gen_slice_custom_mod<Scalar: UnsignedTorus>(
ciphertext_modulus: CiphertextModulus<Scalar>,
) {
let mut gen = new_encryption_random_generator();
let mut vec = vec![Scalar::ZERO; 1000];
let mut retries = 100;
while retries >= 0 {
gen.fill_slice_with_random_mask_custom_mod(&mut vec, ciphertext_modulus);
if vec.iter().all(|&x| x != Scalar::ZERO) {
break;
}
retries -= 1;
}
assert!(retries != 0);
assert!(vec.iter().all(|&x| x != Scalar::ZERO));
}
#[test]
fn mask_gen_slice_custom_mod_u32() {
mask_gen_slice_custom_mod::<u32>(CiphertextModulus::try_new_power_of_2(31).unwrap());
}
#[test]
fn mask_gen_slice_custom_mod_u64() {
mask_gen_slice_custom_mod::<u64>(CiphertextModulus::try_new_power_of_2(63).unwrap());
}
#[test]
fn mask_gen_slice_custom_mod_u128() {
mask_gen_slice_custom_mod::<u128>(CiphertextModulus::try_new_power_of_2(127).unwrap());
}
#[test]
fn mask_gen_slice_native_custom_mod_u32() {
mask_gen_slice_custom_mod::<u32>(CiphertextModulus::new_native());
}
#[test]
fn mask_gen_slice_native_custom_mod_u64() {
mask_gen_slice_custom_mod::<u64>(CiphertextModulus::new_native());
}
#[test]
fn mask_gen_slice_native_custom_mod_u128() {
mask_gen_slice_custom_mod::<u128>(CiphertextModulus::new_native());
}
}

View File

@@ -1,4 +1,6 @@
use crate::core_crypto::commons::math::decomposition::SignedDecompositionIter;
use crate::core_crypto::commons::math::decomposition::{
SignedDecompositionIter, SliceSignedDecompositionIter,
};
use crate::core_crypto::commons::numeric::{Numeric, UnsignedInteger};
use crate::core_crypto::commons::parameters::{DecompositionBaseLog, DecompositionLevelCount};
use std::marker::PhantomData;
@@ -113,6 +115,29 @@ where
res << non_rep_bit_count
}
/// Fills a mutable tensor-like objects with the closest representable values from another
/// tensor-like object.
///
/// # Example
///
/// ```rust
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
/// use tfhe::core_crypto::prelude::{DecompositionBaseLog, DecompositionLevelCount};
/// let decomposer =
/// SignedDecomposer::<u32>::new(DecompositionBaseLog(4), DecompositionLevelCount(3));
///
/// let input = vec![1_340_987_234_u32; 2];
/// let mut closest = vec![0u32; 2];
/// decomposer.fill_slice_with_closest_representable(&mut closest, &input);
/// assert!(closest.iter().all(|&x| x == 1_341_128_704_u32));
/// ```
pub fn fill_slice_with_closest_representable(&self, output: &mut [Scalar], input: &[Scalar]) {
output
.iter_mut()
.zip(input.iter())
.for_each(|(dst, &src)| *dst = self.closest_representable(src));
}
/// Generate an iterator over the terms of the decomposition of the input.
///
/// # Warning
@@ -173,4 +198,87 @@ where
None
}
}
/// Generates an iterator-like object over tensors of terms of the decomposition of the input
/// tensor.
///
/// # Warning
///
/// The returned iterator yields the terms $(\tilde{\theta}^{(a)}\_i)\_{a\in\mathbb{N}}$ in
/// order of decreasing $i$.
///
/// # Example
///
/// ```rust
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
/// use tfhe::core_crypto::commons::numeric::UnsignedInteger;
/// use tfhe::core_crypto::prelude::{DecompositionBaseLog, DecompositionLevelCount};
/// let decomposer =
/// SignedDecomposer::<u32>::new(DecompositionBaseLog(4), DecompositionLevelCount(3));
/// let decomposable = vec![1_340_987_234_u32, 1_340_987_234_u32];
/// let mut decomp = decomposer.decompose_slice(&decomposable);
///
/// let mut count = 0;
/// while let Some(term) = decomp.next_term() {
/// assert!(1 <= term.level().0);
/// assert!(term.level().0 <= 3);
/// for elmt in term.as_slice().iter() {
/// let signed_term = elmt.into_signed();
/// let half_basis = 2i32.pow(4) / 2i32;
/// assert!(-half_basis <= signed_term);
/// assert!(signed_term < half_basis);
/// }
/// count += 1;
/// }
/// assert_eq!(count, 3);
/// ```
pub fn decompose_slice(&self, input: &[Scalar]) -> SliceSignedDecompositionIter<Scalar> {
// Note that there would be no sense of making the decomposition on an input which was
// not rounded to the closest representable first. We then perform it before decomposing.
let mut rounded = vec![Scalar::ZERO; input.len()];
self.fill_slice_with_closest_representable(&mut rounded, input);
SliceSignedDecompositionIter::new(
&rounded,
DecompositionBaseLog(self.base_log),
DecompositionLevelCount(self.level_count),
)
}
/// Fills the output tensor with the recomposition of an other tensor.
///
/// Returns `Some(())` if the decomposition was fresh, and the output was filled with a
/// recomposition, and `None`, if not.
///
/// # Example
///
/// ```rust
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
/// use tfhe::core_crypto::prelude::{DecompositionBaseLog, DecompositionLevelCount};
/// let decomposer =
/// SignedDecomposer::<u32>::new(DecompositionBaseLog(4), DecompositionLevelCount(3));
/// let decomposable = vec![1_340_987_234_u32; 2];
/// let mut rounded = vec![0u32; 2];
/// decomposer.fill_slice_with_closest_representable(&mut rounded, &decomposable);
/// let mut decomp = decomposer.decompose_slice(&rounded);
/// let mut recomposition = vec![0u32; 2];
/// decomposer
/// .fill_slice_with_recompose(decomp, &mut recomposition)
/// .unwrap();
/// assert_eq!(recomposition, rounded);
/// ```
pub fn fill_slice_with_recompose(
&self,
decomp: SliceSignedDecompositionIter<Scalar>,
output: &mut [Scalar],
) -> Option<()> {
let mut decomp = decomp;
if decomp.is_fresh() {
while let Some(term) = decomp.next_term() {
term.update_slice_with_recomposition_summand_wrapping_addition(output);
}
Some(())
} else {
None
}
}
}

View File

@@ -1,4 +1,6 @@
use crate::core_crypto::commons::math::decomposition::{DecompositionLevel, DecompositionTerm};
use crate::core_crypto::commons::math::decomposition::{
DecompositionLevel, DecompositionTerm, DecompositionTermSlice,
};
use crate::core_crypto::commons::numeric::UnsignedInteger;
use crate::core_crypto::commons::parameters::{DecompositionBaseLog, DecompositionLevelCount};
@@ -114,6 +116,155 @@ where
}
}
/// An iterator-like object that yields the terms of the signed decomposition of a tensor of values.
///
/// # Note
///
/// On each call to [`SliceSignedDecompositionIter::next_term`], this structure yields a new
/// [`DecompositionTermSlice`], backed by a `Vec` owned by the structure. This vec is mutated at
/// each call of the `next_term` method, and as such the term must be dropped before `next_term` is
/// called again.
///
/// Such a pattern can not be implemented with iterators yet (without GATs), which is why this
/// iterator must be explicitly called.
///
/// # Warning
///
/// This iterator yields the decomposition in reverse order. That means that the highest level
/// will be yielded first.
pub struct SliceSignedDecompositionIter<Scalar>
where
Scalar: UnsignedInteger,
{
// The base log of the decomposition
base_log: usize,
// The number of levels of the decomposition
level_count: usize,
// The current level
current_level: usize,
// A mask which allows to compute the mod B of a value. For B=2^4, this guy is of the form:
// ...0001111
mod_b_mask: Scalar,
// The values being decomposed
inputs: Vec<Scalar>,
// The internal states of each decomposition
states: Vec<Scalar>,
// In order to avoid allocating a new Vec every time we yield a decomposition term, we store
// a Vec inside the structure and yield slices pointing to it.
outputs: Vec<Scalar>,
// A flag which stores whether the iterator is a fresh one (for the recompose method).
fresh: bool,
}
impl<Scalar> SliceSignedDecompositionIter<Scalar>
where
Scalar: UnsignedInteger,
{
// Creates a new tensor decomposition iterator.
pub(crate) fn new(
input: &[Scalar],
base_log: DecompositionBaseLog,
level: DecompositionLevelCount,
) -> SliceSignedDecompositionIter<Scalar> {
let len = input.len();
SliceSignedDecompositionIter {
base_log: base_log.0,
level_count: level.0,
current_level: level.0,
mod_b_mask: (Scalar::ONE << base_log.0) - Scalar::ONE,
inputs: input.to_vec(),
outputs: vec![Scalar::ZERO; len],
states: input
.iter()
.map(|i| *i >> (Scalar::BITS - base_log.0 * level.0))
.collect(),
fresh: true,
}
}
pub(crate) fn is_fresh(&self) -> bool {
self.fresh
}
/// Returns the logarithm in base two of the base of this decomposition.
///
/// If the decomposition uses a base $B=2^b$, this returns $b$.
///
/// # Example
///
/// ```rust
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
/// use tfhe::core_crypto::prelude::{DecompositionBaseLog, DecompositionLevelCount};
/// let decomposer =
/// SignedDecomposer::<u32>::new(DecompositionBaseLog(4), DecompositionLevelCount(3));
/// let decomposable = vec![1_340_987_234_u32; 2];
/// let decomp = decomposer.decompose_slice(&decomposable);
/// assert_eq!(decomp.base_log(), DecompositionBaseLog(4));
/// ```
pub fn base_log(&self) -> DecompositionBaseLog {
DecompositionBaseLog(self.base_log)
}
/// Returns the number of levels of this decomposition.
///
/// If the decomposition uses $l$ levels, this returns $l$.
///
/// # Example
///
/// ```rust
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
/// use tfhe::core_crypto::prelude::{DecompositionBaseLog, DecompositionLevelCount};
/// let decomposer =
/// SignedDecomposer::<u32>::new(DecompositionBaseLog(4), DecompositionLevelCount(3));
/// let decomposable = vec![1_340_987_234_u32; 2];
/// let decomp = decomposer.decompose_slice(&decomposable);
/// assert_eq!(decomp.level_count(), DecompositionLevelCount(3));
/// ```
pub fn level_count(&self) -> DecompositionLevelCount {
DecompositionLevelCount(self.level_count)
}
/// Yield the next term of the decomposition, if any.
///
/// # Note
///
/// Because this function returns a borrowed tensor, owned by the iterator, the term must be
/// dropped before `next_term` is called again.
///
/// # Example
///
/// ```rust
/// use tfhe::core_crypto::commons::math::decomposition::{DecompositionLevel, SignedDecomposer};
/// use tfhe::core_crypto::prelude::{DecompositionBaseLog, DecompositionLevelCount};
/// let decomposer =
/// SignedDecomposer::<u32>::new(DecompositionBaseLog(4), DecompositionLevelCount(3));
/// let decomposable = vec![1_340_987_234_u32; 2];
/// let mut decomp = decomposer.decompose_slice(&decomposable);
/// let term = decomp.next_term().unwrap();
/// assert_eq!(term.level(), DecompositionLevel(3));
/// assert_eq!(term.as_slice()[0], 4294967295);
/// ```
pub fn next_term(&mut self) -> Option<DecompositionTermSlice<'_, Scalar>> {
// The iterator is not fresh anymore.
self.fresh = false;
// We check if the decomposition is over
if self.current_level == 0 {
return None;
}
// We iterate over the elements of the outputs and decompose
for (output_i, state_i) in self.outputs.iter_mut().zip(self.states.iter_mut()) {
*output_i = decompose_one_level(self.base_log, state_i, self.mod_b_mask);
}
self.current_level -= 1;
// We return the term tensor.
Some(DecompositionTermSlice::new(
DecompositionLevel(self.current_level + 1),
DecompositionBaseLog(self.base_log),
&self.outputs,
))
}
}
fn decompose_one_level<S: UnsignedInteger>(base_log: usize, state: &mut S, mod_b_mask: S) -> S {
let res = *state & mod_b_mask;
*state >>= base_log;

View File

@@ -91,3 +91,115 @@ where
DecompositionLevel(self.level)
}
}
/// A tensor whose elements are the terms of the decomposition of another tensor.
///
/// If we decompose each elements of a set of values $(\theta^{(a)})\_{a\in\mathbb{N}}$ as a set of
/// sums $(\sum\_{i=1}^l\tilde{\theta}^{(a)}\_i\frac{q}{B^i})\_{a\in\mathbb{N}}$, this represents a
/// set of $(\tilde{\theta}^{(a)}\_i)\_{a\in\mathbb{N}}$.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct DecompositionTermSlice<'a, Scalar>
where
Scalar: UnsignedInteger,
{
level: usize,
base_log: usize,
slice: &'a [Scalar],
}
impl<'a, Scalar> DecompositionTermSlice<'a, Scalar>
where
Scalar: UnsignedInteger,
{
// Creates a new tensor decomposition term.
pub(crate) fn new(
level: DecompositionLevel,
base_log: DecompositionBaseLog,
slice: &'a [Scalar],
) -> DecompositionTermSlice<Scalar> {
DecompositionTermSlice {
level: level.0,
base_log: base_log.0,
slice,
}
}
/// Fills the output tensor with the terms turned to summands.
///
/// If our term tensor represents a set of $(\tilde{\theta}^{(a)}\_i)\_{a\in\mathbb{N}}$ of the
/// decomposition, this method fills the output tensor with a set of
/// $(\tilde{\theta}^{(a)}\_i\frac{q}{B^i})\_{a\in\mathbb{N}}$.
///
/// # Example
///
/// ```rust
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
/// use tfhe::core_crypto::prelude::{DecompositionBaseLog, DecompositionLevelCount};
/// let decomposer =
/// SignedDecomposer::<u32>::new(DecompositionBaseLog(4), DecompositionLevelCount(3));
/// let input = vec![2u32.pow(19); 2];
/// let mut decomp = decomposer.decompose_slice(&input);
/// let term = decomp.next_term().unwrap();
/// let mut output = vec![0, 2];
/// term.fill_slice_with_recomposition_summand(&mut output);
/// assert!(output.iter().all(|&x| x == 1048576));
/// ```
pub fn fill_slice_with_recomposition_summand(&self, output: &mut [Scalar]) {
output
.iter_mut()
.zip(self.slice.iter())
.for_each(|(dst, &value)| {
let shift: usize = <Scalar as Numeric>::BITS - self.base_log * self.level;
*dst = value << shift
});
}
pub(crate) fn update_slice_with_recomposition_summand_wrapping_addition(
&self,
output: &mut [Scalar],
) {
output
.iter_mut()
.zip(self.slice.iter())
.for_each(|(out, &value)| {
let shift: usize = <Scalar as Numeric>::BITS - self.base_log * self.level;
*out = (*out).wrapping_add(value << shift);
});
}
/// Returns a tensor with the values of term.
///
/// # Example
///
/// ```rust
/// use tfhe::core_crypto::commons::math::decomposition::SignedDecomposer;
/// use tfhe::core_crypto::prelude::{DecompositionBaseLog, DecompositionLevelCount};
/// let decomposer =
/// SignedDecomposer::<u32>::new(DecompositionBaseLog(4), DecompositionLevelCount(3));
/// let input = vec![2u32.pow(19); 2];
/// let mut decomp = decomposer.decompose_slice(&input);
/// let term = decomp.next_term().unwrap();
/// assert_eq!(term.as_slice()[0], 1);
/// ```
pub fn as_slice(&self) -> &'a [Scalar] {
self.slice
}
/// Returns the level of this decomposition term tensor.
///
/// # Example
///
/// ```rust
/// use tfhe::core_crypto::commons::math::decomposition::{DecompositionLevel, SignedDecomposer};
/// use tfhe::core_crypto::prelude::{DecompositionBaseLog, DecompositionLevelCount};
/// let decomposer =
/// SignedDecomposer::<u32>::new(DecompositionBaseLog(4), DecompositionLevelCount(3));
/// let input = vec![2u32.pow(19); 2];
/// let mut decomp = decomposer.decompose_slice(&input);
/// let term = decomp.next_term().unwrap();
/// assert_eq!(term.level(), DecompositionLevel(3));
/// ```
pub fn level(&self) -> DecompositionLevel {
DecompositionLevel(self.level)
}
}

View File

@@ -175,8 +175,9 @@ impl<G: ByteRandomGenerator> RandomGenerator<G> {
/// use concrete_csprng::seeders::Seed;
/// use tfhe::core_crypto::commons::math::random::RandomGenerator;
/// let mut generator = RandomGenerator::<SoftwareRandomGenerator>::new(Seed(0));
/// let mut vec = vec![1u32; 100];
/// let mut vec = vec![0u32; 1000];
/// generator.fill_slice_with_random_uniform(&mut vec);
/// assert!(vec.iter().any(|&x| x != 0));
/// ```
pub fn fill_slice_with_random_uniform<Scalar>(&mut self, output: &mut [Scalar])
where
@@ -196,11 +197,12 @@ impl<G: ByteRandomGenerator> RandomGenerator<G> {
/// use tfhe::core_crypto::commons::math::random::RandomGenerator;
/// use tfhe::core_crypto::commons::parameters::CiphertextModulus;
/// let mut generator = RandomGenerator::<SoftwareRandomGenerator>::new(Seed(0));
/// let mut vec = vec![1u32; 100];
/// let mut vec = vec![0u32; 1000];
/// generator.fill_slice_with_random_uniform_custom_mod(
/// &mut vec,
/// CiphertextModulus::try_new_power_of_2(31).unwrap(),
/// );
/// assert!(vec.iter().any(|&x| x != 0));
/// ```
pub fn fill_slice_with_random_uniform_custom_mod<Scalar>(
&mut self,
@@ -212,10 +214,9 @@ impl<G: ByteRandomGenerator> RandomGenerator<G> {
self.fill_slice_with_random_uniform(output);
if !custom_modulus.is_native_modulus() {
output
.as_mut()
.iter_mut()
.for_each(|x| *x = (*x).wrapping_rem(custom_modulus.get().cast_into()));
output.as_mut().iter_mut().for_each(|x| {
*x = (*x).wrapping_rem(custom_modulus.get_custom_modulus().cast_into())
});
}
}
@@ -243,8 +244,9 @@ impl<G: ByteRandomGenerator> RandomGenerator<G> {
/// use concrete_csprng::seeders::Seed;
/// use tfhe::core_crypto::commons::math::random::RandomGenerator;
/// let mut generator = RandomGenerator::<SoftwareRandomGenerator>::new(Seed(0));
/// let mut vec = vec![2u32; 100];
/// let mut vec = vec![0u32; 1000];
/// generator.fill_slice_with_random_uniform_binary(&mut vec);
/// assert!(vec.iter().any(|&x| x != 0));
/// ```
pub fn fill_slice_with_random_uniform_binary<Scalar>(&mut self, output: &mut [Scalar])
where
@@ -371,8 +373,9 @@ impl<G: ByteRandomGenerator> RandomGenerator<G> {
/// use concrete_csprng::seeders::Seed;
/// use tfhe::core_crypto::commons::math::random::RandomGenerator;
/// let mut generator = RandomGenerator::<SoftwareRandomGenerator>::new(Seed(0));
/// let mut vec = vec![1000f32; 100];
/// let mut vec = vec![0f32; 1000];
/// generator.fill_slice_with_random_gaussian(&mut vec, 0., 1.);
/// assert!(vec.iter().any(|&x| x != 0.));
/// ```
pub fn fill_slice_with_random_gaussian<Float, Scalar>(
&mut self,
@@ -404,13 +407,14 @@ impl<G: ByteRandomGenerator> RandomGenerator<G> {
/// use tfhe::core_crypto::commons::math::random::RandomGenerator;
/// use tfhe::core_crypto::commons::parameters::CiphertextModulus;
/// let mut generator = RandomGenerator::<SoftwareRandomGenerator>::new(Seed(0));
/// let mut vec = vec![1000u64; 100];
/// let mut vec = vec![0u64; 1000];
/// generator.fill_slice_with_random_gaussian_custom_mod(
/// &mut vec,
/// 0.,
/// 1.,
/// CiphertextModulus::try_new_power_of_2(63).unwrap(),
/// );
/// assert!(vec.iter().any(|&x| x != 0));
/// ```
pub fn fill_slice_with_random_gaussian_custom_mod<Float, Scalar>(
&mut self,
@@ -423,7 +427,12 @@ impl<G: ByteRandomGenerator> RandomGenerator<G> {
Scalar: UnsignedInteger,
(Scalar, Scalar): RandomGenerable<Gaussian<Float>, CustomModulus = Float>,
{
let custom_modulus_float: Float = custom_modulus.get().cast_into();
if custom_modulus.is_native_modulus() {
self.fill_slice_with_random_gaussian(output, mean, std);
return;
}
let custom_modulus_float: Float = custom_modulus.get_custom_modulus().cast_into();
output.chunks_mut(2).for_each(|s| {
let (g1, g2) = <(Scalar, Scalar)>::generate_one_custom_modulus(
self,
@@ -448,8 +457,9 @@ impl<G: ByteRandomGenerator> RandomGenerator<G> {
/// use concrete_csprng::seeders::Seed;
/// use tfhe::core_crypto::commons::math::random::RandomGenerator;
/// let mut generator = RandomGenerator::<SoftwareRandomGenerator>::new(Seed(0));
/// let mut vec = vec![1000u32; 100];
/// let mut vec = vec![0u32; 1000];
/// generator.unsigned_torus_slice_wrapping_add_random_gaussian_assign(&mut vec, 0., 1.);
/// assert!(vec.iter().any(|&x| x != 0));
/// ```
pub fn unsigned_torus_slice_wrapping_add_random_gaussian_assign<Float, Scalar>(
&mut self,
@@ -479,9 +489,16 @@ impl<G: ByteRandomGenerator> RandomGenerator<G> {
/// use concrete_csprng::generators::SoftwareRandomGenerator;
/// use concrete_csprng::seeders::Seed;
/// use tfhe::core_crypto::commons::math::random::RandomGenerator;
/// use tfhe::core_crypto::commons::parameters::CiphertextModulus;
/// let mut generator = RandomGenerator::<SoftwareRandomGenerator>::new(Seed(0));
/// let mut vec = vec![1000u32; 100];
/// generator.unsigned_torus_slice_wrapping_add_random_gaussian_assign(&mut vec, 0., 1.);
/// let mut vec = vec![0u32; 1000];
/// generator.unsigned_torus_slice_wrapping_add_random_gaussian_custom_mod_assign(
/// &mut vec,
/// 0.,
/// 1.,
/// CiphertextModulus::try_new_power_of_2(31).unwrap(),
/// );
/// assert!(vec.iter().any(|&x| x != 0));
/// ```
pub fn unsigned_torus_slice_wrapping_add_random_gaussian_custom_mod_assign<Float, Scalar>(
&mut self,
@@ -494,7 +511,12 @@ impl<G: ByteRandomGenerator> RandomGenerator<G> {
Float: FloatingPoint + CastFrom<u128>,
(Scalar, Scalar): RandomGenerable<Gaussian<Float>, CustomModulus = Float>,
{
let custom_modulus_float: Float = custom_modulus.get().cast_into();
if custom_modulus.is_native_modulus() {
self.unsigned_torus_slice_wrapping_add_random_gaussian_assign(output, mean, std);
return;
}
let custom_modulus_float: Float = custom_modulus.get_custom_modulus().cast_into();
output.chunks_mut(2).for_each(|s| {
let (g1, g2) = <(Scalar, Scalar)>::generate_one_custom_modulus(
self,

View File

@@ -1,8 +1,8 @@
use crate::core_crypto::commons::dispersion::LogStandardDev;
use crate::core_crypto::commons::ciphertext_modulus::CiphertextModulus;
use crate::core_crypto::commons::math::torus::UnsignedTorus;
use crate::core_crypto::commons::test_tools::*;
fn test_normal_random<T: UnsignedTorus>() {
fn test_normal_random_three_sigma<T: UnsignedTorus>() {
//! test if the normal random generation with std_dev is below 3*std_dev (99.7%)
// settings
@@ -22,8 +22,10 @@ fn test_normal_random<T: UnsignedTorus>() {
.zip(samples_int.iter())
.for_each(|(out, &elt)| *out = elt.into_torus());
for x in samples_float.iter_mut() {
// The upper half of the torus corresponds to the negative domain when mapping unsigned
// integer back to float (MSB or sign bit is set)
if *x > 0.5 {
*x = 1. - *x;
*x -= 1.;
}
}
@@ -48,39 +50,306 @@ fn test_normal_random<T: UnsignedTorus>() {
}
#[test]
fn test_normal_random_u32() {
test_normal_random::<u32>();
fn test_normal_random_three_sigma_u32() {
test_normal_random_three_sigma::<u32>();
}
#[test]
fn test_normal_random_u64() {
test_normal_random::<u64>();
fn test_normal_random_three_sigma_u64() {
test_normal_random_three_sigma::<u64>();
}
fn test_distribution<T: UnsignedTorus>() {
//! tests gaussianity against the rand crate generation
// settings
let std_dev: f64 = f64::powi(2., -5);
let mean: f64 = 0.;
let k = 10_000_000;
let mut generator = new_random_generator();
#[test]
fn test_normal_random_f64() {
const RUNS: usize = 10000;
const SAMPLES_PER_RUN: usize = 1000;
let mut rng = new_random_generator();
let failures: f64 = (0..RUNS)
.map(|_| {
let mut samples = vec![0.0f64; SAMPLES_PER_RUN];
// generate normal random
let first = vec![T::ZERO; k];
let mut second = vec![T::ZERO; k];
generator.fill_slice_with_random_gaussian(&mut second, mean, std_dev);
rng.fill_slice_with_random_gaussian(&mut samples, 0.0, 1.0);
assert_noise_distribution(&first, &second, LogStandardDev(-5.));
if normality_test_f64(&samples, 0.05).null_hypothesis_is_valid {
// If we are normal return 0, it's not a failure
0.0
} else {
1.0
}
})
.sum::<f64>();
let failure_rate = failures / (RUNS as f64);
println!("failure_rate: {failure_rate}");
// The expected failure rate even on proper gaussian is 5%, so we take a small safety margin
assert!(failure_rate <= 0.065);
}
// // These tests are notoriously flaky
fn test_normal_random_native<Scalar: UnsignedTorus>() {
const RUNS: usize = 10000;
const SAMPLES_PER_RUN: usize = 1000;
let mut rng = new_random_generator();
let failures: f64 = (0..RUNS)
.map(|_| {
let mut samples = vec![Scalar::ZERO; SAMPLES_PER_RUN];
// #[test]
// fn test_distribution_u32() {
// test_distribution::<u32>();
// }
rng.fill_slice_with_random_gaussian(&mut samples, 0.0, f64::powi(2., -20));
// #[test]
// fn test_distribution_u64() {
// test_distribution::<u64>();
// }
let samples: Vec<f64> = samples
.iter()
.copied()
.map(|x| {
let torus = x.into_torus();
// The upper half of the torus corresponds to the negative domain when mapping
// unsigned integer back to float (MSB or sign bit is set)
if torus > 0.5 {
torus - 1.0
} else {
torus
}
})
.collect();
if normality_test_f64(&samples, 0.05).null_hypothesis_is_valid {
// If we are normal return 0, it's not a failure
0.0
} else {
1.0
}
})
.sum::<f64>();
let failure_rate = failures / (RUNS as f64);
println!("failure_rate: {failure_rate}");
// The expected failure rate even on proper gaussian is 5%, so we take a small safety margin
assert!(failure_rate <= 0.065);
}
#[test]
fn test_normal_random_native_u32() {
test_normal_random_native::<u32>();
}
#[test]
fn test_normal_random_native_u64() {
test_normal_random_native::<u64>();
}
#[test]
fn test_normal_random_native_u128() {
test_normal_random_native::<u128>();
}
fn test_normal_random_custom_mod<Scalar: UnsignedTorus>(
ciphertext_modulus: CiphertextModulus<Scalar>,
) {
const RUNS: usize = 10000;
const SAMPLES_PER_RUN: usize = 1000;
let mut rng = new_random_generator();
let failures: f64 = (0..RUNS)
.map(|_| {
let mut samples = vec![Scalar::ZERO; SAMPLES_PER_RUN];
rng.fill_slice_with_random_gaussian_custom_mod(
&mut samples,
0.0,
f64::powi(2., -20),
ciphertext_modulus,
);
let samples: Vec<f64> = samples
.iter()
.copied()
.map(|x| {
let torus = x.into_torus();
// The upper half of the torus corresponds to the negative domain when mapping
// unsigned integer back to float (MSB or sign bit is set)
if torus > 0.5 {
torus - 1.0
} else {
torus
}
})
.collect();
if normality_test_f64(&samples, 0.05).null_hypothesis_is_valid {
// If we are normal return 0, it's not a failure
0.0
} else {
1.0
}
})
.sum::<f64>();
let failure_rate = failures / (RUNS as f64);
println!("failure_rate: {failure_rate}");
// The expected failure rate even on proper gaussian is 5%, so we take a small safety margin
assert!(failure_rate <= 0.065);
}
#[test]
fn test_normal_random_custom_mod_u32() {
test_normal_random_custom_mod::<u32>(CiphertextModulus::try_new_power_of_2(31).unwrap());
}
#[test]
fn test_normal_random_custom_mod_u64() {
test_normal_random_custom_mod::<u64>(CiphertextModulus::try_new_power_of_2(63).unwrap());
}
#[test]
fn test_normal_random_custom_mod_u128() {
test_normal_random_custom_mod::<u128>(CiphertextModulus::try_new_power_of_2(127).unwrap());
}
#[test]
fn test_normal_random_native_custom_mod_u32() {
test_normal_random_custom_mod::<u32>(CiphertextModulus::new_native());
}
#[test]
fn test_normal_random_native_custom_mod_u64() {
test_normal_random_custom_mod::<u64>(CiphertextModulus::new_native());
}
#[test]
fn test_normal_random_native_custom_mod_u128() {
test_normal_random_custom_mod::<u128>(CiphertextModulus::new_native());
}
fn test_normal_random_add_assign_native<Scalar: UnsignedTorus>() {
const RUNS: usize = 10000;
const SAMPLES_PER_RUN: usize = 1000;
let mut rng = new_random_generator();
let failures: f64 = (0..RUNS)
.map(|_| {
let mut samples = vec![Scalar::ZERO; SAMPLES_PER_RUN];
rng.unsigned_torus_slice_wrapping_add_random_gaussian_assign(
&mut samples,
0.0,
f64::powi(2., -20),
);
let samples: Vec<f64> = samples
.iter()
.copied()
.map(|x| {
let torus = x.into_torus();
// The upper half of the torus corresponds to the negative domain when mapping
// unsigned integer back to float (MSB or sign bit is set)
if torus > 0.5 {
torus - 1.0
} else {
torus
}
})
.collect();
if normality_test_f64(&samples, 0.05).null_hypothesis_is_valid {
// If we are normal return 0, it's not a failure
0.0
} else {
1.0
}
})
.sum::<f64>();
let failure_rate = failures / (RUNS as f64);
println!("failure_rate: {failure_rate}");
// The expected failure rate even on proper gaussian is 5%, so we take a small safety margin
assert!(failure_rate <= 0.065);
}
#[test]
fn test_normal_random_add_assign_native_u32() {
test_normal_random_add_assign_native::<u32>();
}
#[test]
fn test_normal_random_add_assign_native_u64() {
test_normal_random_add_assign_native::<u64>();
}
#[test]
fn test_normal_random_add_assign_native_u128() {
test_normal_random_add_assign_native::<u128>();
}
fn test_normal_random_add_assign_custom_mod<Scalar: UnsignedTorus>(
ciphertext_modulus: CiphertextModulus<Scalar>,
) {
const RUNS: usize = 10000;
const SAMPLES_PER_RUN: usize = 1000;
let mut rng = new_random_generator();
let failures: f64 = (0..RUNS)
.map(|_| {
let mut samples = vec![Scalar::ZERO; SAMPLES_PER_RUN];
rng.unsigned_torus_slice_wrapping_add_random_gaussian_custom_mod_assign(
&mut samples,
0.0,
f64::powi(2., -20),
ciphertext_modulus,
);
let samples: Vec<f64> = samples
.iter()
.copied()
.map(|x| {
let torus = x.into_torus();
// The upper half of the torus corresponds to the negative domain when mapping
// unsigned integer back to float (MSB or sign bit is set)
if torus > 0.5 {
torus - 1.0
} else {
torus
}
})
.collect();
if normality_test_f64(&samples, 0.05).null_hypothesis_is_valid {
// If we are normal return 0, it's not a failure
0.0
} else {
1.0
}
})
.sum::<f64>();
let failure_rate = failures / (RUNS as f64);
println!("failure_rate: {failure_rate}");
// The expected failure rate even on proper gaussian is 5%, so we take a small safety margin
assert!(failure_rate <= 0.065);
}
#[test]
fn test_normal_random_add_assign_custom_mod_u32() {
test_normal_random_add_assign_custom_mod::<u32>(
CiphertextModulus::try_new_power_of_2(31).unwrap(),
);
}
#[test]
fn test_normal_random_add_assign_custom_mod_u64() {
test_normal_random_add_assign_custom_mod::<u64>(
CiphertextModulus::try_new_power_of_2(63).unwrap(),
);
}
#[test]
fn test_normal_random_add_assign_custom_mod_u128() {
test_normal_random_add_assign_custom_mod::<u128>(
CiphertextModulus::try_new_power_of_2(127).unwrap(),
);
}
#[test]
fn test_normal_random_add_assign_native_custom_mod_u32() {
test_normal_random_add_assign_custom_mod::<u32>(CiphertextModulus::new_native());
}
#[test]
fn test_normal_random_add_assign_native_custom_mod_u64() {
test_normal_random_add_assign_custom_mod::<u64>(CiphertextModulus::new_native());
}
#[test]
fn test_normal_random_add_assign_native_custom_mod_u128() {
test_normal_random_add_assign_custom_mod::<u128>(CiphertextModulus::new_native());
}

View File

@@ -67,13 +67,14 @@ pub mod test_tools {
use crate::core_crypto::commons::generators::{
EncryptionRandomGenerator, SecretRandomGenerator,
};
use crate::core_crypto::commons::math::random::{RandomGenerable, RandomGenerator, Uniform};
use crate::core_crypto::commons::math::random::{
ActivatedRandomGenerator, RandomGenerable, RandomGenerator, Uniform,
};
use crate::core_crypto::commons::parameters::{
CiphertextCount, DecompositionBaseLog, DecompositionLevelCount, GlweDimension,
LweDimension, PlaintextCount, PolynomialSize,
};
use crate::core_crypto::commons::traits::*;
use concrete_csprng::generators::SoftwareRandomGenerator;
use concrete_csprng::seeders::{Seed, Seeder};
fn modular_distance<T: UnsignedInteger>(first: T, other: T) -> T {
@@ -94,15 +95,16 @@ pub mod test_tools {
}
}
pub fn new_random_generator() -> RandomGenerator<SoftwareRandomGenerator> {
pub fn new_random_generator() -> RandomGenerator<ActivatedRandomGenerator> {
RandomGenerator::new(random_seed())
}
pub fn new_secret_random_generator() -> SecretRandomGenerator<SoftwareRandomGenerator> {
pub fn new_secret_random_generator() -> SecretRandomGenerator<ActivatedRandomGenerator> {
SecretRandomGenerator::new(random_seed())
}
pub fn new_encryption_random_generator() -> EncryptionRandomGenerator<SoftwareRandomGenerator> {
pub fn new_encryption_random_generator() -> EncryptionRandomGenerator<ActivatedRandomGenerator>
{
EncryptionRandomGenerator::new(random_seed(), &mut UnsafeRandSeeder)
}
@@ -143,49 +145,85 @@ pub mod test_tools {
}
}
pub fn assert_noise_distribution<First, Second, Element>(
first: &First,
second: &Second,
dist: impl DispersionParameter,
) where
First: Container<Element = Element>,
Second: Container<Element = Element>,
Element: UnsignedTorus,
{
use rand_distr::Distribution;
pub struct NormalityTestResult {
pub w_prime: f64,
pub p_value: f64,
pub null_hypothesis_is_valid: bool,
}
let std_dev = dist.get_standard_dev();
let confidence = 0.95;
let n_slots = first.container_len();
/// Based on Shapiro-Francia normality test
pub fn normality_test_f64(samples: &[f64], alpha: f64) -> NormalityTestResult {
assert!(
samples.len() <= 5000,
"normality_test_f64 produces a relevant pvalue for less than 5000 samples"
);
// allocate 2 slices: one for the error samples obtained, the second for fresh samples
// according to the std_dev computed
let mut sdk_samples = vec![0.0_f64; n_slots];
// From "A handy approximation for the error function and its inverse" by Sergei Winitzki
fn erf_inv(x: f64) -> f64 {
let sign = if x < 0.0 { -1.0 } else { 1.0 };
// 1 - x**2
let one_minus_x_2 = (1.0 - x) * (1.0 + x);
// ln(1 - x**2)
let log_term = f64::ln(one_minus_x_2);
let a = 0.147;
let term_1 = 2.0 / (std::f64::consts::PI * a) + 0.5 * log_term;
let term_2 = 1.0 / a * log_term;
// recover the errors from each ciphertexts
sdk_samples
.iter_mut()
.zip(first.as_ref().iter().zip(second.as_ref().iter()))
.for_each(|(out, (&lhs, &rhs))| *out = torus_modular_distance(lhs, rhs));
// fill the theoretical sample vector according to std_dev using the rand crate
let mut theoretical_samples: Vec<f64> = Vec::with_capacity(n_slots);
let normal = rand_distr::Normal::new(0.0, std_dev).unwrap();
for _i in 0..n_slots {
theoretical_samples.push(normal.sample(&mut rand::thread_rng()));
sign * f64::sqrt(-term_1 + f64::sqrt(term_1 * term_1 - term_2))
}
// compute the kolmogorov smirnov test
let result = kolmogorov_smirnov::test_f64(
sdk_samples.as_slice(),
theoretical_samples.as_slice(),
confidence,
);
assert!(
!result.is_rejected,
"Not the same distribution with a probability of {}",
result.reject_probability
);
// Normal law CDF
fn phi(x: f64) -> f64 {
0.5 * (1.0 + libm::erf(x / f64::sqrt(2.0)))
}
fn phi_inv(x: f64) -> f64 {
f64::sqrt(2.0) * erf_inv(2.0 * x - 1.0)
}
let n = samples.len();
let n_f64 = n as f64;
// Sort the input
let mut samples: Vec<_> = samples.to_vec();
samples.sort_by(|x, y| x.partial_cmp(y).unwrap());
let samples = samples;
// Compute the mean
let mean = samples.iter().copied().sum::<f64>() / n_f64;
let frac_three_eight = 3. / 8.;
let frac_one_four = 1. / 4.;
// Compute Blom scores
let m_tilde: Vec<_> = (1..=n)
.map(|i| phi_inv((i as f64 - frac_three_eight) / (n_f64 + frac_one_four)))
.collect();
// Blom scores norm2
let m_norm = f64::sqrt(m_tilde.iter().fold(0.0, |acc, x| acc + x * x));
// Coefficients
let mut coeffs = m_tilde;
coeffs.iter_mut().for_each(|x| *x /= m_norm);
// Test statistic
let denominator = samples.iter().fold(0.0, |acc, x| acc + (x - mean).powi(2));
let numerator = samples
.iter()
.zip(coeffs.iter())
.fold(0.0, |acc, (&sample, &coeff)| acc + sample * coeff)
.powi(2);
let w_prime = numerator / denominator;
let g_w_prime = f64::ln(1.0 - w_prime);
let log_n = n_f64.ln();
let log_log_n = log_n.ln();
let u = log_log_n - log_n;
let mu = 1.0521 * u - 1.2725;
let v = log_log_n + 2.0 / log_n;
let sigma = -0.26758 * v + 1.0308;
let z = (g_w_prime - mu) / sigma;
let p_value = 1.0 - phi(z);
NormalityTestResult {
w_prime,
p_value,
null_hypothesis_is_valid: p_value > alpha,
}
}
/// Return a random plaintext count in [1;max].
@@ -260,4 +298,52 @@ pub mod test_tools {
let mut generator = new_random_generator();
generator.random_uniform()
}
#[test]
pub fn test_normality_tool() {
use rand_distr::{Distribution, Normal};
const RUNS: usize = 10000;
const SAMPLES_PER_RUN: usize = 1000;
let mut rng = rand::thread_rng();
let normal = Normal::new(0.0, 1.0).unwrap();
let failures: f64 = (0..RUNS)
.map(|_| {
let mut samples = vec![0.0f64; SAMPLES_PER_RUN];
samples
.iter_mut()
.for_each(|x| *x = normal.sample(&mut rng));
if normality_test_f64(&samples, 0.05).null_hypothesis_is_valid {
// If we are normal return 0, it's not a failure
0.0
} else {
1.0
}
})
.sum::<f64>();
let failure_rate = failures / (RUNS as f64);
println!("failure_rate: {failure_rate}");
// The expected failure rate even on proper gaussian is 5%, so we take a small safety margin
assert!(failure_rate <= 0.065);
}
#[test]
pub fn test_normality_tool_fail_uniform() {
const RUNS: usize = 10000;
const SAMPLES_PER_RUN: usize = 1000;
let mut rng = rand::thread_rng();
let failures: f64 = (0..RUNS)
.map(|_| {
let mut samples = vec![0.0f64; SAMPLES_PER_RUN];
samples.iter_mut().for_each(|x| *x = rng.gen());
if normality_test_f64(&samples, 0.05).null_hypothesis_is_valid {
// If we are normal return 0, it's not a failure
0.0
} else {
1.0
}
})
.sum::<f64>();
let failure_rate = failures / (RUNS as f64);
assert!(failure_rate == 1.0);
}
}

View File

@@ -0,0 +1,428 @@
//! Module containing the definition of the GlweKeyswitchKey.
use crate::core_crypto::commons::parameters::*;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::entities::*;
/// A [`GLWE keyswitch key`](`GlweKeyswitchKey`).
///
/// # Formal Definition
///
/// ## Key Switching Key
///
/// A key switching key is a vector of GLev ciphertexts (described on the bottom of
/// [`this page`](`crate::core_crypto::entities::GgswCiphertext#Glev-ciphertext`)).
/// It encrypts the coefficient of
/// the [`GLWE secret key`](`crate::core_crypto::entities::GlweSecretKey`)
/// $\vec{S}\_{\mathsf{in}}$ under the
/// [`GLWE secret key`](`crate::core_crypto::entities::GlweSecretKey`)
/// $\vec{S}\_{\mathsf{out}}$.
///
/// $$\mathsf{KSK}\_{\vec{S}\_{\mathsf{in}}\rightarrow \vec{S}\_{\mathsf{out}}} = \left(
/// \overline{\mathsf{CT}\_0}, \cdots , \overline{\mathsf{CT}\_{k\_{\mathsf{in}}-1}}\right)
/// \subseteq R\_q^{(k\_{\mathsf{out}}+1)\cdot k\_{\mathsf{in}}\cdot \ell}$$
///
/// where $\vec{S}\_{\mathsf{in}} = \left( S\_0 , \cdots , S\_{\mathsf{in}-1} \right)$ and for all
/// $0\le i <k\_{\mathsf{in}}$ we have $\overline{\mathsf{CT}\_i} \in
/// \mathsf{GLev}\_{\vec{S}\_{\mathsf{out}}}^{\beta, \ell}\left(S\_i\right)$.
///
/// ## GLWE Keyswitch
///
/// This homomorphic procedure transforms an input
/// [`GLWE ciphertext`](`crate::core_crypto::entities::GlweCiphertext`)
/// $\mathsf{CT}\_{\mathsf{in}} =
/// \left( \vec{A}\_{\mathsf{in}} , B\_{\mathsf{in}}\right) \in \mathsf{GLWE}^{k\_{\mathsf{in}}}\_
/// {\vec{S}\_{\mathsf{in}}}( \mathsf{PT} ) \subseteq R\_q^{(k\_{\mathsf{in}}+1)}$ into an
/// output [`GLWE ciphertext`](`crate::core_crypto::entities::GlweCiphertext`)
/// $\mathsf{CT}\_{\mathsf{out}} =
/// \left( \vec{A}\_{\mathsf{out}} , B\_{\mathsf{out}}\right) \in
/// \mathsf{GLWE}^{k\_{\mathsf{out}}}\_{\vec{S}\_{\mathsf{out}}}( \mathsf{PT} )\subseteq
/// R\_q^{(k\_{\mathsf{out}}+1)}$ where $k\_{\mathsf{in}} = |\vec{S}\_{\mathsf{in}}|$ and
/// $k\_{\mathsf{out}} = |\vec{S}\_{\mathsf{out}}|$. It requires a
/// [`key switching key`](`crate::core_crypto::entities::GlweKeyswitchKey`).
/// The input ciphertext is encrypted under the
/// [`GLWE secret key`](`crate::core_crypto::entities::GlweSecretKey`)
/// $\vec{S}\_{\mathsf{in}}$ and the output ciphertext is
/// encrypted under the [`GLWE secret key`](`crate::core_crypto::entities::GlweSecretKey`)
/// $\vec{S}\_{\mathsf{out}}$.
///
/// $$\mathsf{CT}\_{\mathsf{in}} \in \mathsf{GLWE}^{k\_{\mathsf{in}}}\_{\vec{S}\_{\mathsf{in}}}(
/// \mathsf{PT} ) ~~~~~~~~~~\mathsf{KSK}\_{\vec{S}\_{\mathsf{in}}\rightarrow
/// \vec{S}\_{\mathsf{out}}}$$ $$ \mathsf{keyswitch}\left(\mathsf{CT}\_{\mathsf{in}} , \mathsf{KSK}
/// \right) \rightarrow \mathsf{CT}\_{\mathsf{out}} \in
/// \mathsf{GLWE}^{k\_{\mathsf{out}}}\_{\vec{S}\_{\mathsf{out}}} \left( \mathsf{PT} \right)$$
///
/// ## Algorithm
/// ###### inputs:
/// - $\mathsf{CT}\_{\mathsf{in}} = \left( \vec{A}\_{\mathsf{in}} , B\_{\mathsf{in}}\right) \in
/// \mathsf{GLWE}^{k\_{\mathsf{in}}}\_{\vec{S}\_{\mathsf{in}}}( \mathsf{PT} )$: a [`GLWE
/// ciphertext`](`GlweCiphertext`) with $\vec{A}\_{\mathsf{in}}=\left(A\_0, \cdots
/// A\_{k\_{\mathsf{in}}-1}\right)$
/// - $\mathsf{KSK}\_{\vec{S}\_{\mathsf{in}}\rightarrow \vec{S}\_{\mathsf{out}}}$: a
/// [`key switching key`](`crate::core_crypto::entities::GlweKeyswitchKey`)
///
/// ###### outputs:
/// - $\mathsf{CT}\_{\mathsf{out}} \in \mathsf{GLWE}^{k\_{\mathsf{out}}}\_{\vec{S}\_{\mathsf{out}}}
/// \left( \mathsf{PT} \right)$: a
/// [`GLWE ciphertext`](`crate::core_crypto::entities::GlweCiphertext`)
///
/// ###### algorithm:
/// 1. set $\mathsf{cCT}=\left( 0 , \cdots , 0 , B\_{\mathsf{in}} \right) \in
/// R\_q^{(k\_{\mathsf{out}}+1)}$
/// 2. compute $\mathsf{CT}\_{\mathsf{out}} = \mathsf{CT} -
/// \sum\_{i=0}^{k\_{\mathsf{in}}-1} \mathsf{decompProduct}\left( A\_i , \overline{\mathsf{CT}\_i}
/// \right)$
/// 3. output $\mathsf{CT}\_{\mathsf{out}}$
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct GlweKeyswitchKey<C: Container>
where
C::Element: UnsignedInteger,
{
data: C,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
output_glwe_size: GlweSize,
poly_size: PolynomialSize,
ciphertext_modulus: CiphertextModulus<C::Element>,
}
impl<T: UnsignedInteger, C: Container<Element = T>> AsRef<[T]> for GlweKeyswitchKey<C> {
fn as_ref(&self) -> &[T] {
self.data.as_ref()
}
}
impl<T: UnsignedInteger, C: ContainerMut<Element = T>> AsMut<[T]> for GlweKeyswitchKey<C> {
fn as_mut(&mut self) -> &mut [T] {
self.data.as_mut()
}
}
/// Return the number of elements in an encryption of an input [`GlweSecretKey`] element for a
/// [GlweKeyswitchKey`] given a [`DecompositionLevelCount`] and output [`GlweSize`].
pub fn glwe_keyswitch_key_input_key_element_encrypted_size(
decomp_level_count: DecompositionLevelCount,
output_glwe_size: GlweSize,
poly_size: PolynomialSize,
) -> usize {
// One ciphertext per level encrypted under the output key
decomp_level_count.0 * output_glwe_size.0 * poly_size.0
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> GlweKeyswitchKey<C> {
/// Create an [`GlweKeyswitchKey`] from an existing container.
///
/// # Note
///
/// This function only wraps a container in the appropriate type. If you want to generate an
/// [`GlweKeyswitchKey`] you need to call
/// [`crate::core_crypto::algorithms::generate_glwe_keyswitch_key`] using this key as output.
///
/// This docstring exhibits [`GlweKeyswitchKey`] primitives usage.
///
/// ```
/// use tfhe::core_crypto::prelude::*;
///
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
/// // computations
/// // Define parameters for LweKeyswitchKey creation
/// let input_glwe_dimension = GlweDimension(1);
/// let output_glwe_dimension = GlweDimension(2);
/// let poly_size = PolynomialSize(1024);
/// let decomp_base_log = DecompositionBaseLog(4);
/// let decomp_level_count = DecompositionLevelCount(5);
/// let ciphertext_modulus = CiphertextModulus::new_native();
///
/// // Create a new LweKeyswitchKey
/// let glwe_ksk = GlweKeyswitchKey::new(
/// 0u64,
/// decomp_base_log,
/// decomp_level_count,
/// input_glwe_dimension,
/// output_glwe_dimension,
/// poly_size,
/// ciphertext_modulus,
/// );
///
/// assert_eq!(glwe_ksk.decomposition_base_log(), decomp_base_log);
/// assert_eq!(glwe_ksk.decomposition_level_count(), decomp_level_count);
/// assert_eq!(glwe_ksk.input_key_glwe_dimension(), input_glwe_dimension);
/// assert_eq!(glwe_ksk.output_key_glwe_dimension(), output_glwe_dimension);
/// assert_eq!(glwe_ksk.polynomial_size(), poly_size);
/// assert_eq!(
/// glwe_ksk.output_glwe_size(),
/// output_glwe_dimension.to_glwe_size()
/// );
/// assert_eq!(glwe_ksk.ciphertext_modulus(), ciphertext_modulus);
///
/// // Demonstrate how to recover the allocated container
/// let underlying_container: Vec<u64> = glwe_ksk.into_container();
///
/// // Recreate a keyswithc key using from_container
/// let glwe_ksk = GlweKeyswitchKey::from_container(
/// underlying_container,
/// decomp_base_log,
/// decomp_level_count,
/// output_glwe_dimension.to_glwe_size(),
/// poly_size,
/// ciphertext_modulus,
/// );
///
/// assert_eq!(glwe_ksk.decomposition_base_log(), decomp_base_log);
/// assert_eq!(glwe_ksk.decomposition_level_count(), decomp_level_count);
/// assert_eq!(glwe_ksk.input_key_glwe_dimension(), input_glwe_dimension);
/// assert_eq!(glwe_ksk.output_key_glwe_dimension(), output_glwe_dimension);
/// assert_eq!(
/// glwe_ksk.output_glwe_size(),
/// output_glwe_dimension.to_glwe_size()
/// );
/// assert_eq!(glwe_ksk.ciphertext_modulus(), ciphertext_modulus);
/// ```
pub fn from_container(
container: C,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
output_glwe_size: GlweSize,
poly_size: PolynomialSize,
ciphertext_modulus: CiphertextModulus<C::Element>,
) -> Self {
assert!(
container.container_len() > 0,
"Got an empty container to create an LweKeyswitchKey"
);
assert!(
container.container_len() % (decomp_level_count.0 * output_glwe_size.0 * poly_size.0)
== 0,
"The provided container length is not valid. \
It needs to be dividable by decomp_level_count * output_glwe_size * output_poly_size: {}. \
Got container length: {} and decomp_level_count: {decomp_level_count:?}, \
output_glwe_size: {output_glwe_size:?}, poly_size: {poly_size:?}.",
decomp_level_count.0 * output_glwe_size.0 * poly_size.0,
container.container_len()
);
GlweKeyswitchKey {
data: container,
decomp_base_log,
decomp_level_count,
output_glwe_size,
poly_size,
ciphertext_modulus,
}
}
/// Return the [`DecompositionBaseLog`] of the [`LweKeyswitchKey`].
///
/// See [`LweKeyswitchKey::from_container`] for usage.
pub fn decomposition_base_log(&self) -> DecompositionBaseLog {
self.decomp_base_log
}
/// Return the [`DecompositionLevelCount`] of the [`LweKeyswitchKey`].
///
/// See [`LweKeyswitchKey::from_container`] for usage.
pub fn decomposition_level_count(&self) -> DecompositionLevelCount {
self.decomp_level_count
}
/// Return the input [`GlweDimension`] of the [`GlweKeyswitchKey`].
///
/// See [`GlweKeyswitchKey::from_container`] for usage.
pub fn input_key_glwe_dimension(&self) -> GlweDimension {
GlweDimension(self.data.container_len() / self.input_key_element_encrypted_size())
}
/// Return the input [`PolynomialSize`] of the [`GlweKeyswitchKey`].
///
/// See [`GlweKeyswitchKey::from_container`] for usage.
pub fn polynomial_size(&self) -> PolynomialSize {
self.poly_size
}
/// Return the output [`GlweDimension`] of the [`GlweKeyswitchKey`].
///
/// See [`GlweKeyswitchKey::from_container`] for usage.
pub fn output_key_glwe_dimension(&self) -> GlweDimension {
self.output_glwe_size.to_glwe_dimension()
}
/// Return the output [`GlweSize`] of the [`GlweKeyswitchKey`].
///
/// See [`GlweKeyswitchKey::from_container`] for usage.
pub fn output_glwe_size(&self) -> GlweSize {
self.output_glwe_size
}
/// Return the number of elements in an encryption of an input [`GlweSecretKey`] element of the
/// current [`GlweKeyswitchKey`].
pub fn input_key_element_encrypted_size(&self) -> usize {
glwe_keyswitch_key_input_key_element_encrypted_size(
self.decomp_level_count,
self.output_glwe_size,
self.poly_size,
)
}
/// Return a view of the [`GlweKeyswitchKey`]. This is useful if an algorithm takes a view by
/// value.
pub fn as_view(&self) -> GlweKeyswitchKey<&'_ [Scalar]> {
GlweKeyswitchKey::from_container(
self.as_ref(),
self.decomp_base_log,
self.decomp_level_count,
self.output_glwe_size,
self.poly_size,
self.ciphertext_modulus,
)
}
/// Consume the entity and return its underlying container.
///
/// See [`GlweKeyswitchKey::from_container`] for usage.
pub fn into_container(self) -> C {
self.data
}
/// Return the [`CiphertextModulus`] of the [`GlweKeyswitchKey`].
///
/// See [`GlweKeyswitchKey::from_container`] for usage.
pub fn ciphertext_modulus(&self) -> CiphertextModulus<C::Element> {
self.ciphertext_modulus
}
pub fn as_glwe_ciphertext_list(&self) -> GlweCiphertextListView<'_, Scalar> {
GlweCiphertextListView::from_container(
self.as_ref(),
self.output_glwe_size(),
self.polynomial_size(),
self.ciphertext_modulus(),
)
}
}
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> GlweKeyswitchKey<C> {
/// Mutable variant of [`GlweKeyswitchKey::as_view`].
pub fn as_mut_view(&mut self) -> GlweKeyswitchKey<&'_ mut [Scalar]> {
let decomp_base_log = self.decomp_base_log;
let decomp_level_count = self.decomp_level_count;
let output_glwe_size = self.output_glwe_size;
let poly_size = self.poly_size;
let ciphertext_modulus = self.ciphertext_modulus;
GlweKeyswitchKey::from_container(
self.as_mut(),
decomp_base_log,
decomp_level_count,
output_glwe_size,
poly_size,
ciphertext_modulus,
)
}
pub fn as_mut_glwe_ciphertext_list(&mut self) -> GlweCiphertextListMutView<'_, Scalar> {
let output_glwe_size = self.output_glwe_size();
let poly_size = self.polynomial_size();
let ciphertext_modulus = self.ciphertext_modulus();
GlweCiphertextListMutView::from_container(
self.as_mut(),
output_glwe_size,
poly_size,
ciphertext_modulus,
)
}
}
/// A [`GlweKeyswitchKey`] owning the memory for its own storage.
pub type GlweKeyswitchKeyOwned<Scalar> = GlweKeyswitchKey<Vec<Scalar>>;
impl<Scalar: UnsignedInteger> GlweKeyswitchKeyOwned<Scalar> {
/// Allocate memory and create a new owned [`GlweKeyswitchKey`].
///
/// # Note
///
/// This function allocates a vector of the appropriate size and wraps it in the appropriate
/// type. If you want to generate an [`GlweKeyswitchKey`] you need to call
/// [`crate::core_crypto::algorithms::generate_glwe_keyswitch_key`] using this key as output.
///
/// See [`GlweKeyswitchKey::from_container`] for usage.
pub fn new(
fill_with: Scalar,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
input_key_glwe_dimension: GlweDimension,
output_key_glwe_dimension: GlweDimension,
poly_size: PolynomialSize,
ciphertext_modulus: CiphertextModulus<Scalar>,
) -> GlweKeyswitchKeyOwned<Scalar> {
GlweKeyswitchKeyOwned::from_container(
vec![
fill_with;
input_key_glwe_dimension.0
* glwe_keyswitch_key_input_key_element_encrypted_size(
decomp_level_count,
output_key_glwe_dimension.to_glwe_size(),
poly_size,
)
],
decomp_base_log,
decomp_level_count,
output_key_glwe_dimension.to_glwe_size(),
poly_size,
ciphertext_modulus,
)
}
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> ContiguousEntityContainer
for GlweKeyswitchKey<C>
{
type Element = C::Element;
type EntityViewMetadata = GlweCiphertextListCreationMetadata<Scalar>;
type EntityView<'this> = GlweCiphertextListView<'this, Self::Element>
where
Self: 'this;
type SelfViewMetadata = ();
// At the moment it does not make sense to return "sub" keyswitch keys. So we use a dummy
// placeholder type here.
type SelfView<'this> = DummyCreateFrom
where
Self: 'this;
fn get_entity_view_creation_metadata(&self) -> GlweCiphertextListCreationMetadata<Scalar> {
GlweCiphertextListCreationMetadata(
self.output_glwe_size(),
self.polynomial_size(),
self.ciphertext_modulus(),
)
}
fn get_entity_view_pod_size(&self) -> usize {
self.input_key_element_encrypted_size()
}
/// Unimplemented for [`GlweKeyswitchKey`]. At the moment it does not make sense to
/// return "sub" keyswitch keys.
fn get_self_view_creation_metadata(&self) {
unimplemented!(
"This function is not supported for GlweKeyswitchKey. \
At the moment it does not make sense to return 'sub' keyswitch keys."
)
}
}
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> ContiguousEntityContainerMut
for GlweKeyswitchKey<C>
{
type EntityMutView<'this> = GlweCiphertextListMutView<'this, Self::Element>
where
Self: 'this;
// At the moment it does not make sense to return "sub" keyswitch keys. So we use a dummy
// placeholder type here.
type SelfMutView<'this> = DummyCreateFrom
where
Self: 'this;
}

View File

@@ -0,0 +1,382 @@
//! Module containing the definition of the GlweRelinearisationKey.
use crate::core_crypto::commons::parameters::*;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::entities::*;
/// A [`GLWE relinearisation key`](`GlweRelinearisationKey`).
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct GlweRelinearisationKey<C: Container>
where
C::Element: UnsignedInteger,
{
data: C,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
glwe_size: GlweSize,
polynomial_size: PolynomialSize,
ciphertext_modulus: CiphertextModulus<C::Element>,
}
impl<T: UnsignedInteger, C: Container<Element = T>> AsRef<[T]> for GlweRelinearisationKey<C> {
fn as_ref(&self) -> &[T] {
self.data.as_ref()
}
}
impl<T: UnsignedInteger, C: ContainerMut<Element = T>> AsMut<[T]> for GlweRelinearisationKey<C> {
fn as_mut(&mut self) -> &mut [T] {
self.data.as_mut()
}
}
/// Return the number of elements in an encryption of an input [`GlweSecretKey`] element for a
/// [`GlweRelinearisationKey`] given a [`DecompositionLevelCount`], [`GlweSize`] and
/// [`PolynomialSize`].
pub fn glwe_relinearisation_key_input_key_element_encrypted_size(
decomp_level_count: DecompositionLevelCount,
glwe_size: GlweSize,
polynomial_size: PolynomialSize,
) -> usize {
// One ciphertext per level encrypted under the output key
decomp_level_count.0 * glwe_size.0 * polynomial_size.0
}
/// Return the number of elements in a [`GlweRelinearisationKey`] given a
/// [`DecompositionLevelCount`], [`GlweSize`], and [`PolynomialSize`].
pub fn glwe_relinearisation_key_size(
decomp_level_count: DecompositionLevelCount,
glwe_size: GlweSize,
polynomial_size: PolynomialSize,
) -> usize {
(glwe_size.to_glwe_dimension().0 * (glwe_size.to_glwe_dimension().0 + 1)) / 2
* glwe_relinearisation_key_input_key_element_encrypted_size(
decomp_level_count,
glwe_size,
polynomial_size,
)
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> GlweRelinearisationKey<C> {
/// Create a [`GlweRelinearisationKey`] from an existing container.
///
/// # Note
///
/// This function only wraps a container in the appropriate type. If you want to generate an
/// [`GlweRelinearisationKey`] you need to use
/// [`crate::core_crypto::algorithms::generate_glwe_relinearisation_key`]
/// using this key as output.
///
/// This docstring exhibits [`GlweRelinearisationKey`] primitives usage.
///
/// ```
/// use tfhe::core_crypto::prelude::*;
///
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
/// // computations
/// // Define parameters for GlweRelinearisationKey creation
/// let glwe_size = GlweSize(3);
/// let polynomial_size = PolynomialSize(1024);
/// let decomp_base_log = DecompositionBaseLog(8);
/// let decomp_level_count = DecompositionLevelCount(3);
/// let ciphertext_modulus = CiphertextModulus::new_native();
///
/// // Create a new GlweRelinearisationKey
/// let relin_key = GlweRelinearisationKey::new(
/// 0u64,
/// decomp_base_log,
/// decomp_level_count,
/// glwe_size,
/// polynomial_size,
/// ciphertext_modulus,
/// );
///
/// assert_eq!(relin_key.glwe_dimension(), glwe_size.to_glwe_dimension());
/// assert_eq!(relin_key.glwe_size(), glwe_size);
/// assert_eq!(relin_key.polynomial_size(), polynomial_size);
/// assert_eq!(relin_key.decomposition_base_log(), decomp_base_log);
/// assert_eq!(relin_key.decomposition_level_count(), decomp_level_count);
/// assert_eq!(relin_key.ciphertext_modulus(), ciphertext_modulus);
///
/// // Demonstrate how to recover the allocated container
/// let underlying_container: Vec<u64> = relin_key.into_container();
///
/// // Recreate a key using from_container
/// let relin_key = GlweRelinearisationKey::from_container(
/// underlying_container,
/// decomp_base_log,
/// decomp_level_count,
/// glwe_size,
/// polynomial_size,
/// ciphertext_modulus,
/// );
///
/// assert_eq!(relin_key.glwe_dimension(), glwe_size.to_glwe_dimension());
/// assert_eq!(relin_key.glwe_size(), glwe_size);
/// assert_eq!(relin_key.polynomial_size(), polynomial_size);
/// assert_eq!(relin_key.decomposition_base_log(), decomp_base_log);
/// assert_eq!(relin_key.decomposition_level_count(), decomp_level_count);
/// assert_eq!(relin_key.ciphertext_modulus(), ciphertext_modulus);
/// ```
pub fn from_container(
container: C,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
glwe_size: GlweSize,
polynomial_size: PolynomialSize,
ciphertext_modulus: CiphertextModulus<C::Element>,
) -> GlweRelinearisationKey<C> {
assert!(
container.container_len() > 0,
"Got an empty container to create an LweKeyswitchKey"
);
assert!(
container.container_len()
% glwe_relinearisation_key_input_key_element_encrypted_size(
decomp_level_count,
glwe_size,
polynomial_size
)
== 0,
"The provided container length is not valid. \
It needs to be divisable by decomp_level_count * glwe_size * polynomial_size:\
{}. Got container length: {} and decomp_level_count: {decomp_level_count:?}, \
glwe_size: {glwe_size:?}, polynomial_size: {polynomial_size:?}.",
glwe_relinearisation_key_input_key_element_encrypted_size(
decomp_level_count,
glwe_size,
polynomial_size
),
container.container_len()
);
GlweRelinearisationKey {
data: container,
decomp_base_log,
decomp_level_count,
glwe_size,
polynomial_size,
ciphertext_modulus,
}
}
/// Return the [`GlweDimension`] of the [`GlweRelinearisationKey`].
///
/// See [`GlweRelinearisationKey::from_container`] for usage.
pub fn glwe_dimension(&self) -> GlweDimension {
self.glwe_size.to_glwe_dimension()
}
/// Return the [`GlweSize`] of the [`GlweRelinearisationKey`].
///
/// See [`GlweRelinearisationKey::from_container`] for usage.
pub fn glwe_size(&self) -> GlweSize {
self.glwe_size
}
/// Return the output [`PolynomialSize`] of the [`GlweRelinearisationKey`].
///
/// See [`GlweRelinearisationKey::from_container`] for usage.
pub fn polynomial_size(&self) -> PolynomialSize {
self.polynomial_size
}
/// Return the [`DecompositionLevelCount`] of the [`GlweRelinearisationKey`].
///
/// See [`GlweRelinearisationKey::from_container`] for usage.
pub fn decomposition_level_count(&self) -> DecompositionLevelCount {
self.decomp_level_count
}
/// Return the [`DecompositionBaseLog`] of the [`GlweRelinearisationKey`].
///
/// See [`GlweRelinearisationKey::from_container`] for usage.
pub fn decomposition_base_log(&self) -> DecompositionBaseLog {
self.decomp_base_log
}
/// Return the number of elements in an encryption of an input [`GlweSecretKey`] element of the
/// current [`GlweRelinearisationKey`].
pub fn input_key_element_encrypted_size(&self) -> usize {
glwe_relinearisation_key_input_key_element_encrypted_size(
self.decomp_level_count,
self.glwe_size,
self.polynomial_size,
)
}
/// Return a view of the [`GlweRelinearisationKey`]. This is useful if an
/// algorithm takes a view by value.
pub fn as_view(&self) -> GlweRelinearisationKey<&'_ [Scalar]> {
GlweRelinearisationKey::from_container(
self.as_ref(),
self.decomp_base_log,
self.decomp_level_count,
self.glwe_size,
self.polynomial_size,
self.ciphertext_modulus,
)
}
/// Consume the entity and return its underlying container.
///
/// See [`GlweRelinearisationKey::from_container`] for usage.
pub fn into_container(self) -> C {
self.data
}
/// Return the [`CiphertextModulus`] of the [`GlweRelinearisationKey`]
///
/// See [`GlweRelinearisationKey::from_container`] for usage.
pub fn ciphertext_modulus(&self) -> CiphertextModulus<C::Element> {
self.ciphertext_modulus
}
}
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> GlweRelinearisationKey<C> {
/// Mutable variant of [`LweTracePackingKeyswitchKey::as_view`].
pub fn as_mut_view(&mut self) -> GlweRelinearisationKey<&'_ mut [Scalar]> {
let decomp_base_log = self.decomp_base_log;
let decomp_level_count = self.decomp_level_count;
let glwe_size = self.glwe_size;
let polynomial_size = self.polynomial_size;
let ciphertext_modulus = self.ciphertext_modulus;
GlweRelinearisationKey::from_container(
self.as_mut(),
decomp_base_log,
decomp_level_count,
glwe_size,
polynomial_size,
ciphertext_modulus,
)
}
}
/// A [`GlweRelinearisationKey`] owning the memory for its own storage.
pub type GlweRelinearisationKeyOwned<Scalar> = GlweRelinearisationKey<Vec<Scalar>>;
impl<Scalar: UnsignedInteger> GlweRelinearisationKeyOwned<Scalar> {
/// Create a new [`GlweRelinearisationKey`].
///
/// # Note
///
/// This function allocates a vector of the appropriate size and wraps it in the appropriate
/// type. If you want to generate an [`GlweRelinearisationKey`] you need to use
/// [`crate::core_crypto::algorithms::generate_glwe_relinearisation_key`]
/// using this key as output.
///
/// See [`GlweRelinearisationKey::from_container`] for usage.
pub fn new(
fill_with: Scalar,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
glwe_size: GlweSize,
polynomial_size: PolynomialSize,
ciphertext_modulus: CiphertextModulus<Scalar>,
) -> GlweRelinearisationKeyOwned<Scalar> {
GlweRelinearisationKeyOwned::from_container(
vec![
fill_with;
glwe_relinearisation_key_size(decomp_level_count, glwe_size, polynomial_size)
],
decomp_base_log,
decomp_level_count,
glwe_size,
polynomial_size,
ciphertext_modulus,
)
}
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> ContiguousEntityContainer
for GlweRelinearisationKey<C>
{
type Element = C::Element;
type EntityViewMetadata = GlweCiphertextListCreationMetadata<Scalar>;
type EntityView<'this> = GlweCiphertextListView<'this, Self::Element>
where
Self: 'this;
type SelfViewMetadata = ();
// At the moment it does not make sense to return "sub" packing keyswitch keys. So we use a
// dummy placeholder type here.
type SelfView<'this> = DummyCreateFrom
where
Self: 'this;
fn get_entity_view_creation_metadata(&self) -> Self::EntityViewMetadata {
// Fix to use the correct ciphertext modulus
GlweCiphertextListCreationMetadata(
self.glwe_size,
self.polynomial_size,
self.ciphertext_modulus(),
)
}
fn get_entity_view_pod_size(&self) -> usize {
self.input_key_element_encrypted_size()
}
/// Unimplemented for [`GlweRelinearisationKey`]. At the moment it does not
/// make sense to return "sub" packing keyswitch keys.
fn get_self_view_creation_metadata(&self) -> Self::SelfViewMetadata {
unimplemented!(
"This function is not supported for GlweRelinearisationKey. \
At the moment it does not make sense to return 'sub' relinearisation keys."
)
}
}
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> ContiguousEntityContainerMut
for GlweRelinearisationKey<C>
{
type EntityMutView<'this> = GlweCiphertextListMutView<'this, Self::Element>
where
Self: 'this;
// At the moment it does not make sense to return "sub" relinearisation keys. So we use a
// dummy placeholder type here.
type SelfMutView<'this> = DummyCreateFrom
where
Self: 'this;
}
/// Metadata used in the [`CreateFrom`] implementation to create
/// [`GlweRelinearisationKey`] entities.
#[derive(Clone, Copy)]
pub struct GlweRelinearisationKeyCreationMetadata<Scalar: UnsignedInteger>(
pub DecompositionBaseLog,
pub DecompositionLevelCount,
pub GlweSize,
pub PolynomialSize,
pub CiphertextModulus<Scalar>,
);
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> CreateFrom<C>
for GlweRelinearisationKey<C>
{
type Metadata = GlweRelinearisationKeyCreationMetadata<Scalar>;
#[inline]
fn create_from(from: C, meta: Self::Metadata) -> GlweRelinearisationKey<C> {
let GlweRelinearisationKeyCreationMetadata(
decomp_base_log,
decomp_level_count,
glwe_size,
polynomial_size,
ciphertext_modulus,
) = meta;
GlweRelinearisationKey::from_container(
from,
decomp_base_log,
decomp_level_count,
glwe_size,
polynomial_size,
ciphertext_modulus,
)
}
}

View File

@@ -0,0 +1,421 @@
//! Module containing the definition of the LwePublicFunctionalPackingKeyswitchKey.
use crate::core_crypto::commons::parameters::*;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::entities::*;
/// A [`LWE public functional packing keyswitch key`](`LwePublicFunctionalPackingKeyswitchKey`).
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct LwePublicFunctionalPackingKeyswitchKey<C: Container>
where
C::Element: UnsignedInteger,
{
data: C,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
output_glwe_size: GlweSize,
output_polynomial_size: PolynomialSize,
ciphertext_modulus: CiphertextModulus<C::Element>,
}
impl<T: UnsignedInteger, C: Container<Element = T>> AsRef<[T]>
for LwePublicFunctionalPackingKeyswitchKey<C>
{
fn as_ref(&self) -> &[T] {
self.data.as_ref()
}
}
impl<T: UnsignedInteger, C: ContainerMut<Element = T>> AsMut<[T]>
for LwePublicFunctionalPackingKeyswitchKey<C>
{
fn as_mut(&mut self) -> &mut [T] {
self.data.as_mut()
}
}
/// Return the number of elements in an encryption of a list of input [`LweSecretKey`] elements for
/// a [`LwePublicFunctionalPackingKeyswitchKey`] given a [`DecompositionLevelCount`] and output
/// [`GlweSize`] and [`PolynomialSize`].
pub fn lwe_pubfpksk_input_key_element_encrypted_size(
decomp_level_count: DecompositionLevelCount,
output_glwe_size: GlweSize,
output_polynomial_size: PolynomialSize,
) -> usize {
// One ciphertext per level encrypted under the output key
decomp_level_count.0 * output_glwe_size.0 * output_polynomial_size.0
}
/// Return the number of elements in an [`LwePublicFunctionalPackingKeyswitchKey`] given an input
/// ['size of the list of LWe] [`LweSize`], [`DecompositionLevelCount`], output [`GlweSize`], and
/// output [`PolynomialSize`].
pub fn lwe_pubfpksk_size(
input_lwe_size: LweSize,
decomp_level_count: DecompositionLevelCount,
output_glwe_size: GlweSize,
output_polynomial_size: PolynomialSize,
) -> usize {
input_lwe_size.0
* lwe_pubfpksk_input_key_element_encrypted_size(
decomp_level_count,
output_glwe_size,
output_polynomial_size,
)
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>>
LwePublicFunctionalPackingKeyswitchKey<C>
{
/// Create an [`LwePublicFunctionalPackingKeyswitchKey`] from an existing container.
///
/// # Note
///
/// This function only wraps a container in the appropriate type. If you want to generate an
/// [`LwePublicFunctionalPackingKeyswitchKey`] you need to use
/// [`crate::core_crypto::algorithms::generate_lwe_public_functional_packing_keyswitch_key`] or
/// the parallel variant
/// [`crate::core_crypto::algorithms::par_generate_lwe_public_functional_packing_keyswitch_key`]
/// using this key as output.
///
/// This docstring exhibits [`LwePublicFunctionalPackingKeyswitchKey`] primitives usage.
///
/// ```
/// use tfhe::core_crypto::prelude::*;
///
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
/// // computations
/// // Define parameters for LwePublicFunctionalPackingKeyswitchKey creation
/// let glwe_size = GlweSize(2);
/// let polynomial_size = PolynomialSize(1024);
/// let decomp_base_log = DecompositionBaseLog(8);
/// let decomp_level_count = DecompositionLevelCount(3);
/// let input_lwe_dimension = LweDimension(600);
/// let ciphertext_modulus = CiphertextModulus::new_native();
///
/// // Create a new LwePublicFunctionalPackingKeyswitchKey
/// let pubfpksk = LwePublicFunctionalPackingKeyswitchKey::new(
/// 0u64,
/// decomp_base_log,
/// decomp_level_count,
/// input_lwe_dimension,
/// glwe_size,
/// polynomial_size,
/// ciphertext_modulus,
/// );
///
/// assert_eq!(
/// pubfpksk.output_glwe_key_dimension(),
/// glwe_size.to_glwe_dimension()
/// );
/// assert_eq!(pubfpksk.output_glwe_size(), glwe_size);
/// assert_eq!(pubfpksk.output_polynomial_size(), polynomial_size);
/// assert_eq!(pubfpksk.decomposition_base_log(), decomp_base_log);
/// assert_eq!(pubfpksk.decomposition_level_count(), decomp_level_count);
/// assert_eq!(pubfpksk.input_lwe_key_dimension(), input_lwe_dimension);
/// assert_eq!(pubfpksk.ciphertext_modulus(), ciphertext_modulus);
///
/// // Demonstrate how to recover the allocated container
/// let underlying_container: Vec<u64> = pubfpksk.into_container();
///
/// // Recreate a key using from_container
/// let pubfpksk = LwePublicFunctionalPackingKeyswitchKeyList::from_container(
/// underlying_container,
/// decomp_base_log,
/// decomp_level_count,
/// input_lwe_dimension.to_lwe_size(),
/// glwe_size,
/// polynomial_size,
/// ciphertext_modulus,
/// );
///
/// assert_eq!(
/// pubfpksk.output_glwe_key_dimension(),
/// glwe_size.to_glwe_dimension()
/// );
/// assert_eq!(pubfpksk.output_glwe_size(), glwe_size);
/// assert_eq!(pubfpksk.output_polynomial_size(), polynomial_size);
/// assert_eq!(pubfpksk.decomposition_base_log(), decomp_base_log);
/// assert_eq!(pubfpksk.decomposition_level_count(), decomp_level_count);
/// assert_eq!(pubfpksk.input_lwe_key_dimension(), input_lwe_dimension);
/// assert_eq!(pubfpksk.ciphertext_modulus(), ciphertext_modulus);
/// ```
pub fn from_container(
container: C,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
output_glwe_size: GlweSize,
output_polynomial_size: PolynomialSize,
ciphertext_modulus: CiphertextModulus<C::Element>,
) -> LwePublicFunctionalPackingKeyswitchKey<C> {
assert!(
container.container_len() > 0,
"Got an empty container to create an LweKeyswitchKey"
);
assert!(
container.container_len()
% lwe_pubfpksk_input_key_element_encrypted_size(
decomp_level_count,
output_glwe_size,
output_polynomial_size
)
== 0,
"The provided container length is not valid. \
It needs to be dividable by decomp_level_count * output_glwe_size * output_polynomial_size:\
{}. Got container length: {} and decomp_level_count: {decomp_level_count:?}, \
output_glwe_size: {output_glwe_size:?}, output_polynomial_size: \
{output_polynomial_size:?}.",
lwe_pubfpksk_input_key_element_encrypted_size(
decomp_level_count,
output_glwe_size,
output_polynomial_size
),
container.container_len()
);
LwePublicFunctionalPackingKeyswitchKey {
data: container,
decomp_base_log,
decomp_level_count,
output_glwe_size,
output_polynomial_size,
ciphertext_modulus,
}
}
/// Return the output key [`GlweDimension`] of the [`LwePublicFunctionalPackingKeyswitchKey`].
///
/// See [`LwePublicFunctionalPackingKeyswitchKey::from_container`] for usage.
pub fn output_glwe_key_dimension(&self) -> GlweDimension {
self.output_glwe_size.to_glwe_dimension()
}
/// Return the output [`GlweSize`] of the [`LwePublicFunctionalPackingKeyswitchKey`].
///
/// See [`LwePublicFunctionalPackingKeyswitchKey::from_container`] for usage.
pub fn output_glwe_size(&self) -> GlweSize {
self.output_glwe_size
}
/// Return the output [`PolynomialSize`] of the [`LwePublicFunctionalPackingKeyswitchKey`].
///
/// See [`LwePublicFunctionalPackingKeyswitchKey::from_container`] for usage.
pub fn output_polynomial_size(&self) -> PolynomialSize {
self.output_polynomial_size
}
/// Return the input key [`LweDimension`] of the [`LwePublicFunctionalPackingKeyswitchKey`].
///
/// See [`LwePublicFunctionalPackingKeyswitchKey::from_container`] for usage.
pub fn input_lwe_key_dimension(&self) -> LweDimension {
LweDimension(self.data.container_len() / self.input_key_element_encrypted_size() - 1)
}
/// Return the [`DecompositionLevelCount`] of the [`LwePublicFunctionalPackingKeyswitchKey`].
///
/// See [`LwePublicFunctionalPackingKeyswitchKey::from_container`] for usage.
pub fn decomposition_level_count(&self) -> DecompositionLevelCount {
self.decomp_level_count
}
/// Return the [`DecompositionBaseLog`] of the [`LwePublicFunctionalPackingKeyswitchKey`].
///
/// See [`LwePublicFunctionalPackingKeyswitchKey::from_container`] for usage.
pub fn decomposition_base_log(&self) -> DecompositionBaseLog {
self.decomp_base_log
}
/// Return the number of elements in an encryption of an input [`LweSecretKey`] element of the
/// current [`LwePublicFunctionalPackingKeyswitchKey`].
pub fn input_key_element_encrypted_size(&self) -> usize {
lwe_pubfpksk_input_key_element_encrypted_size(
self.decomp_level_count,
self.output_glwe_size,
self.output_polynomial_size,
)
}
/// Return a view of the [`LwePublicFunctionalPackingKeyswitchKey`]. This is useful if an
/// algorithm takes a view by value.
pub fn as_view(&self) -> LwePublicFunctionalPackingKeyswitchKey<&'_ [Scalar]> {
LwePublicFunctionalPackingKeyswitchKey::from_container(
self.as_ref(),
self.decomp_base_log,
self.decomp_level_count,
self.output_glwe_size,
self.output_polynomial_size,
self.ciphertext_modulus,
)
}
/// Consume the entity and return its underlying container.
///
/// See [`LwePublicFunctionalPackingKeyswitchKey::from_container`] for usage.
pub fn into_container(self) -> C {
self.data
}
/// Return the [`CiphertextModulus`] of the [`LwePublicFunctionalPackingKeyswitchKey`]
///
/// See [`LwePublicFunctionalPackingKeyswitchKey::from_container`] for usage.
pub fn ciphertext_modulus(&self) -> CiphertextModulus<C::Element> {
self.ciphertext_modulus
}
}
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>>
LwePublicFunctionalPackingKeyswitchKey<C>
{
/// Mutable variant of [`LwePublicFunctionalPackingKeyswitchKey::as_view`].
pub fn as_mut_view(&mut self) -> LwePublicFunctionalPackingKeyswitchKey<&'_ mut [Scalar]> {
let decomp_base_log = self.decomp_base_log;
let decomp_level_count = self.decomp_level_count;
let output_glwe_size = self.output_glwe_size;
let output_polynomial_size = self.output_polynomial_size;
let ciphertext_modulus = self.ciphertext_modulus;
LwePublicFunctionalPackingKeyswitchKey::from_container(
self.as_mut(),
decomp_base_log,
decomp_level_count,
output_glwe_size,
output_polynomial_size,
ciphertext_modulus,
)
}
}
/// An [`LwePublicFunctionalPackingKeyswitchKey`] owning the memory for its own storage.
pub type LwePublicFunctionalPackingKeyswitchKeyOwned<Scalar> =
LwePublicFunctionalPackingKeyswitchKey<Vec<Scalar>>;
impl<Scalar: UnsignedInteger> LwePublicFunctionalPackingKeyswitchKeyOwned<Scalar> {
/// Create an [`LwePublicFunctionalPackingKeyswitchKey`] from an existing container.
///
/// # Note
///
/// This function allocates a vector of the appropriate size and wraps it in the appropriate
/// type. If you want to generate an [`LwePublicFunctionalPackingKeyswitchKey`] you need to use
/// [`crate::core_crypto::algorithms::generate_lwe_public_functional_packing_keyswitch_key`] or
/// the parallel variant
/// [`crate::core_crypto::algorithms::par_generate_lwe_public_functional_packing_keyswitch_key`]
/// using this key as output.
///
/// See [`LwePublicFunctionalPackingKeyswitchKey::from_container`] for usage.
pub fn new(
fill_with: Scalar,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
input_key_lwe_dimension: LweDimension,
output_glwe_size: GlweSize,
output_polynomial_size: PolynomialSize,
ciphertext_modulus: CiphertextModulus<Scalar>,
) -> LwePublicFunctionalPackingKeyswitchKeyOwned<Scalar> {
LwePublicFunctionalPackingKeyswitchKeyOwned::from_container(
vec![
fill_with;
lwe_pubfpksk_size(
input_key_lwe_dimension.to_lwe_size(),
decomp_level_count,
output_glwe_size,
output_polynomial_size
)
],
decomp_base_log,
decomp_level_count,
output_glwe_size,
output_polynomial_size,
ciphertext_modulus,
)
}
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> ContiguousEntityContainer
for LwePublicFunctionalPackingKeyswitchKey<C>
{
type Element = C::Element;
type EntityViewMetadata = GlweCiphertextListCreationMetadata<Scalar>;
type EntityView<'this> = GlweCiphertextListView<'this, Self::Element>
where
Self: 'this;
type SelfViewMetadata = ();
// At the moment it does not make sense to return "sub" packing keyswitch keys. So we use a
// dummy placeholder type here.
type SelfView<'this> = DummyCreateFrom
where
Self: 'this;
fn get_entity_view_creation_metadata(&self) -> Self::EntityViewMetadata {
GlweCiphertextListCreationMetadata(
self.output_glwe_size,
self.output_polynomial_size,
self.ciphertext_modulus,
)
}
fn get_entity_view_pod_size(&self) -> usize {
self.input_key_element_encrypted_size()
}
/// Unimplemented for [`LwePublicFunctionalPackingKeyswitchKey`]. At the moment it does not
/// make sense to return "sub" packing keyswitch keys.
fn get_self_view_creation_metadata(&self) -> Self::SelfViewMetadata {
unimplemented!(
"This function is not supported for LwePublicFunctionalPackingKeyswitchKey. \
At the moment it does not make sense to return 'sub' packing keyswitch keys."
)
}
}
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> ContiguousEntityContainerMut
for LwePublicFunctionalPackingKeyswitchKey<C>
{
type EntityMutView<'this> = GlweCiphertextListMutView<'this, Self::Element>
where
Self: 'this;
// At the moment it does not make sense to return "sub" packing keyswitch keys. So we use a
// dummy placeholder type here.
type SelfMutView<'this> = DummyCreateFrom
where
Self: 'this;
}
/// Metadata used in the [`CreateFrom`] implementation to create
/// [`LwePublicFunctionalPackingKeyswitchKey`] entities.
#[derive(Clone, Copy)]
pub struct LwePublicFunctionalPackingKeyswitchKeyCreationMetadata<Scalar: UnsignedInteger>(
pub DecompositionBaseLog,
pub DecompositionLevelCount,
pub GlweSize,
pub PolynomialSize,
pub CiphertextModulus<Scalar>,
);
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> CreateFrom<C>
for LwePublicFunctionalPackingKeyswitchKey<C>
{
type Metadata = LwePublicFunctionalPackingKeyswitchKeyCreationMetadata<Scalar>;
#[inline]
fn create_from(from: C, meta: Self::Metadata) -> LwePublicFunctionalPackingKeyswitchKey<C> {
let LwePublicFunctionalPackingKeyswitchKeyCreationMetadata(
decomp_base_log,
decomp_level_count,
output_glwe_size,
output_polynomial_size,
ciphertext_modulus,
) = meta;
LwePublicFunctionalPackingKeyswitchKey::from_container(
from,
decomp_base_log,
decomp_level_count,
output_glwe_size,
output_polynomial_size,
ciphertext_modulus,
)
}
}

View File

@@ -0,0 +1,444 @@
//! Module containing the definition of the LwePublicFunctionalPackingKeyswitchKeyList.
use crate::core_crypto::commons::parameters::*;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::entities::*;
/// A contiguous list containing [`LWE private functional packing keyswitch
/// keys`](`crate::core_crypto::entities::LwePrivateFunctionalPackingKeyswitchKey`).
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct LwePublicFunctionalPackingKeyswitchKeyList<C: Container>
where
C::Element: UnsignedInteger,
{
data: C,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
input_lwe_size: LweSize,
output_glwe_size: GlweSize,
output_polynomial_size: PolynomialSize,
ciphertext_modulus: CiphertextModulus<C::Element>,
}
impl<T: UnsignedInteger, C: Container<Element = T>> AsRef<[T]>
for LwePublicFunctionalPackingKeyswitchKeyList<C>
{
fn as_ref(&self) -> &[T] {
self.data.as_ref()
}
}
impl<T: UnsignedInteger, C: ContainerMut<Element = T>> AsMut<[T]>
for LwePublicFunctionalPackingKeyswitchKeyList<C>
{
fn as_mut(&mut self) -> &mut [T] {
self.data.as_mut()
}
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>>
LwePublicFunctionalPackingKeyswitchKeyList<C>
{
/// Create an [`LwePublicFunctionalPackingKeyswitchKeyList`] from an existing container.
///
/// # Note
///
/// This function only wraps a container in the appropriate type. If you want to generate keys
/// in the list you need to use
/// [`crate::core_crypto::algorithms::generate_lwe_public_functional_packing_keyswitch_key`] or
/// the parallel variant
/// [`crate::core_crypto::algorithms::par_generate_lwe_public_functional_packing_keyswitch_key`]
/// on the individual keys in the list.
///
/// This docstring exhibits [`LwePublicFunctionalPackingKeyswitchKeyList`] primitives usage.
///
/// ```
/// use tfhe::core_crypto::prelude::*;
///
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
/// // computations
/// // Define parameters for LwePublicFunctionalPackingKeyswitchKeyList creation
/// let glwe_size = GlweSize(2);
/// let polynomial_size = PolynomialSize(1024);
/// let decomp_base_log = DecompositionBaseLog(8);
/// let decomp_level_count = DecompositionLevelCount(3);
/// let input_lwe_dimension = LweDimension(600);
/// let lwe_pubfpksk_count = FunctionalPackingKeyswitchKeyCount(2);
/// let ciphertext_modulus = CiphertextModulus::new_native();
///
/// // Create a new LwePublicFunctionalPackingKeyswitchKeyList
/// let pubfpksk_list = LwePublicFunctionalPackingKeyswitchKeyList::new(
/// 0u64,
/// decomp_base_log,
/// decomp_level_count,
/// input_lwe_dimension,
/// glwe_size,
/// polynomial_size,
/// lwe_pubfpksk_count,
/// ciphertext_modulus,
/// );
///
/// assert_eq!(
/// pubfpksk_list.output_glwe_key_dimension(),
/// glwe_size.to_glwe_dimension()
/// );
/// assert_eq!(pubfpksk_list.output_glwe_size(), glwe_size);
/// assert_eq!(pubfpksk_list.output_polynomial_size(), polynomial_size);
/// assert_eq!(pubfpksk_list.decomposition_base_log(), decomp_base_log);
/// assert_eq!(
/// pubfpksk_list.decomposition_level_count(),
/// decomp_level_count
/// );
/// assert_eq!(
/// pubfpksk_list.input_lwe_size(),
/// input_lwe_dimension.to_lwe_size()
/// );
/// assert_eq!(pubfpksk_list.input_lwe_key_dimension(), input_lwe_dimension);
/// assert_eq!(pubfpksk_list.lwe_pubfpksk_count(), lwe_pubfpksk_count);
/// assert_eq!(pubfpksk_list.ciphertext_modulus(), ciphertext_modulus);
///
/// // Demonstrate how to recover the allocated container
/// let underlying_container: Vec<u64> = pubfpksk_list.into_container();
///
/// // Recreate a list using from_container
/// let pubfpksk_list = LwePublicFunctionalPackingKeyswitchKeyList::from_container(
/// underlying_container,
/// decomp_base_log,
/// decomp_level_count,
/// input_lwe_dimension.to_lwe_size(),
/// glwe_size,
/// polynomial_size,
/// ciphertext_modulus,
/// );
///
/// assert_eq!(
/// pubfpksk_list.output_glwe_key_dimension(),
/// glwe_size.to_glwe_dimension()
/// );
/// assert_eq!(pubfpksk_list.output_glwe_size(), glwe_size);
/// assert_eq!(pubfpksk_list.output_polynomial_size(), polynomial_size);
/// assert_eq!(pubfpksk_list.decomposition_base_log(), decomp_base_log);
/// assert_eq!(
/// pubfpksk_list.decomposition_level_count(),
/// decomp_level_count
/// );
/// assert_eq!(
/// pubfpksk_list.input_lwe_size(),
/// input_lwe_dimension.to_lwe_size()
/// );
/// assert_eq!(pubfpksk_list.input_lwe_key_dimension(), input_lwe_dimension);
/// assert_eq!(pubfpksk_list.lwe_pubfpksk_count(), lwe_pubfpksk_count);
/// assert_eq!(pubfpksk_list.ciphertext_modulus(), ciphertext_modulus);
/// ```
pub fn from_container(
container: C,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
input_lwe_size: LweSize,
output_glwe_size: GlweSize,
output_polynomial_size: PolynomialSize,
ciphertext_modulus: CiphertextModulus<C::Element>,
) -> LwePublicFunctionalPackingKeyswitchKeyList<C> {
assert!(
container.container_len()
% lwe_pubfpksk_size(
input_lwe_size,
decomp_level_count,
output_glwe_size,
output_polynomial_size
)
== 0,
"The provided container length is not valid. \
It needs to be dividable by input_lwe_size * decomp_level_count * output_glwe_size * \
output_polynomial_size: {}. Got container length: {} and input_lwe_size: {input_lwe_size:?}\
decomp_level_count: {decomp_level_count:?}, output_glwe_size: {output_glwe_size:?}, \
output_polynomial_size: {output_polynomial_size:?}.",
lwe_pubfpksk_size(
input_lwe_size,
decomp_level_count,
output_glwe_size,
output_polynomial_size
),
container.container_len()
);
LwePublicFunctionalPackingKeyswitchKeyList {
data: container,
decomp_base_log,
decomp_level_count,
input_lwe_size,
output_glwe_size,
output_polynomial_size,
ciphertext_modulus,
}
}
/// Return the output key [`GlweDimension`] of the [`LwePublicFunctionalPackingKeyswitchKey`]
/// stored in the list.
///
/// See [`LwePublicFunctionalPackingKeyswitchKeyList::from_container`] for usage.
pub fn output_glwe_key_dimension(&self) -> GlweDimension {
self.output_glwe_size.to_glwe_dimension()
}
/// Return the output [`GlweSize`] of the [`LwePublicFunctionalPackingKeyswitchKey`] stored in
/// the list.
///
/// See [`LwePublicFunctionalPackingKeyswitchKeyList::from_container`] for usage.
pub fn output_glwe_size(&self) -> GlweSize {
self.output_glwe_size
}
/// Return the output [`PolynomialSize`] of the [`LwePublicFunctionalPackingKeyswitchKey`]
/// stored in the list.
///
/// See [`LwePublicFunctionalPackingKeyswitchKeyList::from_container`] for usage.
pub fn output_polynomial_size(&self) -> PolynomialSize {
self.output_polynomial_size
}
/// Return the input key [`LweDimension`] of the [`LwePublicFunctionalPackingKeyswitchKey`]
/// stored in the list.
///
/// See [`LwePublicFunctionalPackingKeyswitchKeyList::from_container`] for usage.
pub fn input_lwe_key_dimension(&self) -> LweDimension {
self.input_lwe_size.to_lwe_dimension()
}
/// Return the input [`LweSize`] of the [`LwePublicFunctionalPackingKeyswitchKey`]stored in the
/// list.
///
/// See [`LwePublicFunctionalPackingKeyswitchKeyList::from_container`] for usage.
pub fn input_lwe_size(&self) -> LweSize {
self.input_lwe_size
}
/// Return the [`DecompositionLevelCount`] of the [`LwePublicFunctionalPackingKeyswitchKey`]
/// stored in the list.
///
/// See [`LwePublicFunctionalPackingKeyswitchKeyList::from_container`] for usage.
pub fn decomposition_level_count(&self) -> DecompositionLevelCount {
self.decomp_level_count
}
/// Return the [`DecompositionBaseLog`] of the [`LwePublicFunctionalPackingKeyswitchKey`]
/// stored in the list.
///
/// See [`LwePublicFunctionalPackingKeyswitchKeyList::from_container`] for usage.
pub fn decomposition_base_log(&self) -> DecompositionBaseLog {
self.decomp_base_log
}
/// Return the number of elements in a [`LwePublicFunctionalPackingKeyswitchKey`] stored in
/// the list.
pub fn lwe_pubfpksk_size(&self) -> usize {
lwe_pubfpksk_size(
self.input_lwe_size,
self.decomp_level_count,
self.output_glwe_size,
self.output_polynomial_size,
)
}
/// Return the [`FunctionalPackingKeyswitchKeyCount`] of the
/// [`LwePublicFunctionalPackingKeyswitchKeyList`].
///
/// See [`LwePublicFunctionalPackingKeyswitchKeyList::from_container`] for usage.
pub fn lwe_pubfpksk_count(&self) -> FunctionalPackingKeyswitchKeyCount {
FunctionalPackingKeyswitchKeyCount(self.as_ref().container_len() / self.lwe_pubfpksk_size())
}
/// Return a view of the [`LwePublicFunctionalPackingKeyswitchKeyList`]. This is useful if an
/// algorithm takes a view by value.
pub fn as_view(&self) -> LwePublicFunctionalPackingKeyswitchKeyList<&'_ [Scalar]> {
LwePublicFunctionalPackingKeyswitchKeyList::from_container(
self.as_ref(),
self.decomp_base_log,
self.decomp_level_count,
self.input_lwe_size,
self.output_glwe_size,
self.output_polynomial_size,
self.ciphertext_modulus,
)
}
/// Consume the entity and return its underlying container.
///
/// See [`LwePublicFunctionalPackingKeyswitchKeyList::from_container`] for usage.
pub fn into_container(self) -> C {
self.data
}
/// Return the [`CiphertextModulus`] of the [`LwePublicFunctionalPackingKeyswitchKeyList`]
///
/// See [`LwePublicFunctionalPackingKeyswitchKeyList::from_container`] for usage.
pub fn ciphertext_modulus(&self) -> CiphertextModulus<C::Element> {
self.ciphertext_modulus
}
}
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>>
LwePublicFunctionalPackingKeyswitchKeyList<C>
{
/// Mutable variant of [`LwePublicFunctionalPackingKeyswitchKeyList::as_view`].
pub fn as_mut_view(&mut self) -> LwePublicFunctionalPackingKeyswitchKeyList<&'_ mut [Scalar]> {
let decomp_base_log = self.decomp_base_log;
let decomp_level_count = self.decomp_level_count;
let input_lwe_size = self.input_lwe_size;
let output_glwe_size = self.output_glwe_size;
let output_polynomial_size = self.output_polynomial_size;
let ciphertext_modulus = self.ciphertext_modulus;
LwePublicFunctionalPackingKeyswitchKeyList::from_container(
self.as_mut(),
decomp_base_log,
decomp_level_count,
input_lwe_size,
output_glwe_size,
output_polynomial_size,
ciphertext_modulus,
)
}
}
/// An [`LwePublicFunctionalPackingKeyswitchKeyList`] owning the memory for its own storage.
pub type LwePublicFunctionalPackingKeyswitchKeyListOwned<Scalar> =
LwePublicFunctionalPackingKeyswitchKeyList<Vec<Scalar>>;
impl<Scalar: UnsignedInteger> LwePublicFunctionalPackingKeyswitchKeyListOwned<Scalar> {
/// Allocate memory and create a new owned [`LwePublicFunctionalPackingKeyswitchKeyList`].
///
/// # Note
///
/// This function allocates a vector of the appropriate size and wraps it in the appropriate
/// type. If you want to generate keys in the list you need to use
/// [`crate::core_crypto::algorithms::generate_lwe_public_functional_packing_keyswitch_key`] or
/// the parallel variant
/// [`crate::core_crypto::algorithms::par_generate_lwe_public_functional_packing_keyswitch_key`]
/// on the individual keys in the list.
/// See [`LwePublicFunctionalPackingKeyswitchKeyList::from_container`] for usage.
#[allow(clippy::too_many_arguments)]
pub fn new(
fill_with: Scalar,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
input_key_lwe_dimension: LweDimension,
output_glwe_size: GlweSize,
output_polynomial_size: PolynomialSize,
pubfpksk_count: FunctionalPackingKeyswitchKeyCount,
ciphertext_modulus: CiphertextModulus<Scalar>,
) -> LwePublicFunctionalPackingKeyswitchKeyListOwned<Scalar> {
LwePublicFunctionalPackingKeyswitchKeyListOwned::from_container(
vec![
fill_with;
pubfpksk_count.0
* lwe_pubfpksk_size(
input_key_lwe_dimension.to_lwe_size(),
decomp_level_count,
output_glwe_size,
output_polynomial_size
)
],
decomp_base_log,
decomp_level_count,
input_key_lwe_dimension.to_lwe_size(),
output_glwe_size,
output_polynomial_size,
ciphertext_modulus,
)
}
}
/// Metadata used in the [`CreateFrom`] implementation to create
/// [`LwePublicFunctionalPackingKeyswitchKeyList`] entities.
#[derive(Clone, Copy)]
pub struct LwePublicFunctionalPackingKeyswitchKeyListCreationMetadata<Scalar: UnsignedInteger>(
pub DecompositionBaseLog,
pub DecompositionLevelCount,
pub LweSize,
pub GlweSize,
pub PolynomialSize,
pub CiphertextModulus<Scalar>,
);
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> CreateFrom<C>
for LwePublicFunctionalPackingKeyswitchKeyList<C>
{
type Metadata = LwePublicFunctionalPackingKeyswitchKeyListCreationMetadata<Scalar>;
#[inline]
fn create_from(from: C, meta: Self::Metadata) -> Self {
let LwePublicFunctionalPackingKeyswitchKeyListCreationMetadata(
decomp_base_log,
decomp_level_count,
input_lwe_size,
output_glwe_size,
output_polynomial_size,
ciphertext_modulus,
) = meta;
LwePublicFunctionalPackingKeyswitchKeyList::from_container(
from,
decomp_base_log,
decomp_level_count,
input_lwe_size,
output_glwe_size,
output_polynomial_size,
ciphertext_modulus,
)
}
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> ContiguousEntityContainer
for LwePublicFunctionalPackingKeyswitchKeyList<C>
{
type Element = C::Element;
type EntityViewMetadata = LwePublicFunctionalPackingKeyswitchKeyCreationMetadata<Scalar>;
type EntityView<'this> = LwePublicFunctionalPackingKeyswitchKey<&'this [Self::Element]>
where
Self: 'this;
type SelfViewMetadata = LwePublicFunctionalPackingKeyswitchKeyListCreationMetadata<Scalar>;
type SelfView<'this> = LwePublicFunctionalPackingKeyswitchKeyList<&'this [Self::Element]>
where
Self: 'this;
fn get_entity_view_creation_metadata(&self) -> Self::EntityViewMetadata {
LwePublicFunctionalPackingKeyswitchKeyCreationMetadata(
self.decomposition_base_log(),
self.decomposition_level_count(),
self.output_glwe_size(),
self.output_polynomial_size(),
self.ciphertext_modulus(),
)
}
fn get_entity_view_pod_size(&self) -> usize {
self.lwe_pubfpksk_size()
}
fn get_self_view_creation_metadata(&self) -> Self::SelfViewMetadata {
LwePublicFunctionalPackingKeyswitchKeyListCreationMetadata(
self.decomposition_base_log(),
self.decomposition_level_count(),
self.input_lwe_size(),
self.output_glwe_size(),
self.output_polynomial_size(),
self.ciphertext_modulus(),
)
}
}
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> ContiguousEntityContainerMut
for LwePublicFunctionalPackingKeyswitchKeyList<C>
{
type EntityMutView<'this> = LwePublicFunctionalPackingKeyswitchKey<&'this mut [Self::Element]>
where
Self: 'this;
type SelfMutView<'this> = LwePublicFunctionalPackingKeyswitchKeyList<&'this mut [Self::Element]>
where
Self: 'this;
}

View File

@@ -0,0 +1,410 @@
//! Module containing the definition of the LweTracePackingKeyswitchKey.
use crate::core_crypto::commons::parameters::*;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::entities::*;
/// An [`LWE trace packing keyswitch key`](`LweTracePackingKeyswitchKey`).
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct LweTracePackingKeyswitchKey<C: Container>
where
C::Element: UnsignedInteger,
{
data: C,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
input_lwe_size: LweSize,
output_glwe_size: GlweSize,
polynomial_size: PolynomialSize,
ciphertext_modulus: CiphertextModulus<C::Element>,
}
impl<T: UnsignedInteger, C: Container<Element = T>> AsRef<[T]> for LweTracePackingKeyswitchKey<C> {
fn as_ref(&self) -> &[T] {
self.data.as_ref()
}
}
impl<T: UnsignedInteger, C: ContainerMut<Element = T>> AsMut<[T]>
for LweTracePackingKeyswitchKey<C>
{
fn as_mut(&mut self) -> &mut [T] {
self.data.as_mut()
}
}
/// Return the number of elements in an encryption of an input [`LweSecretKey`] element for a
/// [`LweTracePackingKeyswitchKey`] given a [`DecompositionLevelCount`] and output
/// [`GlweSize`] and [`PolynomialSize`].
pub fn lwe_tpksk_input_key_element_encrypted_size(
decomp_level_count: DecompositionLevelCount,
output_glwe_size: GlweSize,
polynomial_size: PolynomialSize,
) -> usize {
// One ciphertext per level encrypted under the output key
decomp_level_count.0 * output_glwe_size.0 * polynomial_size.0
}
/// Return the number of elements in an [`LweTracePackingKeyswitchKey`] given a
/// [`DecompositionLevelCount`], output [`GlweSize`], and output [`PolynomialSize`].
pub fn lwe_tpksk_size(
decomp_level_count: DecompositionLevelCount,
output_glwe_size: GlweSize,
polynomial_size: PolynomialSize,
) -> usize {
output_glwe_size.to_glwe_dimension().0
* polynomial_size.log2().0
* lwe_tpksk_input_key_element_encrypted_size(
decomp_level_count,
output_glwe_size,
polynomial_size,
)
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> LweTracePackingKeyswitchKey<C> {
/// Create an [`LweTracePackingKeyswitchKey`] from an existing container.
///
/// # Note
///
/// This function only wraps a container in the appropriate type. If you want to generate an
/// [`LweTracePackingKeyswitchKey`] you need to use
/// [`crate::core_crypto::algorithms::generate_lwe_trace_packing_keyswitch_key`]
/// using this key as output.
///
/// This docstring exhibits [`LweTracePackingKeyswitchKey`] primitives usage.
///
/// ```
/// use tfhe::core_crypto::prelude::*;
///
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
/// // computations
/// // Define parameters for LweTracePackingKeyswitchKey creation
/// let lwe_size = LweSize(1001);
/// let glwe_size = GlweSize(2);
/// let polynomial_size = PolynomialSize(1024);
/// let decomp_base_log = DecompositionBaseLog(8);
/// let decomp_level_count = DecompositionLevelCount(3);
/// let ciphertext_modulus = CiphertextModulus::new_native();
///
/// // Create a new LweTracePackingKeyswitchKey
/// let tpksk = LweTracePackingKeyswitchKey::new(
/// 0u64,
/// decomp_base_log,
/// decomp_level_count,
/// lwe_size,
/// glwe_size,
/// polynomial_size,
/// ciphertext_modulus,
/// );
///
/// assert_eq!(
/// tpksk.output_glwe_key_dimension(),
/// glwe_size.to_glwe_dimension()
/// );
/// assert_eq!(tpksk.output_glwe_size(), glwe_size);
/// assert_eq!(tpksk.polynomial_size(), polynomial_size);
/// assert_eq!(tpksk.decomposition_base_log(), decomp_base_log);
/// assert_eq!(tpksk.decomposition_level_count(), decomp_level_count);
/// assert_eq!(tpksk.ciphertext_modulus(), ciphertext_modulus);
///
/// // Demonstrate how to recover the allocated container
/// let underlying_container: Vec<u64> = tpksk.into_container();
///
/// // Recreate a key using from_container
/// let tpksk = LweTracePackingKeyswitchKey::from_container(
/// underlying_container,
/// decomp_base_log,
/// decomp_level_count,
/// lwe_size,
/// glwe_size,
/// polynomial_size,
/// ciphertext_modulus,
/// );
///
/// assert_eq!(
/// tpksk.output_glwe_key_dimension(),
/// glwe_size.to_glwe_dimension()
/// );
/// assert_eq!(tpksk.input_lwe_size(), lwe_size);
/// assert_eq!(tpksk.output_glwe_size(), glwe_size);
/// assert_eq!(tpksk.polynomial_size(), polynomial_size);
/// assert_eq!(tpksk.decomposition_base_log(), decomp_base_log);
/// assert_eq!(tpksk.decomposition_level_count(), decomp_level_count);
/// assert_eq!(tpksk.ciphertext_modulus(), ciphertext_modulus);
/// ```
pub fn from_container(
container: C,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
input_lwe_size: LweSize,
output_glwe_size: GlweSize,
polynomial_size: PolynomialSize,
ciphertext_modulus: CiphertextModulus<C::Element>,
) -> LweTracePackingKeyswitchKey<C> {
assert!(
container.container_len() > 0,
"Got an empty container to create an LweKeyswitchKey"
);
assert!(
container.container_len()
% lwe_tpksk_input_key_element_encrypted_size(
decomp_level_count,
output_glwe_size,
polynomial_size
)
== 0,
"The provided container length is not valid. \
It needs to be divisable by decomp_level_count * output_glwe_size * polynomial_size:\
{}. Got container length: {} and decomp_level_count: {decomp_level_count:?}, \
output_glwe_size: {output_glwe_size:?}, polynomial_size: \
{polynomial_size:?}.",
lwe_tpksk_input_key_element_encrypted_size(
decomp_level_count,
output_glwe_size,
polynomial_size
),
container.container_len()
);
LweTracePackingKeyswitchKey {
data: container,
decomp_base_log,
decomp_level_count,
input_lwe_size,
output_glwe_size,
polynomial_size,
ciphertext_modulus,
}
}
/// Return the output key [`GlweDimension`] of the [`LweTracePackingKeyswitchKey`].
///
/// See [`LweTracePackingKeyswitchKey::from_container`] for usage.
pub fn output_glwe_key_dimension(&self) -> GlweDimension {
self.output_glwe_size.to_glwe_dimension()
}
/// Return the output [`GlweSize`] of the [`LweTracePackingKeyswitchKey`].
///
/// See [`LweTracePackingKeyswitchKey::from_container`] for usage.
pub fn output_glwe_size(&self) -> GlweSize {
self.output_glwe_size
}
/// Return the output [`PolynomialSize`] of the [`LweTracePackingKeyswitchKey`].
///
/// See [`LweTracePackingKeyswitchKey::from_container`] for usage.
pub fn polynomial_size(&self) -> PolynomialSize {
self.polynomial_size
}
/// Return the input [`LweSize`] of the [`LweTracePackingKeyswitchKey`].
///
/// See [`LweTracePackingKeyswitchKey::from_container`] for usage.
pub fn input_lwe_size(&self) -> LweSize {
self.input_lwe_size
}
/// Return the [`DecompositionLevelCount`] of the [`LweTracePackingKeyswitchKey`].
///
/// See [`LweTracePackingKeyswitchKey::from_container`] for usage.
pub fn decomposition_level_count(&self) -> DecompositionLevelCount {
self.decomp_level_count
}
/// Return the [`DecompositionBaseLog`] of the [`LweTracePackingKeyswitchKey`].
///
/// See [`LweTracePackingKeyswitchKey::from_container`] for usage.
pub fn decomposition_base_log(&self) -> DecompositionBaseLog {
self.decomp_base_log
}
/// Return the number of elements in an encryption of an input [`LweSecretKey`] element of the
/// current [`LweTracePackingKeyswitchKey`].
pub fn input_key_element_encrypted_size(&self) -> usize {
lwe_tpksk_input_key_element_encrypted_size(
self.decomp_level_count,
self.output_glwe_size,
self.polynomial_size,
)
}
/// Return a view of the [`LweTracePackingKeyswitchKey`]. This is useful if an
/// algorithm takes a view by value.
pub fn as_view(&self) -> LweTracePackingKeyswitchKey<&'_ [Scalar]> {
LweTracePackingKeyswitchKey::from_container(
self.as_ref(),
self.decomp_base_log,
self.decomp_level_count,
self.input_lwe_size,
self.output_glwe_size,
self.polynomial_size,
self.ciphertext_modulus,
)
}
/// Consume the entity and return its underlying container.
///
/// See [`LweTracePackingKeyswitchKey::from_container`] for usage.
pub fn into_container(self) -> C {
self.data
}
/// Return the [`CiphertextModulus`] of the [`LweTracePackingKeyswitchKey`]
///
/// See [`LweTracePackingKeyswitchKey::from_container`] for usage.
pub fn ciphertext_modulus(&self) -> CiphertextModulus<C::Element> {
self.ciphertext_modulus
}
}
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> LweTracePackingKeyswitchKey<C> {
/// Mutable variant of [`LweTracePackingKeyswitchKey::as_view`].
pub fn as_mut_view(&mut self) -> LweTracePackingKeyswitchKey<&'_ mut [Scalar]> {
let decomp_base_log = self.decomp_base_log;
let decomp_level_count = self.decomp_level_count;
let input_lwe_size = self.input_lwe_size;
let output_glwe_size = self.output_glwe_size;
let polynomial_size = self.polynomial_size;
let ciphertext_modulus = self.ciphertext_modulus;
LweTracePackingKeyswitchKey::from_container(
self.as_mut(),
decomp_base_log,
decomp_level_count,
input_lwe_size,
output_glwe_size,
polynomial_size,
ciphertext_modulus,
)
}
}
/// An [`LweTracePackingKeyswitchKey`] owning the memory for its own storage.
pub type LweTracePackingKeyswitchKeyOwned<Scalar> = LweTracePackingKeyswitchKey<Vec<Scalar>>;
impl<Scalar: UnsignedInteger> LweTracePackingKeyswitchKeyOwned<Scalar> {
/// Create an [`LweTracePackingKeyswitchKey`] from an existing container.
///
/// # Note
///
/// This function allocates a vector of the appropriate size and wraps it in the appropriate
/// type. If you want to generate an [`LweTracePackingKeyswitchKey`] you need to use
/// [`crate::core_crypto::algorithms::generate_lwe_trace_packing_keyswitch_key`]
/// using this key as output.
///
/// See [`LweTracePackingKeyswitchKey::from_container`] for usage.
pub fn new(
fill_with: Scalar,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
input_lwe_size: LweSize,
output_glwe_size: GlweSize,
polynomial_size: PolynomialSize,
ciphertext_modulus: CiphertextModulus<Scalar>,
) -> LweTracePackingKeyswitchKeyOwned<Scalar> {
LweTracePackingKeyswitchKeyOwned::from_container(
vec![fill_with; lwe_tpksk_size(decomp_level_count, output_glwe_size, polynomial_size)],
decomp_base_log,
decomp_level_count,
input_lwe_size,
output_glwe_size,
polynomial_size,
ciphertext_modulus,
)
}
}
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> ContiguousEntityContainer
for LweTracePackingKeyswitchKey<C>
{
type Element = C::Element;
type EntityViewMetadata = GlweCiphertextListCreationMetadata<Scalar>;
type EntityView<'this> = GlweCiphertextListView<'this, Self::Element>
where
Self: 'this;
type SelfViewMetadata = ();
// At the moment it does not make sense to return "sub" packing keyswitch keys. So we use a
// dummy placeholder type here.
type SelfView<'this> = DummyCreateFrom
where
Self: 'this;
fn get_entity_view_creation_metadata(&self) -> Self::EntityViewMetadata {
GlweCiphertextListCreationMetadata(
self.output_glwe_size,
self.polynomial_size,
self.ciphertext_modulus,
)
}
fn get_entity_view_pod_size(&self) -> usize {
self.input_key_element_encrypted_size() * self.output_glwe_size.to_glwe_dimension().0
}
/// Unimplemented for [`LweTracePackingKeyswitchKey`]. At the moment it does not
/// make sense to return "sub" packing keyswitch keys.
fn get_self_view_creation_metadata(&self) -> Self::SelfViewMetadata {
unimplemented!(
"This function is not supported for LweTracePackingKeyswitchKey. \
At the moment it does not make sense to return 'sub' packing keyswitch keys."
)
}
}
impl<Scalar: UnsignedInteger, C: ContainerMut<Element = Scalar>> ContiguousEntityContainerMut
for LweTracePackingKeyswitchKey<C>
{
type EntityMutView<'this> = GlweCiphertextListMutView<'this, Self::Element>
where
Self: 'this;
// At the moment it does not make sense to return "sub" packing keyswitch keys. So we use a
// dummy placeholder type here.
type SelfMutView<'this> = DummyCreateFrom
where
Self: 'this;
}
/// Metadata used in the [`CreateFrom`] implementation to create
/// [`LweTracePackingKeyswitchKey`] entities.
#[derive(Clone, Copy)]
pub struct LweTracePackingKeyswitchKeyCreationMetadata<Scalar: UnsignedInteger>(
pub DecompositionBaseLog,
pub DecompositionLevelCount,
pub LweSize,
pub GlweSize,
pub PolynomialSize,
pub CiphertextModulus<Scalar>,
);
impl<Scalar: UnsignedInteger, C: Container<Element = Scalar>> CreateFrom<C>
for LweTracePackingKeyswitchKey<C>
{
type Metadata = LweTracePackingKeyswitchKeyCreationMetadata<Scalar>;
#[inline]
fn create_from(from: C, meta: Self::Metadata) -> LweTracePackingKeyswitchKey<C> {
let LweTracePackingKeyswitchKeyCreationMetadata(
decomp_base_log,
decomp_level_count,
input_lwe_size,
output_glwe_size,
polynomial_size,
ciphertext_modulus,
) = meta;
LweTracePackingKeyswitchKey::from_container(
from,
decomp_base_log,
decomp_level_count,
input_lwe_size,
output_glwe_size,
polynomial_size,
ciphertext_modulus,
)
}
}

View File

@@ -8,6 +8,8 @@ pub mod ggsw_ciphertext;
pub mod ggsw_ciphertext_list;
pub mod glwe_ciphertext;
pub mod glwe_ciphertext_list;
pub mod glwe_keyswitch_key;
pub mod glwe_relinearisation_key;
pub mod glwe_secret_key;
pub mod gsw_ciphertext;
pub mod lwe_bootstrap_key;
@@ -17,8 +19,11 @@ pub mod lwe_keyswitch_key;
pub mod lwe_multi_bit_bootstrap_key;
pub mod lwe_private_functional_packing_keyswitch_key;
pub mod lwe_private_functional_packing_keyswitch_key_list;
pub mod lwe_public_functional_packing_keyswitch_key;
pub mod lwe_public_functional_packing_keyswitch_key_list;
pub mod lwe_public_key;
pub mod lwe_secret_key;
pub mod lwe_trace_packing_keyswitch_key;
pub mod plaintext;
pub mod plaintext_list;
pub mod polynomial;
@@ -27,6 +32,7 @@ pub mod seeded_ggsw_ciphertext;
pub mod seeded_ggsw_ciphertext_list;
pub mod seeded_glwe_ciphertext;
pub mod seeded_glwe_ciphertext_list;
pub mod seeded_glwe_keyswitch_key;
pub mod seeded_lwe_bootstrap_key;
pub mod seeded_lwe_ciphertext;
pub mod seeded_lwe_ciphertext_list;
@@ -51,6 +57,8 @@ pub use ggsw_ciphertext::*;
pub use ggsw_ciphertext_list::*;
pub use glwe_ciphertext::*;
pub use glwe_ciphertext_list::*;
pub use glwe_keyswitch_key::*;
pub use glwe_relinearisation_key::*;
pub use glwe_secret_key::*;
pub use gsw_ciphertext::*;
pub use lwe_bootstrap_key::*;
@@ -60,8 +68,11 @@ pub use lwe_keyswitch_key::*;
pub use lwe_multi_bit_bootstrap_key::*;
pub use lwe_private_functional_packing_keyswitch_key::*;
pub use lwe_private_functional_packing_keyswitch_key_list::*;
pub use lwe_public_functional_packing_keyswitch_key::*;
pub use lwe_public_functional_packing_keyswitch_key_list::*;
pub use lwe_public_key::*;
pub use lwe_secret_key::*;
pub use lwe_trace_packing_keyswitch_key::*;
pub use plaintext::*;
pub use plaintext_list::*;
pub use polynomial::*;
@@ -70,6 +81,7 @@ pub use seeded_ggsw_ciphertext::*;
pub use seeded_ggsw_ciphertext_list::*;
pub use seeded_glwe_ciphertext::*;
pub use seeded_glwe_ciphertext_list::*;
pub use seeded_glwe_keyswitch_key::*;
pub use seeded_lwe_bootstrap_key::*;
pub use seeded_lwe_ciphertext::*;
pub use seeded_lwe_ciphertext_list::*;

View File

@@ -0,0 +1,379 @@
/*
//! Module containing the definition of the SeededGlweKeyswitchKey.
use crate::core_crypto::algorithms::*;
use crate::core_crypto::commons::math::random::{ActivatedRandomGenerator, CompressionSeed};
use crate::core_crypto::commons::parameters::*;
use crate::core_crypto::commons::traits::*;
use crate::core_crypto::entities::*;
/// A [`seeded GLWE keyswitch key`](`SeededGlweKeyswitchKey`).
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct SeededGlweKeyswitchKey<C: Container> {
data: C,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
output_glwe_size: GlweSize,
poly_size: PolynomialSize,
compression_seed: CompressionSeed,
}
impl<T, C: Container<Element = T>> AsRef<[T]> for SeededGlweKeyswitchKey<C> {
fn as_ref(&self) -> &[T] {
self.data.as_ref()
}
}
impl<T, C: ContainerMut<Element = T>> AsMut<[T]> for SeededGlweKeyswitchKey<C> {
fn as_mut(&mut self) -> &mut [T] {
self.data.as_mut()
}
}
/// Return the number of elements in an encryption of an input [`GlweSecretKey`] element for a
/// [`SeededGlweKeyswitchKey`] given a [`DecompositionLevelCount`] and output [`GlweSize`].
pub fn seeded_glwe_keyswitch_key_input_key_element_encrypted_size(
decomp_level_count: DecompositionLevelCount,
) -> usize {
// One seeded ciphertext per level
decomp_level_count.0
}
impl<Scalar, C: Container<Element = Scalar>> SeededGlweKeyswitchKey<C> {
/// Create an [`SeededGlweKeyswitchKey`] from an existing container.
///
/// # Note
///
/// This function only wraps a container in the appropriate type. If you want to generate an
/// [`SeededGlweKeyswitchKey`] you need to call
/// [`crate::core_crypto::algorithms::generate_seeded_glwe_keyswitch_key`] using this key as
/// output.
///
/// This docstring exhibits [`SeededGlweKeyswitchKey`] primitives usage.
///
/// ```
/// use tfhe::core_crypto::prelude::*;
///
/// // DISCLAIMER: these toy example parameters are not guaranteed to be secure or yield correct
/// // computations
/// // Define parameters for SeededLweKeyswitchKey creation
/// let input_lwe_dimension = LweDimension(600);
/// let output_lwe_dimension = LweDimension(1024);
/// let decomp_base_log = DecompositionBaseLog(4);
/// let decomp_level_count = DecompositionLevelCount(5);
///
/// // Get a seeder
/// let mut seeder = new_seeder();
/// let seeder = seeder.as_mut();
///
/// // Create a new SeededLweKeyswitchKey
/// let lwe_ksk = SeededLweKeyswitchKey::new(
/// 0u64,
/// decomp_base_log,
/// decomp_level_count,
/// input_lwe_dimension,
/// output_lwe_dimension,
/// seeder.seed().into(),
/// );
///
/// assert_eq!(lwe_ksk.decomposition_base_log(), decomp_base_log);
/// assert_eq!(lwe_ksk.decomposition_level_count(), decomp_level_count);
/// assert_eq!(lwe_ksk.input_key_lwe_dimension(), input_lwe_dimension);
/// assert_eq!(lwe_ksk.output_key_lwe_dimension(), output_lwe_dimension);
/// assert_eq!(
/// lwe_ksk.output_lwe_size(),
/// output_lwe_dimension.to_lwe_size()
/// );
///
/// let compression_seed = lwe_ksk.compression_seed();
///
/// // Demonstrate how to recover the allocated container
/// let underlying_container: Vec<u64> = lwe_ksk.into_container();
///
/// // Recreate a secret key using from_container
/// let lwe_ksk = SeededLweKeyswitchKey::from_container(
/// underlying_container,
/// decomp_base_log,
/// decomp_level_count,
/// output_lwe_dimension.to_lwe_size(),
/// compression_seed,
/// );
///
/// assert_eq!(lwe_ksk.decomposition_base_log(), decomp_base_log);
/// assert_eq!(lwe_ksk.decomposition_level_count(), decomp_level_count);
/// assert_eq!(lwe_ksk.input_key_lwe_dimension(), input_lwe_dimension);
/// assert_eq!(lwe_ksk.output_key_lwe_dimension(), output_lwe_dimension);
/// assert_eq!(
/// lwe_ksk.output_lwe_size(),
/// output_lwe_dimension.to_lwe_size()
/// );
///
/// let lwe_ksk = lwe_ksk.decompress_into_lwe_keyswitch_key();
///
/// assert_eq!(lwe_ksk.decomposition_base_log(), decomp_base_log);
/// assert_eq!(lwe_ksk.decomposition_level_count(), decomp_level_count);
/// assert_eq!(lwe_ksk.input_key_lwe_dimension(), input_lwe_dimension);
/// assert_eq!(lwe_ksk.output_key_lwe_dimension(), output_lwe_dimension);
/// assert_eq!(
/// lwe_ksk.output_lwe_size(),
/// output_lwe_dimension.to_lwe_size()
/// );
/// ```
pub fn from_container(
container: C,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
output_glwe_size: GlweSize,
poly_size: PolynomialSize,
compression_seed: CompressionSeed,
) -> Self {
assert!(
container.container_len() > 0,
"Got an empty container to create an SeededLweKeyswitchKey"
);
assert!(
container.container_len() % (decomp_level_count.0) == 0,
"The provided container length is not valid. \
It needs to be dividable by decomp_level_count: {}. \
Got container length: {} and decomp_level_count: {decomp_level_count:?}.",
decomp_level_count.0,
container.container_len()
);
SeededGlweKeyswitchKey {
data: container,
decomp_base_log,
decomp_level_count,
output_glwe_size,
poly_size,
compression_seed,
}
}
/// Return the [`DecompositionBaseLog`] of the [`SeededGlweKeyswitchKey`].
///
/// See [`SeededGlweKeyswitchKey::from_container`] for usage.
pub fn decomposition_base_log(&self) -> DecompositionBaseLog {
self.decomp_base_log
}
/// Return the [`DecompositionLevelCount`] of the [`SeededGlweKeyswitchKey`].
///
/// See [`SeededGlweKeyswitchKey::from_container`] for usage.
pub fn decomposition_level_count(&self) -> DecompositionLevelCount {
self.decomp_level_count
}
/// Return the input [`GlweDimension`] of the [`SeededGlweKeyswitchKey`].
///
/// See [`SeededGlweKeyswitchKey::from_container`] for usage.
pub fn input_key_glwe_dimension(&self) -> GlweDimension {
GlweDimension(self.data.container_len() / (self.seeded_input_key_element_encrypted_size()
* self.input_poly_size))
}
/// Return the input [`PolynomialSize`] of the [`SeededGlweKeyswitchKey`].
///
/// See [`SeededGlweKeyswitchKey::from_container`] for usage.
pub fn polynomial_size(&self) -> PolynomialSize {
self.poly_size
}
/// Return the output [`GlweDimension`] of the [`SeededGlweKeyswitchKey`].
///
/// See [`SeededGlweKeyswitchKey::from_container`] for usage.
pub fn output_key_glwe_dimension(&self) -> GlweDimension {
self.output_glwe_size.to_glwe_dimension()
}
/// Return the output [`GlweSize`] of the [`SeededGlweKeyswitchKey`].
///
/// See [`SeededGlweKeyswitchKey::from_container`] for usage.
pub fn output_glwe_size(&self) -> GlweSize {
self.output_glwe_size
}
/// Return the output [`CompressionSeed`] of the [`SeededGlweKeyswitchKey`].
///
/// See [`SeededGlweKeyswitchKey::from_container`] for usage.
pub fn compression_seed(&self) -> CompressionSeed {
self.compression_seed
}
/// Return the number of elements in an encryption of an input [`GlweSecretKey`] element of the
/// current [`SeededGlweKeyswitchKey`].
pub fn seeded_input_key_element_encrypted_size(&self) -> usize {
seeded_glwe_keyswitch_key_input_key_element_encrypted_size(self.decomp_level_count)
}
/// Return a view of the [`SeededGlweKeyswitchKey`]. This is useful if an algorithm takes a view
/// by value.
pub fn as_view(&self) -> SeededGlweKeyswitchKey<&'_ [Scalar]> {
SeededGlweKeyswitchKey::from_container(
self.as_ref(),
self.decomp_base_log,
self.decomp_level_count,
self.output_glwe_size,
self.poly_size,
self.compression_seed,
)
}
/// Consume the entity and return its underlying container.
///
/// See [`SeededGlweKeyswitchKey::from_container`] for usage.
pub fn into_container(self) -> C {
self.data
}
/// Consume the [`SeededGlweKeyswitchKey`] and decompress it into a standard
/// [`GlweKeyswitchKey`].
///
/// See [`SeededGlweKeyswitchKey::from_container`] for usage.
pub fn decompress_into_glwe_keyswitch_key(self) -> GlweKeyswitchKeyOwned<Scalar>
where
Scalar: UnsignedTorus,
{
let mut decompressed_ksk = GlweKeyswitchKeyOwned::new(
Scalar::ZERO,
self.decomposition_base_log(),
self.decomposition_level_count(),
self.input_key_glwe_dimension(),
self.output_key_glwe_dimension(),
self.polynomial_size(),
);
decompress_seeded_glwe_keyswitch_key::<_, _, _, ActivatedRandomGenerator>(
&mut decompressed_ksk,
&self,
);
decompressed_ksk
}
pub fn as_seeded_glwe_ciphertext_list(&self) -> SeededGlweCiphertextListView<'_, Scalar> {
SeededGlweCiphertextListView::from_container(
self.as_ref(),
self.output_glwe_size(),
self.polynomial_size(),
self.compression_seed(),
)
}
}
impl<Scalar, C: ContainerMut<Element = Scalar>> SeededGlweKeyswitchKey<C> {
/// Mutable variant of [`SeededGlweKeyswitchKey::as_view`].
pub fn as_mut_view(&mut self) -> SeededGlweKeyswitchKey<&'_ mut [Scalar]> {
let decomp_base_log = self.decomp_base_log;
let decomp_level_count = self.decomp_level_count;
let output_glwe_size = self.output_glwe_size;
let poly_size = self.poly_size;
let compression_seed = self.compression_seed;
SeededGlweKeyswitchKey::from_container(
self.as_mut(),
decomp_base_log,
decomp_level_count,
output_glwe_size,
poly_size,
compression_seed,
)
}
pub fn as_mut_seeded_glwe_ciphertext_list(
&mut self,
) -> SeededGlweCiphertextListMutView<'_, Scalar> {
let output_glwe_size = self.output_glwe_size();
let poly_size = self.polynomial_size();
let compression_seed = self.compression_seed();
SeededGlweCiphertextListMutView::from_container(
self.as_mut(),
output_glwe_size,
poly_size,
compression_seed,
)
}
}
/// A [`SeededGlweKeyswitchKey`] owning the memory for its own storage.
pub type SeededGlweKeyswitchKeyOwned<Scalar> = SeededGlweKeyswitchKey<Vec<Scalar>>;
impl<Scalar: Copy> SeededGlweKeyswitchKeyOwned<Scalar> {
/// Allocate memory and create a new owned [`SeededGlweKeyswitchKey`].
///
/// # Note
///
/// This function allocates a vector of the appropriate size and wraps it in the appropriate
/// type. If you want to generate an [`SeededGlweKeyswitchKey`] you need to call
/// [`crate::core_crypto::algorithms::generate_seeded_glwe_keyswitch_key`] using this key as
/// output.
///
/// See [`SeededGlweKeyswitchKey::from_container`] for usage.
pub fn new(
fill_with: Scalar,
decomp_base_log: DecompositionBaseLog,
decomp_level_count: DecompositionLevelCount,
input_key_glwe_dimension: GlweDimension,
output_key_glwe_dimension: GlweDimension,
polynomial_size: PolynomialSize,
compression_seed: CompressionSeed,
) -> SeededGlweKeyswitchKeyOwned<Scalar> {
SeededGlweKeyswitchKeyOwned::from_container(
vec![
fill_with;
input_key_glwe_dimension.0 * polynomial_size.0
* seeded_lwe_keyswitch_key_input_key_element_encrypted_size(decomp_level_count,)
],
decomp_base_log,
decomp_level_count,
output_key_glwe_dimension.to_glwe_size(),
polynomial_size,
compression_seed,
)
}
}
impl<C: Container> ContiguousEntityContainer for SeededGlweKeyswitchKey<C> {
type Element = C::Element;
type EntityViewMetadata = SeededGlweCiphertextListCreationMetadata;
type EntityView<'this> = SeededGlweCiphertextListView<'this, Self::Element>
where
Self: 'this;
type SelfViewMetadata = ();
// At the moment it does not make sense to return "sub" keyswitch keys. So we use a dummy
// placeholder type here.
type SelfView<'this> = DummyCreateFrom
where
Self: 'this;
fn get_entity_view_creation_metadata(&self) -> SeededGlweCiphertextListCreationMetadata {
SeededGlweCiphertextListCreationMetadata(self.output_glwe_size(), self.compression_seed())
}
fn get_entity_view_pod_size(&self) -> usize {
self.seeded_input_key_element_encrypted_size()
}
/// Unimplemented for [`SeededGlweKeyswitchKey`]. At the moment it does not make sense to
/// return "sub" keyswitch keys.
fn get_self_view_creation_metadata(&self) {
unimplemented!(
"This function is not supported for SeededLweKeyswitchKey. \
At the moment it does not make sense to return 'sub' keyswitch keys."
)
}
}
impl<C: ContainerMut> ContiguousEntityContainerMut for SeededGlweKeyswitchKey<C> {
type EntityMutView<'this> = SeededGlweCiphertextListMutView<'this, Self::Element>
where
Self: 'this;
// At the moment it does not make sense to return "sub" keyswitch keys. So we use a dummy
// placeholder type here.
type SelfMutView<'this> = DummyCreateFrom
where
Self: 'this;
}
*/

View File

@@ -324,7 +324,7 @@ where
// empty, this usage of the signed decomposer allows to round while
// keeping the data in the MSBs
let signed_decomposer = SignedDecomposer::new(
DecompositionBaseLog(ciphertext_modulus.get().ilog2() as usize),
DecompositionBaseLog(ciphertext_modulus.get_custom_modulus().ilog2() as usize),
DecompositionLevelCount(1),
);
ct0.as_mut()

View File

@@ -124,7 +124,7 @@ fn f128_floor(x: f128) -> f128 {
let f128(x0, x1) = x;
let x0_floor = x0.floor();
if x0_floor == x0 {
f128(x0_floor, x1.floor())
f128::add_f64_f64(x0_floor, x1.floor())
} else {
f128(x0_floor, 0.0)
}
@@ -225,7 +225,7 @@ fn u128_to_signed_to_f128(x: u128) -> f128 {
#[inline(always)]
fn u128_from_torus_f128(x: f128) -> u128 {
let mut x = x - f128_floor(x);
let mut x = f128::sub_estimate_f128_f128(x, f128_floor(x));
let normalization = 2.0f64.powi(128);
x.0 *= normalization;

View File

@@ -166,7 +166,7 @@ where
)
}
pub(crate) fn bootstrap_u128<ContLweOut, ContLweIn, ContAcc>(
pub fn bootstrap_u128<ContLweOut, ContLweIn, ContAcc>(
&self,
lwe_out: &mut LweCiphertext<ContLweOut>,
lwe_in: &LweCiphertext<ContLweIn>,
@@ -237,7 +237,7 @@ where
// empty, this usage of the signed decomposer allows to round while
// keeping the data in the MSBs
let signed_decomposer = SignedDecomposer::new(
DecompositionBaseLog(ciphertext_modulus.get().ilog2() as usize),
DecompositionBaseLog(ciphertext_modulus.get_custom_modulus().ilog2() as usize),
DecompositionLevelCount(1),
);
local_accumulator

View File

@@ -1,2 +1,5 @@
pub mod bootstrap;
pub mod ggsw;
#[cfg(test)]
mod tests;

View File

@@ -0,0 +1,279 @@
use dyn_stack::{GlobalPodBuffer, PodStack, ReborrowMut};
use super::super::super::{fft128, fft128_u128};
use super::super::math::fft::{Fft128, Fft128View};
use crate::core_crypto::prelude::*;
use aligned_vec::CACHELINE_ALIGN;
fn sqr(x: f64) -> f64 {
x * x
}
#[test]
fn test_split_external_product() {
let small_lwe_dimension = LweDimension(742);
let glwe_dimension = GlweDimension(1);
let polynomial_size = PolynomialSize(2048);
let pbs_base_log = DecompositionBaseLog(23);
let pbs_level = DecompositionLevelCount(1);
let glwe_modular_std_dev = StandardDev(sqr(0.00000000000000029403601535432533));
let ciphertext_modulus = CiphertextModulus::<u128>::new_native();
let ciphertext_modulus_split = CiphertextModulus::<u64>::new_native();
let mut glwe = GlweCiphertext::new(
0u128,
glwe_dimension.to_glwe_size(),
polynomial_size,
ciphertext_modulus,
);
for x in glwe.as_mut() {
*x = rand::random();
}
let mut boxed_seeder = new_seeder();
let seeder = boxed_seeder.as_mut();
let mut secret_generator =
SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
let mut encryption_generator =
EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
let small_lwe_sk =
LweSecretKey::generate_new_binary(small_lwe_dimension, &mut secret_generator);
let glwe_sk =
GlweSecretKey::generate_new_binary(glwe_dimension, polynomial_size, &mut secret_generator);
let std_bootstrapping_key = par_allocate_and_generate_new_lwe_bootstrap_key(
&small_lwe_sk,
&glwe_sk,
pbs_base_log,
pbs_level,
glwe_modular_std_dev,
ciphertext_modulus,
&mut encryption_generator,
);
let mut fourier_bsk = Fourier128LweBootstrapKey::new(
std_bootstrapping_key.input_lwe_dimension(),
std_bootstrapping_key.glwe_size(),
std_bootstrapping_key.polynomial_size(),
std_bootstrapping_key.decomposition_base_log(),
std_bootstrapping_key.decomposition_level_count(),
);
let fft = Fft128::new(polynomial_size);
let fft = fft.as_view();
fourier_bsk.fill_with_forward_fourier(&std_bootstrapping_key, fft);
let ggsw = fourier_bsk.as_view().into_ggsw_iter().next().unwrap();
let mut glwe_lo = GlweCiphertext::new(
0u64,
glwe_dimension.to_glwe_size(),
polynomial_size,
ciphertext_modulus_split,
);
let mut glwe_hi = GlweCiphertext::new(
0u64,
glwe_dimension.to_glwe_size(),
polynomial_size,
ciphertext_modulus_split,
);
for ((lo, hi), val) in glwe_lo
.as_mut()
.iter_mut()
.zip(glwe_hi.as_mut())
.zip(glwe.as_ref())
{
*lo = *val as u64;
*hi = (*val >> 64) as u64;
}
let mut out = GlweCiphertext::new(
0u128,
glwe_dimension.to_glwe_size(),
polynomial_size,
ciphertext_modulus,
);
fft128::crypto::ggsw::add_external_product_assign(
&mut out,
&ggsw,
&glwe,
fft,
PodStack::new(&mut GlobalPodBuffer::new(
fft128::crypto::ggsw::add_external_product_assign_scratch::<u128>(
glwe_dimension.to_glwe_size(),
polynomial_size,
fft,
)
.unwrap(),
)),
);
let mut out_lo = GlweCiphertext::new(
0u64,
glwe_dimension.to_glwe_size(),
polynomial_size,
ciphertext_modulus_split,
);
let mut out_hi = GlweCiphertext::new(
0u64,
glwe_dimension.to_glwe_size(),
polynomial_size,
ciphertext_modulus_split,
);
fft128_u128::crypto::ggsw::add_external_product_assign_split(
&mut out_lo,
&mut out_hi,
&ggsw,
&glwe_lo,
&glwe_hi,
fft,
PodStack::new(&mut GlobalPodBuffer::new(
fft128::crypto::ggsw::add_external_product_assign_scratch::<u128>(
glwe_dimension.to_glwe_size(),
polynomial_size,
fft,
)
.unwrap(),
)),
);
for ((lo, hi), val) in out_lo
.as_ref()
.iter()
.zip(out_hi.as_ref())
.zip(out.as_ref())
{
assert_eq!(*val as u64, *lo);
assert_eq!((*val >> 64) as u64, *hi);
}
}
#[test]
fn test_split_pbs() {
let small_lwe_dimension = LweDimension(742);
let glwe_dimension = GlweDimension(1);
let polynomial_size = PolynomialSize(2048);
let pbs_base_log = DecompositionBaseLog(23);
let pbs_level = DecompositionLevelCount(1);
let glwe_modular_std_dev = StandardDev(sqr(0.00000000000000029403601535432533));
let ciphertext_modulus = CiphertextModulus::<u128>::new_native();
let mut boxed_seeder = new_seeder();
let seeder = boxed_seeder.as_mut();
let mut secret_generator =
SecretRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed());
let mut encryption_generator =
EncryptionRandomGenerator::<ActivatedRandomGenerator>::new(seeder.seed(), seeder);
let small_lwe_sk =
LweSecretKey::generate_new_binary(small_lwe_dimension, &mut secret_generator);
let glwe_sk =
GlweSecretKey::generate_new_binary(glwe_dimension, polynomial_size, &mut secret_generator);
let std_bootstrapping_key = par_allocate_and_generate_new_lwe_bootstrap_key(
&small_lwe_sk,
&glwe_sk,
pbs_base_log,
pbs_level,
glwe_modular_std_dev,
ciphertext_modulus,
&mut encryption_generator,
);
let mut fourier_bsk = Fourier128LweBootstrapKey::new(
std_bootstrapping_key.input_lwe_dimension(),
std_bootstrapping_key.glwe_size(),
std_bootstrapping_key.polynomial_size(),
std_bootstrapping_key.decomposition_base_log(),
std_bootstrapping_key.decomposition_level_count(),
);
let fft = Fft128::new(polynomial_size);
let fft = fft.as_view();
fourier_bsk.fill_with_forward_fourier(&std_bootstrapping_key, fft);
let mut lwe_in =
LweCiphertext::new(0u128, small_lwe_dimension.to_lwe_size(), ciphertext_modulus);
let mut accumulator = GlweCiphertext::new(
0u128,
glwe_dimension.to_glwe_size(),
polynomial_size,
ciphertext_modulus,
);
for x in lwe_in.as_mut() {
*x = rand::random();
}
for x in accumulator.as_mut() {
*x = rand::random();
}
let mut mem = GlobalPodBuffer::new(
fft128::crypto::bootstrap::bootstrap_scratch::<u128>(
glwe_dimension.to_glwe_size(),
polynomial_size,
fft,
)
.unwrap(),
);
let mut stack = PodStack::new(&mut mem);
let mut lwe_out_non_split = LweCiphertext::new(
0u128,
LweDimension(polynomial_size.0 * glwe_dimension.0).to_lwe_size(),
ciphertext_modulus,
);
// Needed as the basic bootstrap function dispatches to the more efficient split version for
// u128
fn bootstrap_non_split<Scalar: UnsignedTorus + CastInto<usize>>(
this: Fourier128LweBootstrapKey<&[f64]>,
mut lwe_out: LweCiphertext<&mut [Scalar]>,
lwe_in: LweCiphertext<&[Scalar]>,
accumulator: GlweCiphertext<&[Scalar]>,
fft: Fft128View<'_>,
stack: PodStack<'_>,
) {
let (mut local_accumulator_data, stack) =
stack.collect_aligned(CACHELINE_ALIGN, accumulator.as_ref().iter().copied());
let mut local_accumulator = GlweCiphertextMutView::from_container(
&mut *local_accumulator_data,
accumulator.polynomial_size(),
accumulator.ciphertext_modulus(),
);
this.blind_rotate_assign(&mut local_accumulator.as_mut_view(), &lwe_in, fft, stack);
extract_lwe_sample_from_glwe_ciphertext(
&local_accumulator,
&mut lwe_out,
MonomialDegree(0),
);
}
bootstrap_non_split(
fourier_bsk.as_view(),
lwe_out_non_split.as_mut_view(),
lwe_in.as_view(),
accumulator.as_view(),
fft,
stack.rb_mut(),
);
let mut lwe_out_split = LweCiphertext::new(
0u128,
LweDimension(polynomial_size.0 * glwe_dimension.0).to_lwe_size(),
ciphertext_modulus,
);
fourier_bsk.bootstrap_u128(
&mut lwe_out_split,
&lwe_in,
&accumulator,
fft,
stack.rb_mut(),
);
assert_eq!(lwe_out_split, lwe_out_non_split);
}

View File

@@ -206,10 +206,11 @@ pub fn wrapping_add_avx2(
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[inline(always)]
pub fn wrapping_neg_avx2(simd: V3, (lo, hi): (u64x4, u64x4)) -> (u64x4, u64x4) {
let diff_lo = pulp::cast(simd.wrapping_sub_u64x4(simd.splat_u64x4(0), lo));
let overflow = pulp::cast(simd.cmp_lt_u64x4(simd.splat_u64x4(0), lo));
let diff_hi = simd.wrapping_sub_u64x4(overflow, hi);
(diff_lo, diff_hi)
wrapping_add_avx2(
simd,
(simd.splat_u64x4(1), simd.splat_u64x4(0)),
(simd.not_u64x4(lo), simd.not_u64x4(hi)),
)
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
@@ -246,10 +247,11 @@ pub fn wrapping_add_avx512(
#[cfg(feature = "nightly-avx512")]
#[inline(always)]
pub fn wrapping_neg_avx512(simd: V4, (lo, hi): (u64x8, u64x8)) -> (u64x8, u64x8) {
let diff_lo = simd.wrapping_sub_u64x8(simd.splat_u64x8(0), lo);
let overflow = simd.convert_mask_b8_to_u64x8(simd.cmp_lt_u64x8(simd.splat_u64x8(0), lo));
let diff_hi = simd.wrapping_sub_u64x8(overflow, hi);
(diff_lo, diff_hi)
wrapping_add_avx512(
simd,
(simd.splat_u64x8(1), simd.splat_u64x8(0)),
(simd.not_u64x8(lo), simd.not_u64x8(hi)),
)
}
#[inline(always)]
@@ -385,9 +387,10 @@ fn f64_to_i128_avx2(simd: V3, f: f64x4) -> (u64x4, u64x4) {
simd.shr_dyn_u64x4(hi, shift),
);
let neg = wrapping_neg_avx2(simd, abs);
let mask = simd.cmp_eq_u64x4(simd.and_u64x4(sign_bit, f), sign_bit);
(
simd.select_u64x4(simd.cmp_eq_u64x4(f, sign_bit), neg.0, abs.0),
simd.select_u64x4(simd.cmp_eq_u64x4(f, sign_bit), neg.1, abs.1),
simd.select_u64x4(mask, neg.0, abs.0),
simd.select_u64x4(mask, neg.1, abs.1),
)
};
(
@@ -554,7 +557,7 @@ fn f128_floor(x: f128) -> f128 {
let f128(x0, x1) = x;
let x0_floor = x0.floor();
if x0_floor == x0 {
f128(x0_floor, x1.floor())
f128::add_f64_f64(x0_floor, x1.floor())
} else {
f128(x0_floor, 0.0)
}
@@ -566,7 +569,8 @@ fn f128_floor_avx2(simd: V3, (x0, x1): (f64x4, f64x4)) -> (f64x4, f64x4) {
let x0_floor = simd.floor_f64x4(x0);
let x1_floor = simd.floor_f64x4(x1);
(
two_sum_f64x4(
simd,
x0_floor,
simd.select_f64x4(
simd.cmp_eq_f64x4(x0_floor, x0),
@@ -583,7 +587,8 @@ fn f128_floor_avx512(simd: V4, (x0, x1): (f64x8, f64x8)) -> (f64x8, f64x8) {
let x0_floor = simd.floor_f64x8(x0);
let x1_floor = simd.floor_f64x8(x1);
(
two_sum_f64x8(
simd,
x0_floor,
simd.select_f64x8(
simd.cmp_eq_f64x8(x0_floor, x0),
@@ -1338,3 +1343,43 @@ impl<'a> Fft128View<'a> {
);
}
}
#[cfg(test)]
mod tests {
use super::*;
// copied from the standard library
fn next_up(this: f64) -> f64 {
// We must use strictly integer arithmetic to prevent denormals from
// flushing to zero after an arithmetic operation on some platforms.
const TINY_BITS: u64 = 0x1; // Smallest positive f64.
const CLEAR_SIGN_MASK: u64 = 0x7fff_ffff_ffff_ffff;
let bits = this.to_bits();
if this.is_nan() || bits == f64::INFINITY.to_bits() {
return this;
}
let abs = bits & CLEAR_SIGN_MASK;
let next_bits = if abs == 0 {
TINY_BITS
} else if bits == abs {
bits + 1
} else {
bits - 1
};
f64::from_bits(next_bits)
}
fn ulp(x: f64) -> f64 {
next_up(x.abs()) - x.abs()
}
#[test]
fn test_f128_floor() {
let a = f128(-11984547.0, -1.0316078675142442e-10);
let b = f128_floor(a);
assert!(b.1.abs() <= 0.5 * ulp(b.0))
}
}

View File

@@ -283,7 +283,7 @@ impl<'a> FourierLweBootstrapKeyView<'a> {
// fill the MSBs and leave the LSBs empty, this usage of the signed decomposer allows to
// round while keeping the data in the MSBs
let signed_decomposer = SignedDecomposer::new(
DecompositionBaseLog(ciphertext_modulus.get().ilog2() as usize),
DecompositionBaseLog(ciphertext_modulus.get_custom_modulus().ilog2() as usize),
DecompositionLevelCount(1),
);
ct0.as_mut()

View File

@@ -4,7 +4,7 @@
//! Having `tfhe::core_crypto::prelude::*;` should be enough to start using the lib.
pub use super::algorithms::{
add_external_product_assign, polynomial_algorithms, slice_algorithms, *,
polynomial_algorithms, slice_algorithms, *,
};
pub use super::commons::computation_buffers::ComputationBuffers;
pub use super::commons::dispersion::*;

View File

@@ -190,3 +190,14 @@ fn test_compressed_bool() {
assert_eq!(a.decrypt(&keys), true);
assert_eq!(b.decrypt(&keys), false);
}
#[test]
fn test_trivial_bool() {
let keys = setup_static_default();
let a = FheBool::encrypt_trivial(true);
let b = FheBool::encrypt_trivial(false);
assert_eq!(a.decrypt(&keys), true);
assert_eq!(b.decrypt(&keys), false);
}

View File

@@ -13,7 +13,7 @@ use crate::high_level_api::keys::{
ClientKey, PublicKey, RefKeyFromKeyChain, RefKeyFromPublicKeyChain,
};
use crate::high_level_api::traits::{
FheDecrypt, FheEncrypt, FheEq, FheTrivialEncrypt, FheTryEncrypt, FheTryTrivialEncrypt,
FheDecrypt, FheEq, FheTrivialEncrypt, FheTryEncrypt, FheTryTrivialEncrypt,
};
/// The FHE boolean data type.
@@ -151,39 +151,6 @@ where
}
}
impl<P> FheEncrypt<bool, ClientKey> for CompressedBool<P>
where
P: BooleanParameterSet,
P::Id: RefKeyFromKeyChain<Key = GenericBoolClientKey<P>> + Default,
{
#[track_caller]
fn encrypt(value: bool, key: &ClientKey) -> Self {
Self::try_encrypt(value, key).unwrap()
}
}
impl<P> FheEncrypt<bool, ClientKey> for GenericBool<P>
where
P: BooleanParameterSet,
P::Id: RefKeyFromKeyChain<Key = GenericBoolClientKey<P>> + Default,
{
#[track_caller]
fn encrypt(value: bool, key: &ClientKey) -> Self {
<Self as FheTryEncrypt<bool, ClientKey>>::try_encrypt(value, key).unwrap()
}
}
impl<P> FheEncrypt<bool, PublicKey> for GenericBool<P>
where
P: BooleanParameterSet,
P::Id: RefKeyFromPublicKeyChain<Key = GenericBoolPublicKey<P>> + Default,
{
#[track_caller]
fn encrypt(value: bool, key: &PublicKey) -> Self {
<Self as FheTryEncrypt<bool, PublicKey>>::try_encrypt(value, key).unwrap()
}
}
impl<P> FheTryEncrypt<bool, ClientKey> for GenericBool<P>
where
P: BooleanParameterSet,

View File

@@ -6,14 +6,14 @@ use crate::high_level_api::integers::IntegerConfig;
use crate::high_level_api::shortints::ShortIntConfig;
/// The config type
#[derive(Clone, Debug)]
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct Config {
#[cfg(feature = "boolean")]
pub(crate) boolean_config: BooleanConfig,
#[cfg(feature = "integer")]
pub(crate) integer_config: IntegerConfig,
#[cfg(feature = "shortint")]
pub(crate) shortint_config: ShortIntConfig,
#[cfg(feature = "integer")]
pub(crate) integer_config: IntegerConfig,
}
/// The builder to create your config
@@ -49,10 +49,10 @@ impl ConfigBuilder {
config: Config {
#[cfg(feature = "boolean")]
boolean_config: BooleanConfig::all_default(),
#[cfg(feature = "integer")]
integer_config: IntegerConfig::all_default(),
#[cfg(feature = "shortint")]
shortint_config: ShortIntConfig::all_default(),
#[cfg(feature = "integer")]
integer_config: IntegerConfig::all_default(),
},
}
}
@@ -63,10 +63,10 @@ impl ConfigBuilder {
config: Config {
#[cfg(feature = "boolean")]
boolean_config: BooleanConfig::all_none(),
#[cfg(feature = "integer")]
integer_config: IntegerConfig::all_none(),
#[cfg(feature = "shortint")]
shortint_config: ShortIntConfig::all_none(),
#[cfg(feature = "integer")]
integer_config: IntegerConfig::all_none(),
},
}
}
@@ -108,155 +108,38 @@ impl ConfigBuilder {
}
#[cfg(feature = "integer")]
pub fn enable_default_uint8(mut self) -> Self {
self.config.integer_config.uint8_params = Some(Default::default());
pub fn enable_default_integers(mut self) -> Self {
self.config.integer_config = IntegerConfig::default_big();
self
}
#[cfg(feature = "integer")]
pub fn enable_default_uint8_small(mut self) -> Self {
let params = crate::high_level_api::integers::FheUint8Parameters::small();
self.config.integer_config.uint8_params = Some(params);
pub fn enable_default_integers_small(mut self) -> Self {
self.config.integer_config = IntegerConfig::default_small();
self
}
#[doc(hidden)]
#[cfg(feature = "integer")]
pub fn enable_function_evaluation_integers(mut self) -> Self {
self.config.integer_config.enable_wopbs();
self
}
#[cfg(feature = "integer")]
pub fn disable_uint8(mut self) -> Self {
self.config.integer_config.uint8_params = None;
pub fn enable_default_custom_integers(
mut self,
block_parameters: crate::shortint::PBSParameters,
wopbs_block_parameters: Option<crate::shortint::WopbsParameters>,
) -> Self {
self.config.integer_config =
IntegerConfig::new(Some(block_parameters), wopbs_block_parameters);
self
}
#[cfg(feature = "integer")]
pub fn enable_default_uint10(mut self) -> Self {
self.config.integer_config.uint10_params = Some(Default::default());
self
}
#[cfg(feature = "integer")]
pub fn enable_default_uint10_small(mut self) -> Self {
let params = crate::high_level_api::integers::FheUint10Parameters::small();
self.config.integer_config.uint10_params = Some(params);
self
}
#[cfg(feature = "integer")]
pub fn disable_uint10(mut self) -> Self {
self.config.integer_config.uint10_params = None;
self
}
#[cfg(feature = "integer")]
pub fn enable_default_uint12(mut self) -> Self {
self.config.integer_config.uint12_params = Some(Default::default());
self
}
#[cfg(feature = "integer")]
pub fn enable_default_uint12_small(mut self) -> Self {
let params = crate::high_level_api::integers::FheUint12Parameters::small();
self.config.integer_config.uint12_params = Some(params);
self
}
#[cfg(feature = "integer")]
pub fn disable_uint12(mut self) -> Self {
self.config.integer_config.uint12_params = None;
self
}
#[cfg(feature = "integer")]
pub fn enable_default_uint14(mut self) -> Self {
self.config.integer_config.uint14_params = Some(Default::default());
self
}
#[cfg(feature = "integer")]
pub fn enable_default_uint14_small(mut self) -> Self {
let params = crate::high_level_api::integers::FheUint14Parameters::small();
self.config.integer_config.uint14_params = Some(params);
self
}
#[cfg(feature = "integer")]
pub fn disable_uint14(mut self) -> Self {
self.config.integer_config.uint14_params = None;
self
}
#[cfg(feature = "integer")]
pub fn enable_default_uint16(mut self) -> Self {
self.config.integer_config.uint16_params = Some(Default::default());
self
}
#[cfg(feature = "integer")]
pub fn enable_default_uint16_small(mut self) -> Self {
let params = crate::high_level_api::integers::FheUint16Parameters::small();
self.config.integer_config.uint16_params = Some(params);
self
}
#[cfg(feature = "integer")]
pub fn disable_uint16(mut self) -> Self {
self.config.integer_config.uint16_params = None;
self
}
#[cfg(feature = "integer")]
pub fn enable_default_uint32(mut self) -> Self {
self.config.integer_config.uint32_params = Some(Default::default());
self
}
#[cfg(feature = "integer")]
pub fn enable_default_uint32_small(mut self) -> Self {
let params = crate::high_level_api::integers::FheUint32Parameters::small();
self.config.integer_config.uint32_params = Some(params);
self
}
#[cfg(feature = "integer")]
pub fn enable_default_uint64(mut self) -> Self {
self.config.integer_config.uint64_params = Some(Default::default());
self
}
#[cfg(feature = "integer")]
pub fn enable_default_uint64_small(mut self) -> Self {
let params = crate::high_level_api::integers::FheUint64Parameters::small();
self.config.integer_config.uint64_params = Some(params);
self
}
#[cfg(feature = "integer")]
pub fn enable_default_uint128(mut self) -> Self {
self.config.integer_config.uint128_params = Some(Default::default());
self
}
#[cfg(feature = "integer")]
pub fn enable_default_uint128_small(mut self) -> Self {
let params = crate::high_level_api::integers::FheUint128Parameters::small();
self.config.integer_config.uint128_params = Some(params);
self
}
#[cfg(feature = "integer")]
pub fn enable_default_uint256(mut self) -> Self {
self.config.integer_config.uint256_params = Some(Default::default());
self
}
#[cfg(feature = "integer")]
pub fn enable_default_uint256_small(mut self) -> Self {
let params = crate::high_level_api::integers::FheUint256Parameters::small();
self.config.integer_config.uint256_params = Some(params);
self
}
#[cfg(feature = "integer")]
pub fn disable_uint256(mut self) -> Self {
self.config.integer_config.uint256_params = None;
pub fn disable_integers(mut self) -> Self {
self.config.integer_config = IntegerConfig::all_none();
self
}

View File

@@ -65,7 +65,7 @@ In practice it is a bit more complex as we have to introduce traits to internall
"parameter struct", eg a trait to convert the `FheUint2Parameters` and `FheUint4Parameters` back into `shortint::Parameters`,
```rust
pub trait ShortIntegerParameter: Copy + Into<crate::shortint::Parameters> {
pub trait ShortIntegerParameter: Copy + Into< crate::shortint::PBSParameters> {
// ...
}
```

View File

@@ -23,7 +23,7 @@ macro_rules! define_key_structs {
///////////////////////
// Config
///////////////////////
#[derive(Clone, Debug)]
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub(crate) struct [<$base_struct_name Config>] {
$(
pub(crate) [<$name _params>]: Option<[<$base_ty_name Parameters>]>,
@@ -55,6 +55,7 @@ macro_rules! define_key_structs {
pub(crate) struct [<$base_struct_name ClientKey>] {
$(
pub(super) [<$name _key>]: Option<[<$base_ty_name ClientKey>]>,
pub(super) [<$name _params>]: Option<[<$base_ty_name Parameters>]>,
)*
}
@@ -62,6 +63,7 @@ macro_rules! define_key_structs {
fn from(config: [<$base_struct_name Config>]) -> Self {
Self {
$(
[<$name _params>]: None,
[<$name _key>]: config.[<$name _params>].map(<[<$base_ty_name ClientKey>]>::from),
)*
}

View File

@@ -92,7 +92,7 @@ impl Display for UninitializedClientKey {
impl std::error::Error for UninitializedClientKey {}
/// The client key of a given type was not initialized
/// The public key of a given type was not initialized
#[derive(Debug)]
pub struct UninitializedPublicKey(pub(crate) Type);
@@ -110,6 +110,24 @@ impl Display for UninitializedPublicKey {
impl std::error::Error for UninitializedPublicKey {}
/// The compresesd public key of a given type was not initialized
#[derive(Debug)]
pub struct UninitializedCompressedPublicKey(pub(crate) Type);
impl Display for UninitializedCompressedPublicKey {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"The compressed public key for the type '{:?}' was not properly initialized\n\
Dis you forget do enable the type in the config ?
",
self.0
)
}
}
impl std::error::Error for UninitializedCompressedPublicKey {}
/// Error when trying to create a short integer from a value that was too big to be represented
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct OutOfRangeError;

View File

@@ -1,144 +1,36 @@
use serde::{Deserialize, Serialize};
use crate::high_level_api::integers::parameters::{IntegerParameter, RadixParameters};
use crate::high_level_api::internal_traits::{DecryptionKey, EncryptionKey, FromParameters};
use crate::integer::{CrtCiphertext, CrtClientKey, U256};
use super::server_key::RadixCiphertextDyn;
use crate::high_level_api::internal_traits::DecryptionKey;
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
pub struct RadixClientKey {
pub(in crate::high_level_api::integers) inner: crate::integer::RadixClientKey,
// To know if we have to encrypt into a big or small ciphertext
pub(in crate::high_level_api::integers) pbs_order: crate::shortint::PBSOrder,
}
// This is needed by the impl EvaluationKey
impl AsRef<crate::integer::ClientKey> for RadixClientKey {
fn as_ref(&self) -> &crate::integer::ClientKey {
self.inner.as_ref()
}
}
impl<P> FromParameters<P> for RadixClientKey
where
P: Into<RadixParameters>,
{
fn from_parameters(parameters: P) -> Self {
let params = parameters.into();
#[cfg(feature = "internal-keycache")]
{
use crate::integer::keycache::KEY_CACHE;
let key = KEY_CACHE.get_from_params(params.block_parameters).0;
let inner = crate::integer::RadixClientKey::from((key, params.num_block));
Self {
inner,
pbs_order: params.pbs_order,
}
}
#[cfg(not(feature = "internal-keycache"))]
{
let inner =
crate::integer::RadixClientKey::new(params.block_parameters, params.num_block);
Self {
inner,
pbs_order: params.pbs_order,
}
}
}
}
impl EncryptionKey<u64, RadixCiphertextDyn> for RadixClientKey {
fn encrypt(&self, value: u64) -> RadixCiphertextDyn {
match self.pbs_order {
crate::shortint::PBSOrder::KeyswitchBootstrap => {
RadixCiphertextDyn::Big(self.inner.encrypt(value))
}
crate::shortint::PBSOrder::BootstrapKeyswitch => RadixCiphertextDyn::Small(
self.inner
.as_ref()
.encrypt_radix_small(value, self.inner.num_blocks()),
),
}
}
}
impl EncryptionKey<U256, RadixCiphertextDyn> for RadixClientKey {
fn encrypt(&self, value: U256) -> RadixCiphertextDyn {
match self.pbs_order {
crate::shortint::PBSOrder::KeyswitchBootstrap => RadixCiphertextDyn::Big(
self.inner
.as_ref()
.encrypt_radix(value, self.inner.num_blocks()),
),
crate::shortint::PBSOrder::BootstrapKeyswitch => RadixCiphertextDyn::Small(
self.inner
.as_ref()
.encrypt_radix_small(value, self.inner.num_blocks()),
),
}
}
}
impl DecryptionKey<RadixCiphertextDyn, u16> for RadixClientKey {
impl DecryptionKey<RadixCiphertextDyn, u16> for crate::integer::ClientKey {
fn decrypt(&self, ciphertext: &RadixCiphertextDyn) -> u16 {
let clear: u64 = match ciphertext {
RadixCiphertextDyn::Big(ct) => self.inner.decrypt(ct),
RadixCiphertextDyn::Small(ct) => self.inner.decrypt(ct),
RadixCiphertextDyn::Big(ct) => self.decrypt_radix(ct),
RadixCiphertextDyn::Small(ct) => self.decrypt_radix(ct),
};
clear as u16
}
}
impl DecryptionKey<RadixCiphertextDyn, u32> for RadixClientKey {
impl DecryptionKey<RadixCiphertextDyn, u32> for crate::integer::ClientKey {
fn decrypt(&self, ciphertext: &RadixCiphertextDyn) -> u32 {
let clear: u64 = match ciphertext {
RadixCiphertextDyn::Big(ct) => self.inner.decrypt(ct),
RadixCiphertextDyn::Small(ct) => self.inner.decrypt(ct),
RadixCiphertextDyn::Big(ct) => self.decrypt_radix(ct),
RadixCiphertextDyn::Small(ct) => self.decrypt_radix(ct),
};
clear as u32
}
}
impl<ClearType> DecryptionKey<RadixCiphertextDyn, ClearType> for RadixClientKey
impl<ClearType> DecryptionKey<RadixCiphertextDyn, ClearType> for crate::integer::ClientKey
where
ClearType: crate::integer::encryption::AsLittleEndianWords + Default,
{
fn decrypt(&self, ciphertext: &RadixCiphertextDyn) -> ClearType {
match ciphertext {
RadixCiphertextDyn::Big(ct) => self.inner.decrypt(ct),
RadixCiphertextDyn::Small(ct) => self.inner.decrypt(ct),
RadixCiphertextDyn::Big(ct) => self.decrypt_radix(ct),
RadixCiphertextDyn::Small(ct) => self.decrypt_radix(ct),
}
}
}
impl EncryptionKey<u64, CrtCiphertext> for CrtClientKey {
fn encrypt(&self, value: u64) -> CrtCiphertext {
self.encrypt(value)
}
}
impl DecryptionKey<CrtCiphertext, u64> for CrtClientKey {
fn decrypt(&self, ciphertext: &CrtCiphertext) -> u64 {
self.decrypt(ciphertext)
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GenericIntegerClientKey<P: IntegerParameter> {
pub(in crate::high_level_api::integers) inner: P::InnerClientKey,
pub(in crate::high_level_api::integers) params: P,
}
impl<P> From<P> for GenericIntegerClientKey<P>
where
P: IntegerParameter,
P::InnerClientKey: FromParameters<P>,
{
fn from(params: P) -> Self {
let key = P::InnerClientKey::from_parameters(params.clone());
Self { inner: key, params }
}
}

View File

@@ -1,13 +1,133 @@
define_key_structs! {
Integer {
uint8: FheUint8,
uint10: FheUint10,
uint12: FheUint12,
uint14: FheUint14,
uint16: FheUint16,
uint32: FheUint32,
uint64: FheUint64,
uint128: FheUint128,
uint256: FheUint256,
use crate::shortint::EncryptionKeyChoice;
#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub(crate) struct IntegerConfig {
pub(crate) block_parameters: Option<crate::shortint::PBSParameters>,
pub(crate) wopbs_block_parameters: Option<crate::shortint::WopbsParameters>,
}
impl IntegerConfig {
pub(crate) fn new(
block_parameters: Option<crate::shortint::PBSParameters>,
wopbs_block_parameters: Option<crate::shortint::WopbsParameters>,
) -> Self {
Self {
block_parameters,
wopbs_block_parameters,
}
}
pub(in crate::high_level_api) fn all_default() -> Self {
Self::default_big()
}
pub(in crate::high_level_api) fn all_none() -> Self {
Self::new(None, None)
}
pub(in crate::high_level_api) fn default_big() -> Self {
Self {
block_parameters: Some(crate::shortint::parameters::PARAM_MESSAGE_2_CARRY_2),
wopbs_block_parameters: None,
}
}
pub(in crate::high_level_api) fn default_small() -> Self {
Self {
block_parameters: Some(crate::shortint::parameters::PARAM_SMALL_MESSAGE_2_CARRY_2),
wopbs_block_parameters: None,
}
}
pub fn enable_wopbs(&mut self) {
let block_parameter = self
.block_parameters
.get_or_insert(crate::shortint::parameters::PARAM_MESSAGE_2_CARRY_2);
let wopbs_block_parameters = match block_parameter.encryption_key_choice {
EncryptionKeyChoice::Big => crate::shortint::parameters::parameters_wopbs_message_carry::WOPBS_PARAM_MESSAGE_2_CARRY_2,
EncryptionKeyChoice::Small => crate::shortint::parameters::parameters_wopbs_message_carry::WOPBS_PARAM_MESSAGE_2_CARRY_2,
};
self.wopbs_block_parameters = Some(wopbs_block_parameters);
}
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub(crate) struct IntegerClientKey {
pub(crate) key: Option<crate::integer::ClientKey>,
pub(crate) wopbs_block_parameters: Option<crate::shortint::WopbsParameters>,
pub(crate) encryption_type: EncryptionKeyChoice,
}
impl IntegerClientKey {
pub(crate) fn encryption_type(&self) -> EncryptionKeyChoice {
self.encryption_type
}
}
impl From<IntegerConfig> for IntegerClientKey {
fn from(config: IntegerConfig) -> Self {
let (key, encryption_type) = match config.block_parameters {
Some(params) => {
let encryption_type = params.encryption_key_choice;
let cks = crate::integer::ClientKey::new(params);
(Some(cks), encryption_type)
}
// setting a default value to encryption_type
// when block parameters is none (ie integers not activated)
// is fine, as since the key will be none, no risk of
// mismatch
None => (None, EncryptionKeyChoice::Big),
};
Self {
key,
wopbs_block_parameters: config.wopbs_block_parameters,
encryption_type,
}
}
}
#[derive(Clone, serde::Serialize, serde::Deserialize)]
pub struct IntegerServerKey {
pub(crate) key: Option<crate::integer::ServerKey>,
pub(crate) wopbs_key: Option<crate::integer::wopbs::WopbsKey>,
// Needed to encrypt trivial ciphertexts
pub(crate) encryption_type: crate::shortint::EncryptionKeyChoice,
}
impl Default for IntegerServerKey {
fn default() -> Self {
Self {
encryption_type: EncryptionKeyChoice::Big,
key: None,
wopbs_key: None,
}
}
}
impl IntegerServerKey {
pub(in crate::high_level_api) fn new(client_key: &IntegerClientKey) -> Self {
let Some(cks) = &client_key.key else {
return Self::default();
};
let base_integer_key = crate::integer::ServerKey::new(cks);
let wopbs_key = client_key
.wopbs_block_parameters
.as_ref()
.map(|wopbs_params| {
crate::integer::wopbs::WopbsKey::new_wopbs_key(cks, &base_integer_key, wopbs_params)
});
Self {
key: Some(base_integer_key),
wopbs_key,
encryption_type: client_key.encryption_type(),
}
}
pub(in crate::high_level_api::integers) fn pbs_key(&self) -> &crate::integer::ServerKey {
self.key
.as_ref()
.expect("Integer ServerKey is not initialized")
}
}

View File

@@ -1,12 +1,3 @@
pub(crate) use keys::{
IntegerClientKey, IntegerCompressedPublicKey, IntegerConfig, IntegerPublicKey, IntegerServerKey,
};
pub use parameters::{CrtParameters, RadixParameters};
pub(in crate::high_level_api) use types::static_::{
FheUint10Parameters, FheUint128Parameters, FheUint12Parameters, FheUint14Parameters,
FheUint16Parameters, FheUint256Parameters, FheUint32Parameters, FheUint64Parameters,
FheUint8Parameters,
};
pub use types::{
CompressedFheUint10, CompressedFheUint12, CompressedFheUint128, CompressedFheUint14,
CompressedFheUint16, CompressedFheUint256, CompressedFheUint32, CompressedFheUint64,
@@ -14,6 +5,10 @@ pub use types::{
FheUint32, FheUint64, FheUint8, GenericInteger,
};
pub(in crate::high_level_api) use keys::{IntegerClientKey, IntegerConfig, IntegerServerKey};
pub(in crate::high_level_api) use public_key::compressed::CompressedPublicKeyDyn;
pub(in crate::high_level_api) use public_key::PublicKeyDyn;
mod client_key;
mod keys;
mod parameters;

Some files were not shown because too many files have changed in this diff Show More