mirror of
https://github.com/zama-ai/tfhe-rs.git
synced 2026-01-09 14:47:56 -05:00
docs(tfhe): add boolean sha256 tutorial
Clap dev dependency added
This commit is contained in:
@@ -23,6 +23,7 @@ lazy_static = { version = "1.4.0" }
|
||||
criterion = "0.4.0"
|
||||
doc-comment = "0.3.3"
|
||||
serde_json = "1.0.94"
|
||||
clap = "4.2.7"
|
||||
# Used in user documentation
|
||||
bincode = "1.3.3"
|
||||
fs2 = { version = "0.4.3" }
|
||||
@@ -205,5 +206,9 @@ required-features = ["boolean"]
|
||||
name = "regex_engine"
|
||||
required-features = ["integer"]
|
||||
|
||||
[[example]]
|
||||
name = "sha256_bool"
|
||||
required-features = ["boolean"]
|
||||
|
||||
[lib]
|
||||
crate-type = ["lib", "staticlib", "cdylib"]
|
||||
|
||||
322
tfhe/docs/tutorial/sha256_bool.md
Normal file
322
tfhe/docs/tutorial/sha256_bool.md
Normal file
@@ -0,0 +1,322 @@
|
||||
# Tutorial
|
||||
|
||||
## Intro
|
||||
|
||||
In this tutorial we will go through the steps to turn a regular sha256 implementation into its homomorphic version. We explain the basics of the sha256 function first, and then how to implement it homomorphically with performance considerations.
|
||||
|
||||
## Sha256
|
||||
|
||||
The first step in this experiment is actually implementing the sha256 function. We can find the specification [here](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf), but let's summarize the three main sections of the document.
|
||||
|
||||
#### Padding
|
||||
|
||||
The sha256 function processes the input data in blocks or chunks of 512 bits. Before actually performing the hash computations we have to pad the input in the following way:
|
||||
* Append a single "1" bit
|
||||
* Append a number of "0" bits such that exactly 64 bits are left to make the message length a multiple of 512
|
||||
* Append the last 64 bits as a binary encoding of the original input length
|
||||
|
||||
Or visually:
|
||||
|
||||
```
|
||||
0 L L+1 L+1+k L+1+k+64
|
||||
|-----------------------------------|---|--------------------------------|----------------------|
|
||||
Original input (L bits) "1" bit "0" bits Encoding of the number L
|
||||
```
|
||||
Where the numbers on the top represent the length of the padded input at each position, and L+1+k+64 is a multiple of 512 (the length of the padded input).
|
||||
|
||||
#### Operations and functions
|
||||
|
||||
Let's take a look at the operations that we will use as building blocks for functions inside the sha256 computation. These are bitwise AND, XOR, NOT, addition modulo 2^32 and the Rotate Right (ROTR) and Shift Right (SHR) operations, all working with 32-bit words and producing a new word.
|
||||
|
||||
We combine these operations inside the sigma (with 4 variations), Ch and Maj functions. At the end of the day, when we change the sha256 to be computed homomorphically, we will mainly change the isolated code of each operation.
|
||||
|
||||
Here is the definition of each function:
|
||||
```
|
||||
Ch(x, y, z) = (x AND y) XOR ((NOT x) AND z)
|
||||
Maj(x, y, z) = (x AND y) XOR (x AND z) XOR (y AND z)
|
||||
|
||||
Σ0(x) = ROTR-2(x) XOR ROTR-13(x) XOR ROTR-22(x)
|
||||
Σ1(x) = ROTR-6(x) XOR ROTR-11(x) XOR ROTR-25(x)
|
||||
σ0(x) = ROTR-7(x) XOR ROTR-18(x) XOR SHR-3(x)
|
||||
σ1(x) = ROTR-17(x) XOR ROTR-19(x) XOR SHR-10(x)
|
||||
```
|
||||
There are some things to note about the functions. Firstly we see that Maj can be simplified by applying the boolean distributive law (x AND y) XOR (x AND z) = x AND (y XOR z). So the new Maj function looks like this:
|
||||
|
||||
```
|
||||
Maj(x, y, z) = (x AND (y XOR z)) XOR (y AND z)
|
||||
```
|
||||
Next we can also see that Ch can be simplified by using a single bitwise multiplexer. Let's take a look at the truth table of the Ch expression.
|
||||
| x | y | z | Result |
|
||||
| - | - | - | ------ |
|
||||
| 0 | 0 | 0 | 0 |
|
||||
| 0 | 0 | 1 | 1 |
|
||||
| 0 | 1 | 0 | 0 |
|
||||
| 0 | 1 | 1 | 1 |
|
||||
| 1 | 0 | 0 | 0 |
|
||||
| 1 | 0 | 1 | 0 |
|
||||
| 1 | 1 | 0 | 1 |
|
||||
| 1 | 1 | 1 | 1 |
|
||||
|
||||
When ```x = 0``` the result is identical to ```z```, but when ```x = 1``` the result is identical to ```y```. This is the same as saying ```if x {y} else {z}```. Hence we can replace the 4 bitwise operations of Ch by a single bitwise multiplexer.
|
||||
|
||||
Note that all these operations can be evaluated homomorphically. ROTR and SHR can be evaluated by changing the index of each individual bit of the word, even if each bit is encrypted, without using any homomorphic operation. Bitwise AND, XOR and multiplexer can be computed homomorphically and addition modulo 2^32 can be broken down into boolean homomorphic operations as well.
|
||||
|
||||
#### Sha256 computation
|
||||
|
||||
As we have mentioned, the sha256 function works with chunks of 512 bits. For each chunk, we will compute 64 32-bit words. 16 will come from the 512 bits and the rest will be computed using the previous functions. After computing the 64 words, and still within the same chunk iteration, a compression loop will compute a hash value (8 32-bit words), again using the previous functions and some constants to mix everything up. When we finish the last chunk iteration, the resulting hash values will be the output of the sha256 function.
|
||||
|
||||
Here is how this function looks like using arrays of 32 bools to represent words:
|
||||
|
||||
```rust
|
||||
fn sha256(padded_input: Vec<bool>) -> [bool; 256] {
|
||||
|
||||
// Initialize hash values with constant values
|
||||
let mut hash: [[bool; 32]; 8] = [
|
||||
hex_to_bools(0x6a09e667), hex_to_bools(0xbb67ae85),
|
||||
hex_to_bools(0x3c6ef372), hex_to_bools(0xa54ff53a),
|
||||
hex_to_bools(0x510e527f), hex_to_bools(0x9b05688c),
|
||||
hex_to_bools(0x1f83d9ab), hex_to_bools(0x5be0cd19),
|
||||
];
|
||||
|
||||
let chunks = padded_input.chunks(512);
|
||||
|
||||
for chunk in chunks {
|
||||
let mut w = [[false; 32]; 64];
|
||||
|
||||
// Copy first 16 words from current chunk
|
||||
for i in 0..16 {
|
||||
w[i].copy_from_slice(&chunk[i * 32..(i + 1) * 32]);
|
||||
}
|
||||
|
||||
// Compute the other 48 words
|
||||
for i in 16..64 {
|
||||
w[i] = add(add(add(sigma1(&w[i - 2]), w[i - 7]), sigma0(&w[i - 15])), w[i - 16]);
|
||||
}
|
||||
|
||||
let mut a = hash[0];
|
||||
let mut b = hash[1];
|
||||
let mut c = hash[2];
|
||||
let mut d = hash[3];
|
||||
let mut e = hash[4];
|
||||
let mut f = hash[5];
|
||||
let mut g = hash[6];
|
||||
let mut h = hash[7];
|
||||
|
||||
// Compression loop, each iteration uses a specific constant from K
|
||||
for i in 0..64 {
|
||||
let temp1 = add(add(add(add(h, ch(&e, &f, &g)), w[i]), hex_to_bools(K[i])), sigma_upper_case_1(&e));
|
||||
let temp2 = add(sigma_upper_case_0(&a), maj(&a, &b, &c));
|
||||
h = g;
|
||||
g = f;
|
||||
f = e;
|
||||
e = add(d, temp1);
|
||||
d = c;
|
||||
c = b;
|
||||
b = a;
|
||||
a = add(temp1, temp2);
|
||||
}
|
||||
|
||||
hash[0] = add(hash[0], a);
|
||||
hash[1] = add(hash[1], b);
|
||||
hash[2] = add(hash[2], c);
|
||||
hash[3] = add(hash[3], d);
|
||||
hash[4] = add(hash[4], e);
|
||||
hash[5] = add(hash[5], f);
|
||||
hash[6] = add(hash[6], g);
|
||||
hash[7] = add(hash[7], h);
|
||||
}
|
||||
|
||||
// Concatenate the final hash values to produce a 256-bit hash
|
||||
let mut output = [false; 256];
|
||||
for i in 0..8 {
|
||||
output[i * 32..(i + 1) * 32].copy_from_slice(&hash[i]);
|
||||
}
|
||||
output
|
||||
}
|
||||
```
|
||||
|
||||
## Making it homomorphic
|
||||
|
||||
The key idea is that we can replace each bit of ```padded_input``` with a Fully Homomorphic Encryption of the same bit value, and operate over the encrypted values using homomorphic operations. To achieve this we need to change the function signatures and deal with the borrowing rules of the Ciphertext type (which represents an encrypted bit) but the structure of the sha256 function remains the same. The part of the code that requires more consideration is the implementation of the sha256 operations, since they will use homomorphic boolean operations internally.
|
||||
|
||||
Homomorphic operations are really expensive, so we have to remove their unnecessary use and maximize parallelization in order to speed up the program. To simplify our code we use the Rayon crate which provides parallel iterators and efficiently manages threads. Let's now take a look at each sha256 operation!
|
||||
|
||||
#### Rotate Right and Shift Right
|
||||
|
||||
As we have highlighted, these two operations can be evaluated by changing the position of each encrypted bit in the word, thereby requiring 0 homomorphic operations. Here is our implementation:
|
||||
|
||||
```rust
|
||||
fn rotate_right(x: &[Ciphertext; 32], n: usize) -> [Ciphertext; 32] {
|
||||
let mut result = x.clone();
|
||||
result.rotate_right(n);
|
||||
result
|
||||
}
|
||||
|
||||
fn shift_right(x: &[Ciphertext; 32], n: usize, sk: &ServerKey) -> [Ciphertext; 32] {
|
||||
let mut result = x.clone();
|
||||
result.rotate_right(n);
|
||||
result[..n].fill_with(|| sk.trivial_encrypt(false));
|
||||
result
|
||||
}
|
||||
```
|
||||
|
||||
#### Bitwise XOR, AND, Multiplexer
|
||||
|
||||
To implement these operations we will use the ```xor```, ```and``` and ```mux``` methods provided by the tfhe library to evaluate each boolean operation homomorphically. It's important to note that, since we will operate bitwise, we can parallelize the homomorphic computations. In other words, we can homomorphically XOR the bits at index 0 of two words using a thread, while XORing the bits at index 1 using another thread, and so on. This means we could compute these bitwise operations using up to 32 concurrent threads (since we work with 32-bit words).
|
||||
|
||||
Here is our implementation of the bitwise homomorphic XOR operation. The ```par_iter``` and ```par_iter_mut``` methods create a parallel iterator that we use to compute each individual XOR efficiently. The other two bitwise operations are implemented in the same way.
|
||||
|
||||
```rust
|
||||
fn xor(a: &[Ciphertext; 32], b: &[Ciphertext; 32], sk: &ServerKey) -> [Ciphertext; 32] {
|
||||
let mut result = a.clone();
|
||||
result.par_iter_mut()
|
||||
.zip(a.par_iter().zip(b.par_iter()))
|
||||
.for_each(|(dst, (lhs, rhs))| *dst = sk.xor(lhs, rhs));
|
||||
result
|
||||
}
|
||||
```
|
||||
|
||||
#### Addition modulo 2^32
|
||||
|
||||
This is perhaps the trickiest operation to efficiently implement in a homomorphic fashion. A naive implementation could use the Ripple Carry Adder algorithm, which is straightforward but cannot be parallelized because each step depends on the previous one.
|
||||
|
||||
A better choice would be the Carry Lookahead Adder, which allows us to use the parallelized AND and XOR bitwise operations. With this design, our adder is around 50% faster than the Ripple Carry Adder.
|
||||
|
||||
```rust
|
||||
pub fn add(a: &[Ciphertext; 32], b: &[Ciphertext; 32], sk: &ServerKey) -> [Ciphertext; 32] {
|
||||
let propagate = xor(a, b, sk); // Parallelized bitwise XOR
|
||||
let generate = and(a, b, sk); // Parallelized bitwise AND
|
||||
|
||||
let carry = compute_carry(&propagate, &generate, sk);
|
||||
let sum = xor(&propagate, &carry, sk); // Parallelized bitwise XOR
|
||||
|
||||
sum
|
||||
}
|
||||
|
||||
fn compute_carry(propagate: &[Ciphertext; 32], generate: &[Ciphertext; 32], sk: &ServerKey) -> [Ciphertext; 32] {
|
||||
let mut carry = trivial_bools(&[false; 32], sk);
|
||||
carry[31] = sk.trivial_encrypt(false);
|
||||
|
||||
for i in (0..31).rev() {
|
||||
carry[i] = sk.or(&generate[i + 1], &sk.and(&propagate[i + 1], &carry[i + 1]));
|
||||
}
|
||||
|
||||
carry
|
||||
}
|
||||
```
|
||||
|
||||
To even improve performance more, the function that computes the carry signals can also be parallelized using parallel prefix algorithms. These algorithms involve more boolean operations (so homomorphic operations for us) but may be faster because of their parallel nature. We have implemented the Brent-Kung and Ladner-Fischer algorithms, which entail different tradeoffs.
|
||||
|
||||
Brent-Kung has the least amount of boolean operations we could find (140 when using grey cells, for 32-bit numbers), which makes it suitable when we can't process many operations concurrently and fast. Our results confirm that it's indeed faster than both the sequential algorithm and Ladner-Fischer when run on regular computers.
|
||||
|
||||
On the other hand, Ladner-Fischer performs more boolean operations (209 using grey cells) than Brent-Kung, but they are performed in larger batches. Hence we can compute more operations in parallel and finish earlier, but we need more fast threads available or they will slow down the carry signals computation. Ladner-Fischer can be suitable when using cloud-based computing services, which offer many high-speed threads.
|
||||
|
||||
Our implementation uses Brent-Kung by default, but Ladner-Fischer can be enabled when needed by using the ```--ladner-fischer``` command line argument.
|
||||
|
||||
For more information about parallel prefix adders you can read [this paper](https://www.iosrjournals.org/iosr-jece/papers/Vol6-Issue1/A0610106.pdf) or [this other paper](https://www.ijert.org/research/design-and-implementation-of-parallel-prefix-adder-for-improving-the-performance-of-carry-lookahead-adder-IJERTV4IS120608.pdf).
|
||||
|
||||
Finally, with all these sha256 operations working homomorphically, our functions will be homomomorphic as well along with the whole sha256 function (after adapting the code to work with the Ciphertext type). Let's talk about other performance improvements we can make before we finish.
|
||||
|
||||
### More parallel processing
|
||||
|
||||
If we inspect the main ```sha256_fhe``` function, we will find operations that can be performed in parallel. For instance, within the compression loop, ```temp1``` and ```temp2``` can be computed concurrently. An efficient way to parallelize computations here is using the ```rayon::join()``` function, which uses parallel processing only when there are available CPUs. Recall that the two temporary values in the compression loop are the result of several additions, so we can use nested calls to ```rayon::join()``` to potentially parallelize more operations.
|
||||
|
||||
Another way to speed up consecutive additions would be using the Carry Save Adder, a very efficient adder that takes 3 numbers and returns a sum and carry sequence. If our inputs are A, B and C, we can construct a CSA with our previously implemented Maj function and the bitwise XOR operation as follows:
|
||||
|
||||
```
|
||||
Carry = Maj(A, B, C)
|
||||
Sum = A XOR B XOR C
|
||||
```
|
||||
|
||||
By chaining CSAs, we can input the sum and carry from a preceding stage along with another number into a new CSA. Finally, to get the result of the additions we add the sum and carry sequences using a conventional adder. At the end we are performing the same number of additions, but some of them are now CSAs, speeding up the process. Let's see all this together in the ```temp1``` and ```temp2``` computations.
|
||||
|
||||
```rust
|
||||
let (temp1, temp2) = rayon::join(
|
||||
|| {
|
||||
let ((sum, carry), s1) = rayon::join(
|
||||
|| {
|
||||
let ((sum, carry), ch) = rayon::join(
|
||||
|| csa(&h, &w[i], &trivial_bools(&hex_to_bools(K[i]), sk), sk),
|
||||
|| ch(&e, &f, &g, sk),
|
||||
);
|
||||
csa(&sum, &carry, &ch, sk)
|
||||
},
|
||||
|| sigma_upper_case_1(&e, sk)
|
||||
);
|
||||
|
||||
let (sum, carry) = csa(&sum, &carry, &s1, sk);
|
||||
add(&sum, &carry, sk)
|
||||
},
|
||||
|| {
|
||||
add(&sigma_upper_case_0(&a, sk), &maj(&a, &b, &c, sk), sk)
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
The first closure of the outer call to join will return ```temp1``` and the second ```temp2```. Inside the first outer closure we call join recursively until we reach the addition of the value ```h```, the current word ```w[i]``` and the current constant ```K[i]``` by using the CSA, while potentially computing in parallel the ```ch``` function. Then we take the sum, carry and ch values and add them again using the CSA.
|
||||
|
||||
All this is done while potentially computing the ```sigma_upper_case_1``` function. Finally we input the previous sum, carry and sigma values to the CSA and perform the final addition with ```add```. Once again, this is done while potentially computing ```sigma_upper_case_0``` and ```maj``` and adding them to get ```temp2```, in the second outer closure.
|
||||
|
||||
With some changes of this type, we finally get a homomorphic sha256 function that doesn't leave unused computational resources.
|
||||
|
||||
## How to use sha256_bool
|
||||
|
||||
First of all, the most important thing when running the program is using the ```--release``` flag. The use of sha256_bool would look like this, given the implementation of ```encrypt_bools``` and ```decrypt_bools```:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let matches = Command::new("Homomorphic sha256")
|
||||
.arg(Arg::new("ladner_fischer")
|
||||
.long("ladner-fischer")
|
||||
.help("Use the Ladner Fischer parallel prefix algorithm for additions")
|
||||
.action(ArgAction::SetTrue))
|
||||
.get_matches();
|
||||
|
||||
// If set using the command line flag "--ladner-fischer" this algorithm will be used in additions
|
||||
let ladner_fischer: bool = matches.get_flag("ladner_fischer");
|
||||
|
||||
// INTRODUCE INPUT FROM STDIN
|
||||
|
||||
let mut input = String::new();
|
||||
println!("Write input to hash:");
|
||||
|
||||
io::stdin()
|
||||
.read_line(&mut input)
|
||||
.expect("Failed to read line");
|
||||
|
||||
input = input.trim_end_matches('\n').to_string();
|
||||
|
||||
println!("You entered: \"{}\"", input);
|
||||
|
||||
// CLIENT PADS DATA AND ENCRYPTS IT
|
||||
|
||||
let (ck, sk) = gen_keys();
|
||||
|
||||
let padded_input = pad_sha256_input(&input);
|
||||
let encrypted_input = encrypt_bools(&padded_input, &ck);
|
||||
|
||||
// SERVER COMPUTES OVER THE ENCRYPTED PADDED DATA
|
||||
|
||||
println!("Computing the hash");
|
||||
let encrypted_output = sha256_fhe(encrypted_input, ladner_fischer, &sk);
|
||||
|
||||
// CLIENT DECRYPTS THE OUTPUT
|
||||
|
||||
let output = decrypt_bools(&encrypted_output, &ck);
|
||||
let outhex = bools_to_hex(output);
|
||||
|
||||
println!("{}", outhex);
|
||||
}
|
||||
```
|
||||
|
||||
By using ```stdin``` we can supply the data to hash using a file instead of the command line. For example, if our file ```input.txt``` is in the same directory as the project, we can use the following shell command after building with ```cargo build --release```:
|
||||
|
||||
```sh
|
||||
./target/release/examples/sha256_bool < input.txt
|
||||
```
|
||||
|
||||
Our implementation also accepts hexadecimal inputs. To be considered as such, the input must start with "0x" and contain only valid hex digits (otherwise it's interpreted as text).
|
||||
|
||||
Finally see that padding is executed on the client side. This has the advantage of hiding the exact length of the input to the server, who already doesn't know anything about the contents of it but may extract information from the length.
|
||||
|
||||
Another option would be to perform padding on the server side. The padding function would receive the encrypted input and pad it with trivial bit encryptions. We could then integrate the padding function inside the ```sha256_fhe``` function computed by the server.
|
||||
363
tfhe/examples/sha256_bool/boolean_ops.rs
Normal file
363
tfhe/examples/sha256_bool/boolean_ops.rs
Normal file
@@ -0,0 +1,363 @@
|
||||
// This module contains all the operations and functions used in the sha256 function, implemented with homomorphic boolean
|
||||
// operations. Both the bitwise operations, which serve as the building blocks for other functions, and the adders employ
|
||||
// parallel processing techniques.
|
||||
|
||||
use std::array;
|
||||
use rayon::prelude::*;
|
||||
use tfhe::boolean::prelude::{BinaryBooleanGates, Ciphertext, ServerKey};
|
||||
|
||||
// Implementation of a Carry Save Adder, which computes sum and carry sequences very efficiently. We then add the final
|
||||
// sum and carry values to obtain the result. CSAs are useful to speed up sequential additions
|
||||
pub fn csa(a: &[Ciphertext; 32], b: &[Ciphertext; 32], c: &[Ciphertext; 32], sk: &ServerKey) -> ([Ciphertext; 32], [Ciphertext; 32]) {
|
||||
|
||||
let (carry, sum) = rayon::join(
|
||||
|| {
|
||||
maj(&a, &b, &c, sk)
|
||||
},
|
||||
|| {
|
||||
xor(&a, &xor(&b, &c, sk), sk)
|
||||
},
|
||||
);
|
||||
|
||||
// perform a left shift by one to discard the carry-out and set the carry-in to 0
|
||||
let mut shifted_carry = trivial_bools(&[false; 32], sk);
|
||||
for (i, elem) in carry.into_iter().enumerate() {
|
||||
if i == 0 {
|
||||
continue;
|
||||
} else {
|
||||
shifted_carry[i-1] = elem;
|
||||
}
|
||||
}
|
||||
|
||||
(sum, shifted_carry)
|
||||
}
|
||||
|
||||
pub fn add(a: &[Ciphertext; 32], b: &[Ciphertext; 32], ladner_fischer_opt: bool, sk: &ServerKey) -> [Ciphertext; 32] {
|
||||
let (propagate, generate) = rayon::join(
|
||||
|| xor(a, b, sk),
|
||||
|| and(a, b, sk)
|
||||
);
|
||||
|
||||
let carry = if ladner_fischer_opt {
|
||||
ladner_fischer(&propagate, &generate, sk)
|
||||
} else {
|
||||
brent_kung(&propagate, &generate, sk)
|
||||
};
|
||||
|
||||
let sum = xor(&propagate, &carry, sk);
|
||||
|
||||
sum
|
||||
}
|
||||
|
||||
// Implementation of the Brent Kung parallel prefix algorithm
|
||||
// This function computes the carry signals in parallel while minimizing the number of homomorphic operations
|
||||
fn brent_kung(propagate: &[Ciphertext; 32], generate: &[Ciphertext; 32], sk: &ServerKey) -> [Ciphertext; 32] {
|
||||
let mut propagate = propagate.clone();
|
||||
let mut generate = generate.clone();
|
||||
|
||||
for d in 0..5 { // first 5 stages
|
||||
let stride = 1 << d;
|
||||
|
||||
let indices: Vec<(usize, usize)> = (0..32 - stride)
|
||||
.rev()
|
||||
.step_by(2 * stride)
|
||||
.map(|i| i + 1 - stride)
|
||||
.enumerate()
|
||||
.collect();
|
||||
|
||||
let updates: Vec<(usize, Ciphertext, Ciphertext)> = indices.into_par_iter().map(|(n, index)| {
|
||||
|
||||
let new_p;
|
||||
let new_g;
|
||||
|
||||
if n == 0 { // grey cell
|
||||
new_p = propagate[index].clone();
|
||||
new_g = sk.or(&generate[index], &sk.and(&generate[index + stride], &propagate[index]));
|
||||
|
||||
} else { // black cell
|
||||
new_p = sk.and(&propagate[index], &propagate[index + stride]);
|
||||
new_g = sk.or(&generate[index], &sk.and(&generate[index + stride], &propagate[index]));
|
||||
}
|
||||
|
||||
(index, new_p, new_g)
|
||||
}).collect();
|
||||
|
||||
for (index, new_p, new_g) in updates {
|
||||
propagate[index] = new_p;
|
||||
generate[index] = new_g;
|
||||
}
|
||||
|
||||
if d == 4 {
|
||||
let mut cells = 0;
|
||||
for d_2 in 0..4 { // last 4 stages
|
||||
let stride = 1 << (4 - d_2 - 1);
|
||||
cells += 1 << d_2;
|
||||
|
||||
let indices: Vec<(usize, usize)> = (0..cells).map(|cell| {
|
||||
(cell, stride + 2*stride*cell)
|
||||
}).collect();
|
||||
|
||||
let updates: Vec<(usize, Ciphertext)> = indices.into_par_iter().map(|(_, index)| {
|
||||
let new_g = sk.or(&generate[index], &sk.and(&generate[index+stride], &propagate[index]));
|
||||
|
||||
(index, new_g)
|
||||
}).collect();
|
||||
|
||||
for (index, new_g) in updates {
|
||||
generate[index] = new_g;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut carry = trivial_bools(&[false; 32], sk);
|
||||
for bit in 0..31 {
|
||||
carry[bit] = generate[bit + 1].clone();
|
||||
}
|
||||
|
||||
carry
|
||||
}
|
||||
|
||||
// Implementation of the Ladner Fischer parallel prefix algorithm
|
||||
// This function may perform better than the previous one when many threads are available as it has less stages
|
||||
fn ladner_fischer(propagate: &[Ciphertext; 32], generate: &[Ciphertext; 32], sk: &ServerKey) -> [Ciphertext; 32] {
|
||||
let mut propagate = propagate.clone();
|
||||
let mut generate = generate.clone();
|
||||
|
||||
for d in 0..5 {
|
||||
let stride = 1 << d;
|
||||
|
||||
let indices: Vec<(usize, usize)> = (0..32 - stride)
|
||||
.rev()
|
||||
.step_by(2 * stride)
|
||||
.flat_map(|i| (0..stride).map(move |count| (i, count)))
|
||||
.collect();
|
||||
|
||||
let updates: Vec<(usize, Ciphertext, Ciphertext)> = indices
|
||||
.into_par_iter()
|
||||
.map(|(i, count)| {
|
||||
let index = i - count; // current column
|
||||
|
||||
let p = propagate[i + 1].clone(); // propagate from a previous column
|
||||
let g = generate[i + 1].clone(); // generate from a previous column
|
||||
let new_p;
|
||||
let new_g;
|
||||
|
||||
if index < 32 - (2 * stride) { // black cell
|
||||
new_p = sk.and(&propagate[index], &p);
|
||||
new_g = sk.or(&generate[index], &sk.and(&g, &propagate[index]));
|
||||
|
||||
} else { // grey cell
|
||||
new_p = propagate[index].clone();
|
||||
new_g = sk.or(&generate[index], &sk.and(&g, &propagate[index]));
|
||||
}
|
||||
(index, new_p, new_g)
|
||||
})
|
||||
.collect();
|
||||
|
||||
for (index, new_p, new_g) in updates {
|
||||
propagate[index] = new_p;
|
||||
generate[index] = new_g;
|
||||
}
|
||||
}
|
||||
|
||||
let mut carry = trivial_bools(&[false; 32], sk);
|
||||
for bit in 0..31 {
|
||||
carry[bit] = generate[bit + 1].clone();
|
||||
}
|
||||
|
||||
carry
|
||||
}
|
||||
|
||||
// 2 (homomorphic) bitwise ops
|
||||
pub fn sigma0(x: &[Ciphertext; 32], sk: &ServerKey) -> [Ciphertext; 32] {
|
||||
let a = rotate_right(x, 7);
|
||||
let b = rotate_right(x, 18);
|
||||
let c = shift_right(x, 3, sk);
|
||||
xor(&xor(&a, &b, sk), &c, sk)
|
||||
}
|
||||
|
||||
pub fn sigma1(x: &[Ciphertext; 32], sk: &ServerKey) -> [Ciphertext; 32] {
|
||||
let a = rotate_right(x, 17);
|
||||
let b = rotate_right(x, 19);
|
||||
let c = shift_right(x, 10, sk);
|
||||
xor(&xor(&a, &b, sk), &c, sk)
|
||||
}
|
||||
|
||||
pub fn sigma_upper_case_0(x: &[Ciphertext; 32], sk: &ServerKey) -> [Ciphertext; 32] {
|
||||
let a = rotate_right(x, 2);
|
||||
let b = rotate_right(x, 13);
|
||||
let c = rotate_right(x, 22);
|
||||
xor(&xor(&a, &b, sk), &c, sk)
|
||||
}
|
||||
|
||||
pub fn sigma_upper_case_1(x: &[Ciphertext; 32], sk: &ServerKey) -> [Ciphertext; 32] {
|
||||
let a = rotate_right(x, 6);
|
||||
let b = rotate_right(x, 11);
|
||||
let c = rotate_right(x, 25);
|
||||
xor(&xor(&a, &b, sk), &c, sk)
|
||||
}
|
||||
|
||||
// 0 bitwise ops
|
||||
fn rotate_right(x: &[Ciphertext; 32], n: usize) -> [Ciphertext; 32] {
|
||||
let mut result = x.clone();
|
||||
result.rotate_right(n);
|
||||
result
|
||||
}
|
||||
|
||||
fn shift_right(x: &[Ciphertext; 32], n: usize, sk: &ServerKey) -> [Ciphertext; 32] {
|
||||
let mut result = x.clone();
|
||||
result.rotate_right(n);
|
||||
result[..n].fill_with(|| sk.trivial_encrypt(false));
|
||||
result
|
||||
}
|
||||
|
||||
// 1 bitwise op
|
||||
pub fn ch(x: &[Ciphertext; 32], y: &[Ciphertext; 32], z: &[Ciphertext; 32], sk: &ServerKey) -> [Ciphertext; 32] {
|
||||
mux(x, y, z, sk)
|
||||
}
|
||||
|
||||
// 4 bitwise ops
|
||||
pub fn maj(x: &[Ciphertext; 32], y: &[Ciphertext; 32], z: &[Ciphertext; 32], sk: &ServerKey) -> [Ciphertext; 32] {
|
||||
|
||||
let (lhs, rhs) = rayon::join(
|
||||
|| and(x, &xor(y, z, sk), sk),
|
||||
|| and(y, z, sk),
|
||||
);
|
||||
xor(&lhs, &rhs, sk)
|
||||
}
|
||||
|
||||
// Parallelized homomorphic bitwise ops
|
||||
// Building block for most of the previous functions
|
||||
fn xor(a: &[Ciphertext; 32], b: &[Ciphertext; 32], sk: &ServerKey) -> [Ciphertext; 32] {
|
||||
let mut result = a.clone();
|
||||
result.par_iter_mut()
|
||||
.zip(a.par_iter().zip(b.par_iter()))
|
||||
.for_each(|(dst, (lhs, rhs))| *dst = sk.xor(lhs, rhs));
|
||||
result
|
||||
}
|
||||
|
||||
fn and(a: &[Ciphertext; 32], b: &[Ciphertext; 32], sk: &ServerKey) -> [Ciphertext; 32] {
|
||||
let mut result = a.clone();
|
||||
result.par_iter_mut()
|
||||
.zip(a.par_iter().zip(b.par_iter()))
|
||||
.for_each(|(dst, (lhs, rhs))| *dst = sk.and(lhs, rhs));
|
||||
result
|
||||
}
|
||||
|
||||
fn mux(condition: &[Ciphertext; 32], then: &[Ciphertext; 32], otherwise: &[Ciphertext; 32], sk: &ServerKey) -> [Ciphertext; 32] {
|
||||
let mut result = condition.clone();
|
||||
result.par_iter_mut()
|
||||
.zip(condition.par_iter().zip(then.par_iter().zip(otherwise.par_iter())))
|
||||
.for_each(|(dst, (condition, (then, other)))| *dst = sk.mux(condition, then, other));
|
||||
result
|
||||
}
|
||||
|
||||
// Trivial encryption of 32 bools
|
||||
pub fn trivial_bools(bools: &[bool; 32], sk: &ServerKey) -> [Ciphertext; 32] {
|
||||
array::from_fn(|i| sk.trivial_encrypt(bools[i]))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use tfhe::boolean::prelude::*;
|
||||
use super::*;
|
||||
|
||||
fn to_bool_array(arr: [i32; 32]) -> [bool; 32] {
|
||||
let mut bool_arr = [false; 32];
|
||||
for i in 0..32 {
|
||||
if arr[i] == 1 {
|
||||
bool_arr[i] = true;
|
||||
}
|
||||
}
|
||||
bool_arr
|
||||
}
|
||||
fn encrypt(bools: &[bool; 32], ck: &ClientKey) -> [Ciphertext; 32] {
|
||||
[
|
||||
ck.encrypt(bools[0]), ck.encrypt(bools[1]), ck.encrypt(bools[2]), ck.encrypt(bools[3]),
|
||||
ck.encrypt(bools[4]), ck.encrypt(bools[5]), ck.encrypt(bools[6]), ck.encrypt(bools[7]),
|
||||
ck.encrypt(bools[8]), ck.encrypt(bools[9]), ck.encrypt(bools[10]), ck.encrypt(bools[11]),
|
||||
ck.encrypt(bools[12]), ck.encrypt(bools[13]), ck.encrypt(bools[14]), ck.encrypt(bools[15]),
|
||||
ck.encrypt(bools[16]), ck.encrypt(bools[17]), ck.encrypt(bools[18]), ck.encrypt(bools[19]),
|
||||
ck.encrypt(bools[20]), ck.encrypt(bools[21]), ck.encrypt(bools[22]), ck.encrypt(bools[23]),
|
||||
ck.encrypt(bools[24]), ck.encrypt(bools[25]), ck.encrypt(bools[26]), ck.encrypt(bools[27]),
|
||||
ck.encrypt(bools[28]), ck.encrypt(bools[29]), ck.encrypt(bools[30]), ck.encrypt(bools[31]),
|
||||
]
|
||||
}
|
||||
fn decrypt(bools: &[Ciphertext; 32], ck: &ClientKey) -> [bool; 32] {
|
||||
[
|
||||
ck.decrypt(&bools[0]), ck.decrypt(&bools[1]), ck.decrypt(&bools[2]), ck.decrypt(&bools[3]),
|
||||
ck.decrypt(&bools[4]), ck.decrypt(&bools[5]), ck.decrypt(&bools[6]), ck.decrypt(&bools[7]),
|
||||
ck.decrypt(&bools[8]), ck.decrypt(&bools[9]), ck.decrypt(&bools[10]), ck.decrypt(&bools[11]),
|
||||
ck.decrypt(&bools[12]), ck.decrypt(&bools[13]), ck.decrypt(&bools[14]), ck.decrypt(&bools[15]),
|
||||
ck.decrypt(&bools[16]), ck.decrypt(&bools[17]), ck.decrypt(&bools[18]), ck.decrypt(&bools[19]),
|
||||
ck.decrypt(&bools[20]), ck.decrypt(&bools[21]), ck.decrypt(&bools[22]), ck.decrypt(&bools[23]),
|
||||
ck.decrypt(&bools[24]), ck.decrypt(&bools[25]), ck.decrypt(&bools[26]), ck.decrypt(&bools[27]),
|
||||
ck.decrypt(&bools[28]), ck.decrypt(&bools[29]), ck.decrypt(&bools[30]), ck.decrypt(&bools[31]),
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_add_modulo_2_32() {
|
||||
let (ck, sk) = gen_keys();
|
||||
|
||||
let a = encrypt(&to_bool_array([0,1,0,1,1,0,1,1,1,1,1,0,0,0,0,0,1,1,0,0,1,1,0,1,0,0,0,1,1,0,0,1,]), &ck);
|
||||
let b = encrypt(&to_bool_array([0,0,1,1,0,1,0,1,1,0,0,0,0,1,1,1,0,0,1,0,0,1,1,1,0,0,1,0,1,0,1,1,]), &ck);
|
||||
let c = encrypt(&to_bool_array([0,0,0,1,1,1,1,1,1,0,0,0,0,1,0,1,1,1,0,0,1,0,0,1,1,0,0,0,1,1,0,0,]), &ck);
|
||||
let d = encrypt(&to_bool_array([0,1,0,0,0,0,1,0,1,0,0,0,1,0,1,0,0,0,1,0,1,1,1,1,1,0,0,1,1,0,0,0,]), &ck);
|
||||
let e = encrypt(&to_bool_array([0,1,1,0,1,0,0,0,0,1,1,0,0,1,0,1,0,1,1,0,1,1,0,0,0,1,1,0,1,1,0,0,]), &ck);
|
||||
|
||||
let (sum, carry) = csa(&c, &d, &e, &sk);
|
||||
let (sum, carry) = csa(&b, &sum, &carry, &sk);
|
||||
let (sum, carry) = csa(&a, &sum, &carry, &sk);
|
||||
let output = add(&sum, &carry, false, &sk);
|
||||
|
||||
let result = decrypt(&output, &ck);
|
||||
let expected = to_bool_array([0,1,0,1,1,0,1,1,1,1,0,1,1,1,0,1,0,1,0,1,1,0,0,1,1,1,0,1,0,1,0,0,]);
|
||||
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sigma0() {
|
||||
let (ck, sk) = gen_keys();
|
||||
|
||||
let input = encrypt(&to_bool_array([0,1,1,0,1,1,1,1,0,0,1,0,0,0,0,0,0,1,1,1,0,1,1,1,0,1,1,0,1,1,1,1,]), &ck);
|
||||
let output = sigma0(&input, &sk);
|
||||
let result = decrypt(&output, &ck);
|
||||
let expected = to_bool_array([1,1,0,0,1,1,1,0,1,1,1,0,0,0,0,1,1,0,0,1,0,1,0,1,1,1,0,0,1,0,1,1,]);
|
||||
|
||||
assert_eq!(result, expected);
|
||||
} //the other sigmas are implemented in the same way
|
||||
|
||||
#[test]
|
||||
fn test_ch() {
|
||||
let (ck, sk) = gen_keys();
|
||||
|
||||
let e = encrypt(&to_bool_array([0,1,0,1,0,0,0,1,0,0,0,0,1,1,1,0,0,1,0,1,0,0,1,0,0,1,1,1,1,1,1,1,]), &ck);
|
||||
let f = encrypt(&to_bool_array([1,0,0,1,1,0,1,1,0,0,0,0,0,1,0,1,0,1,1,0,1,0,0,0,1,0,0,0,1,1,0,0,]), &ck);
|
||||
let g = encrypt(&to_bool_array([0,0,0,1,1,1,1,1,1,0,0,0,0,0,1,1,1,1,0,1,1,0,0,1,1,0,1,0,1,0,1,1,]), &ck);
|
||||
|
||||
let output = ch(&e, &f, &g, &sk);
|
||||
let result = decrypt(&output, &ck);
|
||||
let expected = to_bool_array([0,0,0,1,1,1,1,1,1,0,0,0,0,1,0,1,1,1,0,0,1,0,0,1,1,0,0,0,1,1,0,0,]);
|
||||
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_maj() {
|
||||
let (ck, sk) = gen_keys();
|
||||
|
||||
let a = encrypt(&to_bool_array([0,1,1,0,1,0,1,0,0,0,0,0,1,0,0,1,1,1,1,0,0,1,1,0,0,1,1,0,0,1,1,1,]), &ck);
|
||||
let b = encrypt(&to_bool_array([1,0,1,1,1,0,1,1,0,1,1,0,0,1,1,1,1,0,1,0,1,1,1,0,1,0,0,0,0,1,0,1,]), &ck);
|
||||
let c = encrypt(&to_bool_array([0,0,1,1,1,1,0,0,0,1,1,0,1,1,1,0,1,1,1,1,0,0,1,1,0,1,1,1,0,0,1,0,]), &ck);
|
||||
|
||||
let output = maj(&a, &b, &c, &sk);
|
||||
let result = decrypt(&output, &ck);
|
||||
let expected = to_bool_array([0,0,1,1,1,0,1,0,0,1,1,0,1,1,1,1,1,1,1,0,0,1,1,0,0,1,1,0,0,1,1,1,]);
|
||||
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
}
|
||||
71
tfhe/examples/sha256_bool/main.rs
Normal file
71
tfhe/examples/sha256_bool/main.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
mod padding;
|
||||
mod boolean_ops;
|
||||
mod sha256_function;
|
||||
|
||||
use std::io;
|
||||
use clap::{Arg, ArgAction, Command};
|
||||
use tfhe::boolean::prelude::*;
|
||||
use padding::pad_sha256_input;
|
||||
use sha256_function::{sha256_fhe, bools_to_hex};
|
||||
|
||||
fn main() {
|
||||
let matches = Command::new("Homomorphic sha256")
|
||||
.arg(Arg::new("ladner_fischer")
|
||||
.long("ladner-fischer")
|
||||
.help("Use the Ladner Fischer parallel prefix algorithm for additions")
|
||||
.action(ArgAction::SetTrue))
|
||||
.get_matches();
|
||||
|
||||
// If set using the command line flag "--ladner-fischer" this algorithm will be used in additions
|
||||
let ladner_fischer: bool = matches.get_flag("ladner_fischer");
|
||||
|
||||
// INTRODUCE INPUT FROM STDIN
|
||||
|
||||
let mut input = String::new();
|
||||
println!("Write input to hash:");
|
||||
|
||||
io::stdin()
|
||||
.read_line(&mut input)
|
||||
.expect("Failed to read line");
|
||||
|
||||
input = input.trim_end_matches('\n').to_string();
|
||||
|
||||
println!("You entered: \"{}\"", input);
|
||||
|
||||
// CLIENT PADS DATA AND ENCRYPTS IT
|
||||
|
||||
let (ck, sk) = gen_keys();
|
||||
|
||||
let padded_input = pad_sha256_input(&input);
|
||||
let encrypted_input = encrypt_bools(&padded_input, &ck);
|
||||
|
||||
// SERVER COMPUTES OVER THE ENCRYPTED PADDED DATA
|
||||
|
||||
println!("Computing the hash");
|
||||
let encrypted_output = sha256_fhe(encrypted_input, ladner_fischer, &sk);
|
||||
|
||||
// CLIENT DECRYPTS THE OUTPUT
|
||||
|
||||
let output = decrypt_bools(&encrypted_output, &ck);
|
||||
let outhex = bools_to_hex(output);
|
||||
|
||||
println!("{}", outhex);
|
||||
}
|
||||
|
||||
fn encrypt_bools(bools: &Vec<bool>, ck: &ClientKey) -> Vec<Ciphertext> {
|
||||
let mut ciphertext = vec![];
|
||||
|
||||
for bool in bools {
|
||||
ciphertext.push(ck.encrypt(*bool));
|
||||
}
|
||||
ciphertext
|
||||
}
|
||||
|
||||
fn decrypt_bools(ciphertext: &Vec<Ciphertext>, ck: &ClientKey) -> Vec<bool> {
|
||||
let mut bools = vec![];
|
||||
|
||||
for cipher in ciphertext {
|
||||
bools.push(ck.decrypt(&cipher));
|
||||
}
|
||||
bools
|
||||
}
|
||||
67
tfhe/examples/sha256_bool/padding.rs
Normal file
67
tfhe/examples/sha256_bool/padding.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
// This module contains the padding function, which is computed by the client over the plain text.
|
||||
// The function returns the padded data as a vector of bools, for later encryption. Note that padding
|
||||
// could also be performed by the server, by appending trivially encrypted bools. However, in our
|
||||
// implementation, the exact length of the pre-image (hashed message) is not revealed.
|
||||
|
||||
// If input starts with "0x" and following characters are valid hexadecimal values, it's interpreted
|
||||
// as hex, otherwise input is interpreted as text
|
||||
pub fn pad_sha256_input(input: &str) -> Vec<bool> {
|
||||
let bytes = if input.starts_with("0x") && is_valid_hex(&input[2..]) {
|
||||
let no_prefix = &input[2..];
|
||||
let hex_input = if no_prefix.len() % 2 == 0 { // hex value can be converted to bytes
|
||||
no_prefix.to_string()
|
||||
} else {
|
||||
format!("0{}", no_prefix) // pad hex value to ensure a correct conversion to bytes
|
||||
};
|
||||
hex_input
|
||||
.as_bytes()
|
||||
.chunks(2)
|
||||
.map(|chunk| u8::from_str_radix(std::str::from_utf8(chunk).unwrap(), 16).unwrap())
|
||||
.collect::<Vec<u8>>()
|
||||
} else {
|
||||
input.as_bytes().to_vec()
|
||||
};
|
||||
|
||||
pad_sha256_data(&bytes)
|
||||
}
|
||||
|
||||
fn is_valid_hex(hex: &str) -> bool {
|
||||
hex.chars().all(|c| c.is_digit(16))
|
||||
}
|
||||
|
||||
fn pad_sha256_data(data: &[u8]) -> Vec<bool> {
|
||||
let mut bits: Vec<bool> = data.iter().flat_map(|byte| (0..8).rev().map(move |i| (byte >> i) & 1 == 1)).collect();
|
||||
|
||||
// Append a single '1' bit
|
||||
bits.push(true);
|
||||
|
||||
// Calculate the number of padding zeros required
|
||||
let padding_zeros = (512 - ((bits.len() + 64) % 512)) % 512;
|
||||
bits.extend(std::iter::repeat(false).take(padding_zeros));
|
||||
|
||||
// Append a 64-bit big-endian representation of the original message length
|
||||
let data_len_bits = (data.len() as u64) * 8;
|
||||
bits.extend((0..64).rev().map(|i| (data_len_bits >> i) & 1 == 1));
|
||||
|
||||
bits
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::sha256_function::bools_to_hex;
|
||||
|
||||
#[test]
|
||||
fn test_pad_sha256_input() {
|
||||
|
||||
let input = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq";
|
||||
let expected_output = "6162636462636465636465666465666765666768666768696768696a68696a6\
|
||||
b696a6b6c6a6b6c6d6b6c6d6e6c6d6e6f6d6e6f706e6f70718000000000000000000000000000000000000000000\
|
||||
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c0";
|
||||
|
||||
let result = pad_sha256_input(input);
|
||||
let hex_result = bools_to_hex(result);
|
||||
|
||||
assert_eq!(hex_result, expected_output);
|
||||
}
|
||||
}
|
||||
219
tfhe/examples/sha256_bool/sha256_function.rs
Normal file
219
tfhe/examples/sha256_bool/sha256_function.rs
Normal file
@@ -0,0 +1,219 @@
|
||||
// This module implements the main sha256 homomorphic function using parallel processing when possible and some helper functions
|
||||
|
||||
use std::array;
|
||||
use tfhe::boolean::prelude::*;
|
||||
use crate::boolean_ops::{add, sigma0, sigma1, ch, maj, sigma_upper_case_0, sigma_upper_case_1, trivial_bools, csa};
|
||||
|
||||
pub fn sha256_fhe(padded_input: Vec<Ciphertext>, ladner_fischer: bool, sk: &ServerKey) -> Vec<Ciphertext> {
|
||||
assert_eq!(padded_input.len() % 512, 0, "padded input length is not a multiple of 512");
|
||||
|
||||
// Initialize hash values
|
||||
let mut hash: [[Ciphertext; 32]; 8] = [
|
||||
trivial_bools(&hex_to_bools(0x6a09e667), &sk),
|
||||
trivial_bools(&hex_to_bools(0xbb67ae85), &sk),
|
||||
trivial_bools(&hex_to_bools(0x3c6ef372), &sk),
|
||||
trivial_bools(&hex_to_bools(0xa54ff53a), &sk),
|
||||
trivial_bools(&hex_to_bools(0x510e527f), &sk),
|
||||
trivial_bools(&hex_to_bools(0x9b05688c), &sk),
|
||||
trivial_bools(&hex_to_bools(0x1f83d9ab), &sk),
|
||||
trivial_bools(&hex_to_bools(0x5be0cd19), &sk),
|
||||
];
|
||||
|
||||
let chunks = padded_input.chunks_exact(512);
|
||||
|
||||
for chunk in chunks {
|
||||
|
||||
// Compute the 64 words
|
||||
let mut w = initialize_w(&sk);
|
||||
|
||||
for i in 0..16 {
|
||||
w[i].clone_from_slice(&chunk[i * 32..(i + 1) * 32]);
|
||||
}
|
||||
|
||||
for i in (16..64).step_by(2) {
|
||||
let u = i+1;
|
||||
|
||||
let (word_i, word_u) = rayon::join(
|
||||
|| {
|
||||
let (s0, s1) = rayon::join(
|
||||
|| sigma0(&w[i - 15], sk),
|
||||
|| sigma1(&w[i - 2], sk));
|
||||
|
||||
let (sum, carry) = csa(&s0, &w[i - 7], &w[i - 16], sk);
|
||||
let (sum, carry) = csa(&s1, &sum, &carry, sk);
|
||||
add(&sum, &carry, ladner_fischer, sk)
|
||||
},
|
||||
|| {
|
||||
let (s0, s1) = rayon::join(
|
||||
|| sigma0(&w[u - 15], sk),
|
||||
|| sigma1(&w[u - 2], sk));
|
||||
|
||||
let (sum, carry) = csa(&s0, &w[u - 7], &w[u - 16], sk);
|
||||
let (sum, carry) = csa(&s1, &sum, &carry, sk);
|
||||
add(&sum, &carry, ladner_fischer, sk)
|
||||
}
|
||||
);
|
||||
|
||||
w[i] = word_i;
|
||||
w[u] = word_u;
|
||||
}
|
||||
|
||||
let mut a = hash[0].clone();
|
||||
let mut b = hash[1].clone();
|
||||
let mut c = hash[2].clone();
|
||||
let mut d = hash[3].clone();
|
||||
let mut e = hash[4].clone();
|
||||
let mut f = hash[5].clone();
|
||||
let mut g = hash[6].clone();
|
||||
let mut h = hash[7].clone();
|
||||
|
||||
// Compression loop
|
||||
for i in 0..64 {
|
||||
let (temp1, temp2) = rayon::join(
|
||||
|| {
|
||||
let ((sum, carry), s1) = rayon::join(
|
||||
|| {
|
||||
let ((sum, carry), ch) = rayon::join(
|
||||
|| csa(&h, &w[i], &trivial_bools(&hex_to_bools(K[i]), sk), sk),
|
||||
|| ch(&e, &f, &g, sk),
|
||||
);
|
||||
csa(&sum, &carry, &ch, sk)
|
||||
},
|
||||
|| sigma_upper_case_1(&e, sk)
|
||||
);
|
||||
|
||||
let (sum, carry) = csa(&sum, &carry, &s1, sk);
|
||||
add(&sum, &carry, ladner_fischer, sk)
|
||||
},
|
||||
|| {
|
||||
add(&sigma_upper_case_0(&a, sk), &maj(&a, &b, &c, sk), ladner_fischer, sk)
|
||||
},
|
||||
);
|
||||
|
||||
let (temp_e, temp_a) = rayon::join(
|
||||
|| add(&d, &temp1, ladner_fischer, sk),
|
||||
|| add(&temp1, &temp2, ladner_fischer, sk),
|
||||
);
|
||||
|
||||
h = g;
|
||||
g = f;
|
||||
f = e;
|
||||
e = temp_e;
|
||||
d = c;
|
||||
c = b;
|
||||
b = a;
|
||||
a = temp_a;
|
||||
}
|
||||
|
||||
hash[0] = add(&hash[0], &a, ladner_fischer, sk);
|
||||
hash[1] = add(&hash[1], &b, ladner_fischer, sk);
|
||||
hash[2] = add(&hash[2], &c, ladner_fischer, sk);
|
||||
hash[3] = add(&hash[3], &d, ladner_fischer, sk);
|
||||
hash[4] = add(&hash[4], &e, ladner_fischer, sk);
|
||||
hash[5] = add(&hash[5], &f, ladner_fischer, sk);
|
||||
hash[6] = add(&hash[6], &g, ladner_fischer, sk);
|
||||
hash[7] = add(&hash[7], &h, ladner_fischer, sk);
|
||||
}
|
||||
|
||||
// Concatenate the final hash values to produce a 256-bit hash
|
||||
let mut output = vec![];
|
||||
|
||||
for i in 0..8 {
|
||||
for j in 0..32 {
|
||||
output.push(hash[i][j].clone());
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
// Initialize the 64 words with trivial encryptions
|
||||
fn initialize_w(sk: &ServerKey) -> [[Ciphertext; 32]; 64] {
|
||||
array::from_fn(|_| trivial_bools(&[false; 32], sk))
|
||||
}
|
||||
|
||||
// To represent decrypted digest bools as hexadecimal String
|
||||
pub fn bools_to_hex(bools: Vec<bool>) -> String {
|
||||
let mut hex_string = String::new();
|
||||
let mut byte = 0u8;
|
||||
let mut counter = 0;
|
||||
|
||||
for bit in bools {
|
||||
byte <<= 1;
|
||||
if bit {
|
||||
byte |= 1;
|
||||
}
|
||||
|
||||
counter += 1;
|
||||
|
||||
if counter == 8 {
|
||||
hex_string.push_str(&format!("{:02x}", byte));
|
||||
byte = 0;
|
||||
counter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle any remaining bits in case the bools vector length is not a multiple of 8
|
||||
if counter > 0 {
|
||||
byte <<= 8 - counter;
|
||||
hex_string.push_str(&format!("{:02x}", byte));
|
||||
}
|
||||
|
||||
hex_string
|
||||
}
|
||||
|
||||
// To represent constant values as bool arrays
|
||||
fn hex_to_bools(hex_value: u32) -> [bool; 32] {
|
||||
let mut bool_array = [false; 32];
|
||||
let mut mask = 0x8000_0000;
|
||||
|
||||
for i in 0..32 {
|
||||
bool_array[i] = (hex_value & mask) != 0;
|
||||
mask >>= 1;
|
||||
}
|
||||
|
||||
bool_array
|
||||
}
|
||||
|
||||
const K: [u32; 64] = [
|
||||
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
||||
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
||||
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
||||
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
||||
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
||||
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
||||
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
||||
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
|
||||
];
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn to_bool_array(arr: [i32; 32]) -> [bool; 32] {
|
||||
let mut bool_arr = [false; 32];
|
||||
for i in 0..32 {
|
||||
if arr[i] == 1 {
|
||||
bool_arr[i] = true;
|
||||
}
|
||||
}
|
||||
bool_arr
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bools_to_hex() {
|
||||
let bools = to_bool_array([1,0,0,1,0,0,0,0,1,0,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,]);
|
||||
let hex_bools = bools_to_hex(bools.to_vec());
|
||||
|
||||
assert_eq!(hex_bools, "90befffa");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hex_to_bools() {
|
||||
let hex = 0x428a2f98;
|
||||
let result = hex_to_bools(hex);
|
||||
let expected = to_bool_array([0,1,0,0,0,0,1,0,1,0,0,0,1,0,1,0,0,0,1,0,1,1,1,1,1,0,0,1,1,0,0,0,]);
|
||||
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user