Skip to content

Commit be29332

Browse files
committed
feat: uint multiplication
1 parent 5078a4e commit be29332

File tree

7 files changed

+152
-84
lines changed

7 files changed

+152
-84
lines changed

Cargo.lock

Lines changed: 20 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ lto = "thin"
2828
[[bench]]
2929
name = "u32"
3030
harness = false
31+
[[bench]]
32+
name = "u8"
33+
harness = false
3134

3235
[[example]]
3336
name = "simple_struct"

README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,20 +65,21 @@ which is re-exported by the crate behind the `custom_rand` feature.
6565

6666
## Benchmarks
6767

68-
Benchmarks were made using a Ryzen 7 7800x3D on Windows 11 by averaging on 10 000 `u32`s using `cargo bench`.
68+
Benchmarks were made using a Ryzen 7 7800x3D on `u32`s using `cargo bench`.
6969

7070
Parameters used for this benchmark were :
7171
- `d` = 128
7272
- `dp` = 128
7373
- `delta` = 1
7474
- `tau` = 128
7575

76-
| Operation | Average time |
76+
| Operation | Average time |
7777
|:-----------------:|:----------------:|
78-
| Encryption | 74.9 µs |
79-
| Decryption | 13.2 µs |
80-
| Add | 1.16 ms |
81-
| Dec. after add | 1.04 ms |
78+
| Encryption | 76.0 µs |
79+
| Decryption | 12.5 µs |
80+
| Add | 1.11 ms |
81+
| Dec. after add | 1.03 ms |
82+
| Mul |Uncomfortably long|
8283

8384
It is still more efficient to decrypt, operate and then re-encrypt the data. This limits the use of the system to applications where security is paramount, and takes precedence over speed.
8485

benches/u32.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@ fn criterion_cipher(c: &mut Criterion) {
2323

2424
let mut d = 0;
2525
c.bench_function("decipher", |b| b.iter(|| d = c1.decipher(sk)));
26-
27-
assert_eq!(n1, d);
2826
}
2927

3028
fn criterion_add(c: &mut Criterion) {
@@ -52,8 +50,6 @@ fn criterion_add(c: &mut Criterion) {
5250
// because usually the operation skyrockets the degree of the polynomial
5351
let mut d = 0;
5452
c.bench_function("decipher after add", |b| b.iter(|| d = c3.decipher(sk)));
55-
56-
assert_eq!(n1 + n2, d);
5753
}
5854

5955
criterion_group!(

benches/u8.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
use criterion::{criterion_group, criterion_main, Criterion};
2+
use homomorph::prelude::*;
3+
use homomorph_impls::numbers::HomomorphicMultiplication;
4+
5+
type Number = u8;
6+
7+
fn criterion_mul(c: &mut Criterion) {
8+
const PARAMETERS: Parameters = Parameters::new(128, 128, 1, 128);
9+
let mut context = Context::new(PARAMETERS);
10+
context.generate_secret_key();
11+
context.generate_public_key().unwrap();
12+
let sk = context.get_secret_key().unwrap();
13+
let pk = context.get_public_key().unwrap();
14+
15+
// Note that the number doesn't matter, the benchmark won't change
16+
let n1: Number = 6;
17+
let n2: Number = 7;
18+
19+
let c1 = Ciphered::cipher(&n1, pk);
20+
let c2 = Ciphered::cipher(&n2, pk);
21+
22+
let mut c3 = None;
23+
c.bench_function("mul", |b| {
24+
b.iter(|| c3 = Some(unsafe { HomomorphicMultiplication::apply(&c1, &c2) }))
25+
});
26+
let c4 = c3.unwrap();
27+
28+
// Decipher after an operation can be significantly slower than deciphering before
29+
// because usually the operation skyrockets the degree of the polynomial
30+
let mut d = 0;
31+
c.bench_function("decipher after mul", |b| b.iter(|| d = c4.decipher(sk)));
32+
}
33+
34+
criterion_group!(
35+
name = benches;
36+
config = Criterion::default().measurement_time(core::time::Duration::from_secs(10));
37+
targets = criterion_mul
38+
);
39+
criterion_main!(benches);

src/impls/numbers/uint.rs

Lines changed: 30 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -110,48 +110,42 @@ macro_rules! impl_homomorphic_addition_uint {
110110

111111
impl_homomorphic_addition_uint!(u8, u16, u32, usize, u64, u128);
112112

113-
// TODO: Remove these two lines
114-
#[allow(unreachable_code)]
115-
#[allow(unused_variables)]
116-
// https://en.m.wikipedia.org/wiki/Binary_multiplier#Unsigned_integers
117-
fn homomorph_mul_internal(a: &[CipheredBit], b: &[CipheredBit], size: usize) -> Vec<CipheredBit> {
118-
todo!("Homormophic multiplication for uint");
119-
113+
/// `a` and `b` must have the same length, equal to the number of bits
114+
///
115+
/// From <https://en.m.wikipedia.org/wiki/Binary_multiplier#Unsigned_integers>
116+
fn homomorph_mul_internal(a: &[CipheredBit], b: &[CipheredBit]) -> Vec<CipheredBit> {
120117
// We stop before overflow as overflowed bits will be thrown away on decryption
121-
let max_len = size;
118+
let length = a.len();
119+
let mut result: Vec<CipheredBit> = vec![CipheredBit::zero(); length];
122120

123-
let mut result: Vec<CipheredBit> = vec![CipheredBit::zero(); max_len];
121+
let partial_products: Vec<_> = a
122+
.iter()
123+
.map(|ai| b.iter().map(|bj| ai.and(bj)).collect::<Vec<_>>())
124+
.collect();
124125

125-
// Avoid borrowing issues
126-
let null_bit = CipheredBit::zero();
126+
let mut carry: Vec<Vec<CipheredBit>> = Vec::with_capacity(length);
127+
carry.push(Vec::new());
127128

128-
let mut partial_products = Vec::with_capacity(max_len);
129-
for i in 0..max_len {
130-
let mut pi = Vec::with_capacity(max_len);
131-
let ai = a.get(i).unwrap_or(&null_bit);
132-
for j in 0..max_len {
133-
pi.push(ai.and(b.get(j).unwrap_or(&null_bit)));
134-
}
135-
partial_products.push(pi);
136-
}
129+
// Compiler hints
130+
assert_eq!(result.len(), length);
131+
assert_eq!(partial_products.len(), length);
137132

138-
// TODO: Fix this broken carry
139-
let mut carry: Vec<Vec<CipheredBit>> = Vec::with_capacity(max_len);
140-
carry.push(Vec::with_capacity(0));
141-
for i in 0..max_len {
142-
if i + 1 < max_len {
133+
// TODO: Optimize this
134+
for i in 0..length {
135+
if i + 1 < length {
143136
carry.push(Vec::with_capacity(i + carry[i].len()));
144137
}
145138
// Apply partial products
146-
for j in 0..i {
147-
if i + 1 < max_len {
148-
carry[i + 1].push(partial_products[i][i - j].and(&result[i]));
139+
for (j, pj) in partial_products.iter().enumerate().take(i + 1) {
140+
let pp = &pj[i - j];
141+
if i + 1 < length {
142+
carry[i + 1].push(pp.and(&result[i]));
149143
}
150-
result[i] = result[i].xor(&partial_products[i][i - j]);
144+
result[i] = result[i].xor(pp);
151145
}
152146
// Propagate carry
153147
for j in 0..carry[i].len() {
154-
if i + 1 < max_len {
148+
if i + 1 < length {
155149
let t = result[i].and(&carry[i][j]);
156150
carry[i + 1].push(t);
157151
}
@@ -173,7 +167,9 @@ macro_rules! impl_homomorphic_multiplication_uint {
173167
///
174168
/// `d/delta` on cipher must have been at least TBD.
175169
unsafe fn apply(a: &Ciphered<$t>, b: &Ciphered<$t>) -> Ciphered<$t> {
176-
Ciphered::new_from_raw(homomorph_mul_internal(a, b, <$t>::BITS as usize))
170+
debug_assert_eq!(a.len(), b.len());
171+
debug_assert_eq!(a.len(), <$t>::BITS as usize);
172+
Ciphered::new_from_raw(homomorph_mul_internal(a, b))
177173
}
178174
}
179175
)+
@@ -330,9 +326,8 @@ mod tests {
330326
}
331327

332328
#[test]
333-
#[should_panic = "not yet implemented: Homormophic multiplication for uint"]
334329
fn test_homomorphic_multiplication() {
335-
let parameters = Parameters::new(1024, 8, 1, 4);
330+
let parameters = Parameters::new(128, 64, 1, 64);
336331
let mut context = Context::new(parameters);
337332
context.generate_secret_key();
338333
context.generate_public_key().unwrap();
@@ -351,8 +346,8 @@ mod tests {
351346
let d = c.decipher(sk);
352347
assert_eq!(0, d);
353348

354-
let a_raw = thread_rng().gen::<u16>() / 2;
355-
let b_raw = thread_rng().gen::<u16>() / 2;
349+
let a_raw = thread_rng().gen::<u8>() % 13;
350+
let b_raw = thread_rng().gen::<u8>() % 20;
356351

357352
let a = Ciphered::cipher(&a_raw, pk);
358353
let b = Ciphered::cipher(&b_raw, pk);

0 commit comments

Comments
 (0)