├── toolchain.txt ├── demos ├── fpga-test │ ├── rustfmt.toml │ ├── Cargo.toml │ └── src │ │ └── fpga.rs ├── game-of-life │ ├── rustfmt.toml │ ├── src │ │ ├── lib.rs │ │ ├── gol.rs │ │ ├── gol │ │ │ ├── operators.rs │ │ │ ├── graph.rs │ │ │ └── board.rs │ │ └── game-of-life.rs │ ├── FPT.mp4 │ ├── initial_state │ ├── readme.md │ └── Cargo.toml ├── accel.awsxclbin ├── README.md ├── aws_afi_gen.md └── aws_f1_setup.md ├── rustfmt.toml ├── .gitignore ├── Cargo.toml ├── scripts └── prepare_env.sh ├── LICENSE ├── CITATION ├── CODE_OF_CONDUCT.md ├── readme.md └── Makefile /toolchain.txt: -------------------------------------------------------------------------------- 1 | nightly-2023-04-12 2 | -------------------------------------------------------------------------------- /demos/fpga-test/rustfmt.toml: -------------------------------------------------------------------------------- 1 | tab_spaces = 2 -------------------------------------------------------------------------------- /demos/game-of-life/rustfmt.toml: -------------------------------------------------------------------------------- 1 | tab_spaces = 2 -------------------------------------------------------------------------------- /demos/game-of-life/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod gol; 2 | -------------------------------------------------------------------------------- /demos/accel.awsxclbin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KULeuven-COSIC/fpt-demo/HEAD/demos/accel.awsxclbin -------------------------------------------------------------------------------- /demos/game-of-life/FPT.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KULeuven-COSIC/fpt-demo/HEAD/demos/game-of-life/FPT.mp4 -------------------------------------------------------------------------------- /demos/game-of-life/src/gol.rs: -------------------------------------------------------------------------------- 1 | pub mod board; 2 | pub mod graph; 3 | pub mod operators; 4 | 5 | pub use board::*; 6 | pub use graph::*; 7 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | unstable_features = true 2 | imports_granularity="Module" 3 | format_code_in_doc_comments = true 4 | wrap_comments = true 5 | comment_width = 100 6 | -------------------------------------------------------------------------------- /demos/game-of-life/initial_state: -------------------------------------------------------------------------------- 1 | 0 0 1 0 0 0 0 0 2 | 1 0 1 0 0 0 0 0 3 | 0 1 1 0 0 0 0 0 4 | 0 0 0 0 0 0 0 0 5 | 0 0 0 0 0 0 0 0 6 | 0 0 0 0 0 0 0 0 7 | 0 0 0 0 0 0 0 0 8 | 0 0 0 0 0 0 0 0 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .idea/ 3 | .vscode/ 4 | testvectors/ 5 | 6 | # Path we use for internal-keycache during tests 7 | ./keys/ 8 | # In case of symlinked keys 9 | ./keys 10 | 11 | **/Cargo.lock 12 | **/*.bin 13 | 14 | # Some of our bench outputs 15 | /tfhe/benchmarks_parameters 16 | **/*.csv 17 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["demos/fpga-test", "demos/game-of-life"] 4 | 5 | [profile.bench] 6 | lto = "fat" 7 | 8 | [profile.release] 9 | lto = "fat" 10 | 11 | [profile.release_lto_off] 12 | inherits = "release" 13 | lto = "off" 14 | 15 | # Compiles much faster for tests and allows reasonable performance for iterating 16 | [profile.devo] 17 | inherits = "dev" 18 | opt-level = 3 19 | lto = "off" 20 | -------------------------------------------------------------------------------- /demos/game-of-life/readme.md: -------------------------------------------------------------------------------- 1 | # Game of life using Fully homomorphic encryption 2 | 3 | This is a very simple implementation of Conway's [Game of Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life) using periodic boundary conditions, with a twist: the board is encrypted and all calculations are performed in encrypted space. In principle, calculations can thus be done by a thread or server which has no access to the state of the board. 4 | 5 | ## Build and run 6 | 7 | ```bash 8 | cargo run --release --bin game-of-life 9 | cargo run --release --bin game-of-life --features fpga 10 | ``` 11 | -------------------------------------------------------------------------------- /demos/fpga-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fpga" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | tfhe = { path = "../../../tfhe-rs/tfhe", features = [ "boolean", "shortint", "integer", "nightly-avx512", "x86_64-unix", "seeder_deterministic" ] } 10 | rayon = "1.7.0" 11 | rand = "0.8.5" 12 | rand_chacha = "0.3.1" 13 | serde_json = "1.0.94" 14 | serde = "1.0.156" 15 | concrete-fft = { version = "0.2.1", features = ["serde"] } 16 | 17 | [dev-dependencies] 18 | criterion = { version = "0.3", features = [ "html_reports" ]} 19 | 20 | [features] 21 | fpga = ["tfhe/fpga"] 22 | 23 | [[bin]] 24 | name = "fpga" 25 | path = "src/fpga.rs" -------------------------------------------------------------------------------- /demos/game-of-life/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "homomorphic_game_of_life" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | tfhe = { path = "../../../tfhe-rs/tfhe", features = [ "boolean", "shortint", "integer", "nightly-avx512", "x86_64-unix", "seeder_deterministic" ] } 10 | rayon = "1.7.0" 11 | rand = "0.8.5" 12 | rand_chacha = "0.3.1" 13 | serde_json = "1.0.94" 14 | serde = "1.0.156" 15 | concrete-fft = { version = "0.2.1", features = ["serde"] } 16 | tui = "0.19.0" 17 | crossterm = "0.25.0" 18 | ggez = "0.6.0" 19 | ctrlc = "3.4.0" 20 | 21 | [dev-dependencies] 22 | criterion = { version = "0.3", features = [ "html_reports" ]} 23 | 24 | [features] 25 | fpga = ["tfhe/fpga"] 26 | 27 | [[bin]] 28 | name = "game-of-life" 29 | path = "src/game-of-life.rs" -------------------------------------------------------------------------------- /scripts/prepare_env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export REPO_DIR="$(realpath "$(dirname "$(realpath "$0")")"/..)" 4 | 5 | ################################################################################ 6 | # Clone TFHE-rs and patch it with Belfort FPGA integration 7 | 8 | export TFHERS_DIR="$HOME/tfhe-rs" 9 | export TFHERS_COMMIT="2e58fe36a4ac40f9c2a4efb3cccb3d33a67d6439" 10 | export TFHERS_URL=https://github.com/zama-ai/tfhe-rs.git 11 | export PATCH_COMMIT_MSG="FPT Patch Applied" 12 | 13 | separator() { 14 | printf "\n=========================================================\n\n" 15 | } 16 | 17 | if [ -d "$TFHERS_DIR" ]; then 18 | echo "Stash changes and set ZAMA GitHub as the origin" 19 | pushd $TFHERS_DIR 20 | git stash -m "Stashed changes" 21 | git remote set-url origin $TFHERS_URL 22 | else 23 | echo "Fresh Clone of TFHE-rs" 24 | git clone --no-checkout $TFHERS_URL $TFHERS_DIR 25 | pushd $TFHERS_DIR 26 | fi 27 | 28 | separator 29 | echo "Checkout TFHE-rs for FPT FPGA acceleration" 30 | PATCH_COMMIT=$(git log --grep="$PATCH_COMMIT_MSG" --format="%H" | head -n 1) 31 | 32 | if [ -n "$PATCH_COMMIT" ]; then 33 | git checkout $PATCH_COMMIT 34 | else 35 | git checkout $TFHERS_COMMIT 36 | 37 | echo "Applying FPT patch (excluding docs images)..." 38 | git apply --exclude=tfhe/docs/_static/* --whitespace=nowarn $REPO_DIR/fpt.patch 39 | 40 | separator 41 | git add . 42 | echo "Group all changes into one commit" 43 | git commit -m "$PATCH_COMMIT_MSG" 44 | fi 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause Clear License 2 | 3 | Copyright © 2023 ZAMA. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without modification, 7 | are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, this 13 | list of conditions and the following disclaimer in the documentation and/or other 14 | materials provided with the distribution. 15 | 16 | 3. Neither the name of ZAMA nor the names of its contributors may be used to endorse 17 | or promote products derived from this software without specific prior written permission. 18 | 19 | NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. 20 | THIS SOFTWARE IS PROVIDED BY THE ZAMA AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 21 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 22 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 23 | ZAMA OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 24 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 27 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 28 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /demos/game-of-life/src/gol/operators.rs: -------------------------------------------------------------------------------- 1 | use tfhe::boolean::prelude::*; 2 | 3 | // add one encrypted bit `a` to the encrypted binary representation `b` of a 3-bit number, with 8 4 | // identified with 0 5 | fn add_1( 6 | server_key: &ServerKey, 7 | a: &Vec, 8 | b: &(Vec, Vec, Vec), 9 | ) -> (Vec, Vec, Vec) { 10 | let c1 = server_key.xor_packed(a, &b.0); 11 | let r = server_key.and_packed(a, &b.0); 12 | let c2 = server_key.xor_packed(&r, &b.1); 13 | let r = server_key.and_packed(&r, &b.1); 14 | let c3 = server_key.xor_packed(&r, &b.2); 15 | (c1, c2, c3) 16 | } 17 | 18 | // sum the encrypted bits in `elements`, starting from an encrypted 3-bit representation of 0 19 | fn sum( 20 | server_key: &ServerKey, 21 | elements: [Vec; 8], 22 | zeros: &(Vec, Vec, Vec), 23 | ) -> (Vec, Vec, Vec) { 24 | let mut result = add_1(server_key, &elements[0], zeros); 25 | for i in 1..8 { 26 | result = add_1(server_key, &elements[i], &result); 27 | } 28 | result 29 | } 30 | 31 | // check if a cell will be alive after the update 32 | pub fn is_alive( 33 | server_key: &ServerKey, 34 | cell_p: Vec, 35 | neighbours_p: [Vec; 8], 36 | zeros: &(Vec, Vec, Vec), 37 | new_states: &mut Vec, 38 | ) { 39 | let sum_neighbours = sum(server_key, neighbours_p, zeros); 40 | let sum_is_2_or_3 = 41 | server_key.and_packed(&sum_neighbours.1, &server_key.not_packed(&sum_neighbours.2)); 42 | let sum_is_3 = server_key.and_packed( 43 | &sum_neighbours.0, 44 | &server_key.and_packed(&sum_neighbours.1, &server_key.not_packed(&sum_neighbours.2)), 45 | ); 46 | let mut new_state = 47 | server_key.or_packed(&sum_is_3, &server_key.and_packed(&cell_p, &sum_is_2_or_3)); 48 | new_states.append(&mut new_state); 49 | } 50 | -------------------------------------------------------------------------------- /demos/fpga-test/src/fpga.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | #![allow(non_camel_case_types)] 3 | #![allow(non_snake_case)] 4 | // Supress not FFI-safe warning 5 | #![allow(improper_ctypes)] 6 | 7 | use tfhe::boolean::engine::BooleanEngine; 8 | use tfhe::boolean::prelude::*; 9 | 10 | use rand::{Rng, SeedableRng}; 11 | 12 | use std::iter::zip; 13 | #[cfg(feature = "fpga")] 14 | use tfhe::boolean::server_key::FpgaGates; 15 | 16 | fn main() { 17 | const TEST_COUNT: u32 = 10; 18 | 19 | let mut boolean_engine = BooleanEngine::new(); 20 | 21 | let client_key = boolean_engine.create_client_key(DEMO_PARAMETERS); 22 | 23 | // generate the server key, only the SW needs this 24 | let server_key = boolean_engine.create_server_key(&client_key); 25 | 26 | #[cfg(feature = "fpga")] 27 | server_key.enable_fpga(); 28 | 29 | let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0); 30 | 31 | for test in 0..TEST_COUNT { 32 | let mut lefts = Vec::::new(); 33 | let mut rights = Vec::::new(); 34 | let mut outputs = Vec::::new(); 35 | 36 | let mut lefts_ct = Vec::::new(); 37 | let mut rights_ct = Vec::::new(); 38 | 39 | for _ in 0..FPGA_BOOTSTRAP_PACKING { 40 | let l = rng.gen_range(0..=1) != 0; 41 | let r = rng.gen_range(0..=1) != 0; 42 | 43 | let l_ct = boolean_engine.encrypt(l, &client_key); 44 | let r_ct = boolean_engine.encrypt(r, &client_key); 45 | 46 | lefts.push(l); 47 | rights.push(r); 48 | outputs.push(l && r); 49 | 50 | lefts_ct.push(l_ct); 51 | rights_ct.push(r_ct); 52 | } 53 | 54 | let outputs_ct = server_key.and_packed(&lefts_ct, &rights_ct); 55 | 56 | let mut success = true; 57 | for (output, ct_output) in zip(outputs, outputs_ct) { 58 | let expected: bool = output; 59 | let calculated: bool = client_key.decrypt(&ct_output); 60 | 61 | if expected != calculated { 62 | success = false; 63 | } 64 | } 65 | 66 | if success { 67 | println!("TEST {} PASSED", test); 68 | } else { 69 | println!("TEST {} FAILED", test); 70 | } 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /demos/README.md: -------------------------------------------------------------------------------- 1 | # Demos 2 | 3 | In this directory, we provide two apps that are used to verify, profile, and benchmark the FPT-accelerated execution of `tfhe-rs`. 4 | 5 | :warning: This demo version of FPT implements only a toy parameter set. The goal is to illustrate that FPT is available on AWS, functional, and integrated with `tfhe-rs`. The full design is much more capable, as described in the paper. If you have an application that could benefit from full parameter set FPT acceleration, please [send us an email](mailto:michiel.vanbeirendonck@esat.kuleuven.be,janpieter.danvers@esat.kuleuven.be,furkan.turan@esat.kuleuven.be,ingrid.verbauwhede@esat.kuleuven.be). 6 | 7 | 8 | ## Setup Before Running the Demos 9 | 10 | To run the demos, you must first configure the AWS F1 instances. This includes installing AWS and AMD-provided drivers. A guide [`aws_f1_setup.md`](aws_f1_setup.md) is provided to describe the setup in steps. 11 | 12 | Let's clone this repository and prepare the environment: 13 | 14 | ```bash 15 | FPT_DIR=~/fpt-demo 16 | git clone https://github.com/KULeuven-COSIC/fpt-demo.git $FPT_DIR 17 | cd $FPT_DIR 18 | ./scripts/prepare_env.sh 19 | ``` 20 | 21 | You should set these two environmental variables, which will indicate to the demo where to find the FPGA image of the FPT. 22 | 23 | ```bash 24 | export FPGA_IMAGE="$FPT_DIR/demos/accel.awsxclbin" 25 | export FPGA_INDEX=0 26 | ``` 27 | 28 | :warning: In AWS F1 context, the `accel.awsxclbin` is a container that does not contain the actual FPGA image, but rather links to the image hosted by the AWS. It also makes the image subject to access control. If you wish to run this demo, send us an email with your AWS user ID, and we will grant you access to the image. 29 | 30 | Once the setup above is done, you can continue with the demos below. 31 | 32 | ## Demo: FPGA Test 33 | 34 | Once AWS F1 instance is set up, this app tests the correctness of FPT. It also allows benchmarking performance etc. 35 | 36 | ```bash 37 | cargo run --release --bin fpga --features fpga 38 | ``` 39 | 40 | ## Demo: Game-of-Life w/o FPGA 41 | 42 | [/demos/game-of-life/readme.md](/demos/game-of-life/readme.md) 43 | 44 | To run the demo *without* FPT acceleration: 45 | ```bash 46 | cargo run --release --bin game-of-life 47 | ``` 48 | 49 | To run the demo *with* FPT acceleration: 50 | ```bash 51 | cargo run --release --bin game-of-life --features fpga 52 | ``` 53 | -------------------------------------------------------------------------------- /CITATION: -------------------------------------------------------------------------------- 1 | @inproceedings{10.1145/3576915.3623159, 2 | author = {Van Beirendonck, Michiel and D'Anvers, Jan-Pieter and Turan, Furkan and Verbauwhede, Ingrid}, 3 | title = {FPT: A Fixed-Point Accelerator for Torus Fully Homomorphic Encryption}, 4 | year = {2023}, 5 | isbn = {9798400700507}, 6 | publisher = {Association for Computing Machinery}, 7 | address = {New York, NY, USA}, 8 | url = {https://doi-org.kuleuven.e-bronnen.be/10.1145/3576915.3623159}, 9 | doi = {10.1145/3576915.3623159}, 10 | abstract = {Fully Homomorphic Encryption (FHE) is a technique that allows computation on encrypted data. It has the potential to drastically change privacy considerations in the cloud, but high computational and memory overheads are preventing its broad adoption. TFHE is a promising Torus-based FHE scheme that heavily relies on bootstrapping, the noise-removal tool invoked after each encrypted logical/arithmetical operation. We present FPT, a Fixed-Point FPGA accelerator for TFHE bootstrapping. FPT is the first hardware accelerator to heavily exploit the inherent noise present in FHE calculations. Instead of double or single-precision floating-point arithmetic, it implements TFHE bootstrapping entirely with approximate fixed-point arithmetic. Using an in-depth analysis of noise propagation in bootstrapping FFT computations, FPT is able to use noise-trimmed fixed-point representations that are up to 50\% smaller than prior implementations that prefer floating-point or integer FFTs. FPT is built as a streaming processor inspired by traditional streaming DSPs: it instantiates directly cascaded high-throughput computational stages, with minimal control logic and routing networks. We explore different throughput-balanced compositions of streaming kernels with a user-configurable streaming width in order to construct a full bootstrapping pipeline. Our proposed approach allows 100\% utilization of arithmetic units and requires only small bootstrapping key cache, enabling an entirely compute-bound bootstrapping throughput of 1 BS / 35us. This is in stark contrast to the established classical CPU approach to FHE bootstrapping acceleration, which is typically constrained by memory and bandwidth. FPT is fully implemented and evaluated as a bootstrapping FPGA kernel for an Alveo U280 datacenter accelerator card. FPT achieves two to three orders of magnitude higher bootstrapping throughput than existing CPU-based implementations, and 2.5x higher throughput compared to recent ASIC emulation experiments.}, 11 | booktitle = {Proceedings of the 2023 ACM SIGSAC Conference on Computer and Communications Security}, 12 | pages = {741–755}, 13 | numpages = {15}, 14 | keywords = {hardware accelerator, fpga, fully homomorphic encryption, tfhe}, 15 | location = {, Copenhagen, Denmark, }, 16 | series = {CCS '23} 17 | } 18 | -------------------------------------------------------------------------------- /demos/game-of-life/src/gol/graph.rs: -------------------------------------------------------------------------------- 1 | use tfhe::boolean::prelude::*; 2 | 3 | pub use ggez::{conf, event, graphics, Context, GameResult}; 4 | 5 | use super::board::*; 6 | 7 | pub struct MainState { 8 | board: Board, 9 | // time_step: Duration, 10 | first_frame: bool, 11 | pixel_size: usize, 12 | col1: (f32, f32, f32), 13 | col2: (f32, f32, f32), 14 | server_key: ServerKey, 15 | zeros: (Vec, Vec, Vec), 16 | client_key: ClientKey, 17 | } 18 | 19 | impl MainState { 20 | pub fn new( 21 | board: Board, 22 | config: &Config, 23 | server_key: ServerKey, 24 | zeros: (Vec, Vec, Vec), 25 | client_key: ClientKey, 26 | ) -> Result> { 27 | Ok(MainState { 28 | board, 29 | // time_step: Duration::from_micros(config.wait_time_micros), 30 | first_frame: true, 31 | pixel_size: config.pixel_size, 32 | col1: config.col1, 33 | col2: config.col2, 34 | server_key, 35 | zeros, 36 | client_key, 37 | }) 38 | } 39 | } 40 | 41 | impl event::EventHandler for MainState { 42 | fn update(&mut self, _ctx: &mut Context) -> GameResult { 43 | if self.first_frame { 44 | self.first_frame = false; 45 | } else { 46 | // sleep(self.time_step); 47 | use std::time::Instant; 48 | let now = Instant::now(); 49 | self.board.update(&self.server_key, &self.zeros); 50 | let elapsed = now.elapsed(); 51 | println!("Elapsed: {:.2?}", elapsed); 52 | } 53 | Ok(()) 54 | } 55 | 56 | fn draw(&mut self, mut ctx: &mut Context) -> GameResult { 57 | // clear the window 58 | graphics::clear(ctx, [self.col1.0, self.col1.1, self.col1.2, 1.].into()); 59 | 60 | for i in 0..self.board.dimensions.0 { 61 | for j in 0..self.board.dimensions.1 { 62 | if self 63 | .client_key 64 | .decrypt(&self.board.states[i * self.board.dimensions.1 + j]) 65 | { 66 | let pixel = graphics::MeshBuilder::new() 67 | .rectangle( 68 | graphics::DrawMode::Fill(graphics::FillOptions::DEFAULT), 69 | graphics::Rect::new(0., 0., self.pixel_size as f32, self.pixel_size as f32), 70 | graphics::Color::new(self.col2.0, self.col2.1, self.col2.2, 1.), 71 | ) 72 | .unwrap() 73 | .build(&mut ctx) 74 | .unwrap(); 75 | graphics::draw( 76 | ctx, 77 | &pixel, 78 | graphics::DrawParam::new().offset([ 79 | -((j * self.pixel_size) as f32), 80 | -((i * self.pixel_size) as f32), 81 | ]), 82 | )?; 83 | } 84 | } 85 | } 86 | 87 | graphics::present(ctx)?; 88 | Ok(()) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /demos/game-of-life/src/game-of-life.rs: -------------------------------------------------------------------------------- 1 | use crossterm::event::{DisableMouseCapture, EnableMouseCapture}; 2 | use crossterm::execute; 3 | use crossterm::terminal::{ 4 | disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen, 5 | }; 6 | use std::error::Error; 7 | use std::io; 8 | 9 | use std::time::Instant; 10 | use tui::backend::{Backend, CrosstermBackend}; 11 | use tui::layout::Rect; 12 | use tui::widgets::{Block, Borders}; 13 | use tui::{Frame, Terminal}; 14 | 15 | use homomorphic_game_of_life::gol::*; 16 | 17 | #[cfg(feature = "fpga")] 18 | use tfhe::boolean::server_key::FpgaGates; 19 | 20 | use std::env; 21 | use tfhe::boolean::prelude::*; 22 | 23 | fn main() -> Result<(), Box> { 24 | // setup terminal 25 | enable_raw_mode()?; 26 | let mut stdout = io::stdout(); 27 | execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; 28 | let backend = CrosstermBackend::new(stdout); 29 | let mut terminal = Terminal::new(backend)?; 30 | 31 | let mut current_dir = env::current_dir().unwrap(); 32 | current_dir.push("demos/game-of-life/initial_state"); 33 | let csv_path = current_dir.to_str().unwrap(); 34 | 35 | // read the dimensions and initial state 36 | let (dimensions, initial_state): ((usize, usize), Vec) = read_csv(csv_path).unwrap(); 37 | 38 | // generate the client key 39 | let client_key = ClientKey::new(&DEMO_PARAMETERS); 40 | 41 | // generate the server key 42 | let server_key = ServerKey::new(&client_key); 43 | 44 | #[cfg(feature = "fpga")] 45 | server_key.enable_fpga(); 46 | 47 | // encrypt false three times 48 | let mut zeros = ( 49 | Vec::::new(), 50 | Vec::::new(), 51 | Vec::::new(), 52 | ); 53 | for _ in 0..FPGA_BOOTSTRAP_PACKING { 54 | zeros.0.push(client_key.encrypt(false)); 55 | zeros.1.push(client_key.encrypt(false)); 56 | zeros.2.push(client_key.encrypt(false)); 57 | } 58 | 59 | // encrypt the initial configuration 60 | let initial_state: Vec = initial_state 61 | .into_iter() 62 | .map(|x| client_key.encrypt(x)) 63 | .collect(); 64 | 65 | // build the board 66 | let board = Board::new(dimensions.1, initial_state, client_key); 67 | 68 | // create app and run it 69 | let res = run_app(&mut terminal, board, &server_key, &zeros); 70 | 71 | // restore terminal 72 | disable_raw_mode()?; 73 | execute!( 74 | terminal.backend_mut(), 75 | LeaveAlternateScreen, 76 | DisableMouseCapture 77 | )?; 78 | terminal.show_cursor()?; 79 | 80 | if let Err(err) = res { 81 | println!("{:?}", err) 82 | } 83 | 84 | Ok(()) 85 | } 86 | 87 | fn run_app( 88 | terminal: &mut Terminal, 89 | mut board: Board, 90 | server_key: &ServerKey, 91 | zeros: &(Vec, Vec, Vec), 92 | ) -> io::Result<()> { 93 | loop { 94 | terminal.draw(|f| ui(f, board.clone()))?; 95 | let now = Instant::now(); 96 | board.update(server_key, zeros); 97 | board.tick_time = now.elapsed(); 98 | } 99 | } 100 | 101 | fn ui(f: &mut Frame, board: Board) { 102 | // Surrounding block 103 | let size = f.size(); 104 | let block = Block::default() 105 | .borders(Borders::ALL) 106 | .title("Conway's Game of Life"); 107 | f.render_widget(block, size); 108 | 109 | // Game block 110 | let block = Block::default().borders(Borders::ALL); 111 | f.render_widget( 112 | block, 113 | Rect::new( 114 | 1, 115 | 1, 116 | (board.dimensions.0 + 2) as u16, 117 | (board.dimensions.1 + 2) as u16, 118 | ), 119 | ); 120 | 121 | // Game board 122 | f.render_widget(board, f.size()); 123 | } 124 | -------------------------------------------------------------------------------- /demos/aws_afi_gen.md: -------------------------------------------------------------------------------- 1 | # Creating a new `.awsxclbin` from `.xclbin` 2 | 3 | The FPGA image is called `.xclbin`. However, this file cannot be directly used when working on AWS. This file should be submitted to AWS for making it convert it to an AFI (Amazon FPGA Image). This image will be hosted by AWS, and will be accessed by the demo apps through its corresponding `.awsxclbin` file. 4 | 5 | In fact, we already provide an `.awsxclbin` file, which is set publicly accessible on all known AWS regions that employ F1 instances. However, if new regions are introduced in future, you can follow the instructions below, (on an F1 instance), to regenearte a new AFI for that region. 6 | 7 | ___ 8 | 9 | Before the start, you should follow the instructions on [aws_f1_setup.md](./aws_f1_setup.md) for setting up an F1 instance. 10 | 11 | You need the version of this repository that includes the `.xclbin` file. As that file is >650 MB, we hosted that version on [Zenodo](https://zenodo.org/), which is the CERN Data Centre-backed research data repository. You can reach to that archive version by [https://zenodo.org/records/10158881](https://zenodo.org/records/10158881). 12 | 13 | Let's still work on this GitHub version: 14 | 15 | ```bash 16 | FPT_DIR=~/fpt-demo 17 | git clone https://github.com/KULeuven-COSIC/fpt-demo.git $FPT_DIR 18 | cd $FPT_DIR 19 | ``` 20 | 21 | Get the `.xclbin` from the Zenodo version: 22 | 23 | ``` 24 | cp /demos/accel.xclbin $FPT_DIR/demos/accel.xclbin 25 | ``` 26 | 27 | ___ 28 | 29 | As the first step you should configure the `aws-cli``. Run the command below and provide your credentials and the region of preference. Remember that you should pick the region that has F1 instances. 30 | 31 | ```bash 32 | aws configure 33 | ``` 34 | 35 | Let's set the environmental variables, in case, they are not already set: 36 | 37 | ```bash 38 | AWS_REGION=eu-west-1 39 | AWS_DIR=~/aws-fpga 40 | FPT_DIR=~/fpt-demo 41 | FPT_IMAGE="$FPT_DIR/demos/accel.awsxclbin" 42 | XCL_BIN="$FPT_DIR/demos/accel.xclbin" 43 | ``` 44 | 45 | It is preferrable to work a folder within a temporary folder: 46 | 47 | ```bash 48 | tmp_dir=~/afi-tmp 49 | mkdir -p $tmp_dir 50 | pushd $tmp_dir 51 | ``` 52 | 53 | Create S3 buckets for AWS to place the AFI related files at its generation. 54 | 55 | ```bash 56 | bucket_name=tfhe-fpga 57 | dcp_key=dcps 58 | logs_key=logs 59 | 60 | # Create an S3 bucket 61 | aws s3 mb s3://${bucket_name} --region ${AWS_REGION} 62 | 63 | # Create a temp file 64 | # Choose a dcp folder name 65 | touch DCP_FILES_GO_HERE.txt 66 | aws s3 cp DCP_FILES_GO_HERE.txt s3://${bucket_name}/${dcp_key}/ 67 | rm -f DCP_FILES_GO_HERE.txt 68 | 69 | # Create a temp file 70 | # Choose a logs folder name 71 | touch LOG_FILES_GO_HERE.txt 72 | aws s3 cp LOG_FILES_GO_HERE.txt s3://${bucket_name}/${logs_key}/ 73 | rm -f LOG_FILES_GO_HERE.txt 74 | ``` 75 | 76 | Start AFI Creation 77 | 78 | ```bash 79 | export RELEASE_VER=2021.2 80 | 81 | pushd $AWS_DIR 82 | source hdk_setup.sh 83 | popd 84 | 85 | $VITIS_DIR/tools/create_vitis_afi.sh -xclbin=${XCL_BIN} -s3_bucket=${bucket_name} -s3_dcp_key=${dcp_key} -s3_logs_key=${logs_key} 86 | 87 | # Leave $tmp_dir dir 88 | popd 89 | ``` 90 | 91 | Extract the AFI ID from the created file: 92 | 93 | ```bash 94 | sudo yum install jq 95 | afi_id=$(jq -r '.FpgaImageId' $tmp_dir/*_afi_id.txt) 96 | echo $afi_id 97 | ``` 98 | 99 | Check the status of the AFI generation. The compilation might take several hours. You can execute the following command to check the status. Until the compilation finishes, the output of this command will indicate `"Code": "pending"`. 100 | 101 | ```bash 102 | aws ec2 describe-fpga-images --fpga-image-id $afi_id 103 | ``` 104 | 105 | Replace the existing `.awsxclbin` file with the newly created one: 106 | 107 | ```bash 108 | rm -f $FPT_IMAGE 109 | cp $tmp_dir/*.awsxclbin $FPT_IMAGE 110 | ``` 111 | -------------------------------------------------------------------------------- /demos/aws_f1_setup.md: -------------------------------------------------------------------------------- 1 | # Setting up the F1 Instance 2 | 3 | ## Creating an AWS account 4 | 5 | :exclamation: A new AWS account may not have the required quota allowance to run a `f1.2xlarge` type instance. If that is the case, one should file a [quota increase request](https://aws.amazon.com/getting-started/hands-on/request-service-quota-increase/) for the `Running On-Demand F instances` service. Make sure to request a limit increase to `8 instances`, since `f1.2xlarge` contains 8 vCPUs. The quota increase may take up to a few days to process. 6 | 7 | In your communication to AWS, please pay attention that the F1 access permissions are tied to a given region. Though we provide the demo application in this repository, the actual FPGA image is hosted by AWS. We make that image publicly available in all the F1 instance regions of today, which are `us-east-1`, `us-west-2`, `eu-west-1`, `ap-southeast-2`, `eu-central-1` and `eu-west-2`. If more regions with F1 instances appear in future, we will make the image available in those regions too. If you notice that we are late to do this, you can create an issue. 8 | 9 | :exclamation: In fact, we also prepared the instructions in [aws_afi_gen.md](./aws_afi_gen.md), so that you can create the image yourself from the actual image file (`.xclbin`); however, the file is 620MB (when compressed 570MB), hence exceeds the file size limitations of GitHub. We will look for a solution to share it in another way. 10 | 11 | ## Instantiate an F1 Instance 12 | 13 | Instantiate an `f1.2xlarge` instance with `FPGA Developer AMI`. This AMI is not free of charge, but it is easier to work with for short-time uses of this demo. Otherwise, the XRT installation is actually more complicated than described [here](https://github.com/aws/aws-fpga/blob/master/Vitis/docs/XRT_installation_instructions.md). 14 | 15 | Please note that, the instructions below are NOT prepared for the `Amazon Linux 2` version, or the one from `Community AMI`. 16 | 17 | ## Preparation 18 | 19 | Once the instance is initialised, login and install the dependencies. 20 | 21 | At first, we should update the package repo URLs. Unfortunately, `FPGA Developer AMI` is based on CentOS 7, which is arriving to its End-of-Life. Its default URL to package download mirror is already obsolate. As a workaround, we will start with updating the `.repo` files: 22 | 23 | ```bash 24 | sudo sed -i s/mirror.centos.org/vault.centos.org/g /etc/yum.repos.d/*.repo 25 | sudo sed -i s/^#.*baseurl=http/baseurl=http/g /etc/yum.repos.d/*.repo 26 | sudo sed -i s/^mirrorlist=http/#mirrorlist=http/g /etc/yum.repos.d/*.repo 27 | ``` 28 | Note that: we might need to re-run these commands, if the following `yum` commands fail; one can introduce a new `yum.repo`, causing the next one to fail. 29 | 30 | Let's start with the basics: 31 | ```bash 32 | sudo yum update 33 | sudo yum upgrade 34 | sudo yum group install "Development Tools" 35 | ``` 36 | ___ 37 | 38 | Next, we will install Rust for compiling `tfhe-rs`. 39 | 40 | First, we should install a few dependencies. 41 | 42 | Install `alsa`: 43 | ```bash 44 | sudo yum install alsa-lib-devel 45 | ``` 46 | 47 | Install `systemd-devel`: 48 | ```bash 49 | sudo yum install systemd-devel 50 | ``` 51 | 52 | This one is to install a newer version of `clang`, the default of which is lower than 3.5, and not compatible: 53 | ```bash 54 | sudo yum install centos-release-scl 55 | sudo yum install llvm-toolset-7 56 | scl enable llvm-toolset-7 bash 57 | ``` 58 | 59 | Now, install Rust: 60 | ```bash 61 | curl https://sh.rustup.rs -sSf > RUSTUP.sh 62 | sh RUSTUP.sh -y 63 | rm RUSTUP.sh 64 | source "$HOME/.cargo/env" 65 | ``` 66 | 67 | The demo uses the `nightly` version of Rust, and preferrably the `2023-11-29` version used at the preparation of this demo: 68 | ```bash 69 | rustup install nightly-2023-11-29 70 | rustup default nightly-2023-11-29 71 | ``` 72 | ___ 73 | 74 | For preparing the FPGA runtime, we will need the `aws-fpga` repo: 75 | ```bash 76 | AWS_DIR=~/aws-fpga 77 | git clone https://github.com/aws/aws-fpga.git $AWS_DIR 78 | ``` 79 | 80 | ## Source the libraries 81 | 82 | With the below commands, you need to make the binaries and libraries accessible for compiling the demo application. 83 | 84 | ```bash 85 | scl enable llvm-toolset-7 bash 86 | source /opt/xilinx/xrt/setup.sh 87 | AWS_DIR=~/aws-fpga 88 | source $AWS_DIR/vitis_runtime_setup.sh 89 | ``` 90 | 91 | Now, you are ready to go. You can follow the instructions on the [readme](./readme.md) file for running the demos. 92 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | - Demonstrating empathy and kindness toward other people 21 | - Being respectful of differing opinions, viewpoints, and experiences 22 | - Giving and gracefully accepting constructive feedback 23 | - Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | - Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | - The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | - Trolling, insulting or derogatory comments, and personal or political attacks 33 | - Public or private harassment 34 | - Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | - Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported by contacting us anonymously through [this form](https://forms.gle/569j3cZqGRFgrR3u9). 63 | All complaints will be reviewed and investigated promptly and fairly. 64 | 65 | All community leaders are obligated to respect the privacy and security of the 66 | reporter of any incident. 67 | 68 | ## Enforcement guidelines 69 | 70 | Community leaders will follow these Community Impact Guidelines in determining 71 | the consequences for any action they deem in violation of this Code of Conduct: 72 | 73 | ### 1. Correction 74 | 75 | **Community Impact**: Use of inappropriate language or other behavior deemed 76 | unprofessional or unwelcome in the community. 77 | 78 | **Consequence**: A private, written warning from community leaders, providing 79 | clarity around the nature of the violation and an explanation of why the 80 | behavior was inappropriate. A public apology may be requested. 81 | 82 | ### 2. Warning 83 | 84 | **Community Impact**: A violation through a single incident or series of 85 | actions. 86 | 87 | **Consequence**: A warning with consequences for continued behavior. No 88 | interaction with the people involved, including unsolicited interaction with 89 | those enforcing the Code of Conduct, for a specified period of time. This 90 | includes avoiding interactions in community spaces as well as external channels 91 | like social media. Violating these terms may lead to a temporary or permanent 92 | ban. 93 | 94 | ### 3. Temporary ban 95 | 96 | **Community Impact**: A serious violation of community standards, including 97 | sustained inappropriate behavior. 98 | 99 | **Consequence**: A temporary ban from any sort of interaction or public 100 | communication with the community for a specified period of time. No public or 101 | private interaction with the people involved, including unsolicited interaction 102 | with those enforcing the Code of Conduct, is allowed during this period. 103 | Violating these terms may lead to a permanent ban. 104 | 105 | ### 4. Permanent ban 106 | 107 | **Community Impact**: Demonstrating a pattern of violation of community 108 | standards, including sustained inappropriate behavior, harassment of an 109 | individual, or aggression toward or disparagement of classes of individuals. 110 | 111 | **Consequence**: A permanent ban from any sort of public interaction within the 112 | community. 113 | 114 | ## Attribution 115 | 116 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 117 | version 2.1, available at 118 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 119 | 120 | Community Impact Guidelines were inspired by 121 | [Mozilla's code of conduct enforcement ladder][mozilla coc]. 122 | 123 | For answers to common questions about this code of conduct, see the FAQ at 124 | [https://www.contributor-covenant.org/faq][faq]. Translations are available at 125 | [https://www.contributor-covenant.org/translations][translations]. 126 | 127 | [faq]: https://www.contributor-covenant.org/faq 128 | [homepage]: https://www.contributor-covenant.org 129 | [mozilla coc]: https://github.com/mozilla/diversity 130 | [translations]: https://www.contributor-covenant.org/translations 131 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 132 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # FPT: a Fixed-Point Accelerator for Torus Fully Homomorphic Encryption 2 | 3 | FPT accelerates the TFHE bootstrapping operation on FPGA. 4 | 5 | This repository provides a demo of FPT's performance improvements over an encrypted version of [Conway's Game-of-Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life) implemented with [TFHE-rs](https://github.com/zama-ai/tfhe-rs/). The code in this repo is a fork of TFHE-rs, extending it with the use of FPT and with the demos implemented in `/demo` folder. 6 | 7 | The design of FPT is published at ACM CCS 2023. Please consider citing our work if you use or build upon the results in this repository. 8 | 9 |
10 | ACM Ref 11 |
12 | Michiel Van Beirendonck, Jan-Pieter D'Anvers, Furkan Turan, and Ingrid Verbauwhede. 2023. FPT: A Fixed-Point Accelerator for Torus Fully Homomorphic Encryption. In Proceedings of the 2023 ACM SIGSAC Conference on Computer and Communications Security (CCS '23). Association for Computing Machinery, New York, NY, USA, 741–755. https://doi.org/10.1145/3576915.3623159 13 |
14 | 15 |
16 | BibTex 17 |
18 | 19 | 20 | 21 | 22 | 23 | ``` 24 | @inproceedings{10.1145/3576915.3623159, 25 | author = {Van Beirendonck, Michiel and D'Anvers, Jan-Pieter and Turan, Furkan and Verbauwhede, Ingrid}, 26 | title = {FPT: A Fixed-Point Accelerator for Torus Fully Homomorphic Encryption}, 27 | year = {2023}, 28 | isbn = {9798400700507}, 29 | publisher = {Association for Computing Machinery}, 30 | address = {New York, NY, USA}, 31 | url = {https://doi-org.kuleuven.e-bronnen.be/10.1145/3576915.3623159}, 32 | doi = {10.1145/3576915.3623159}, 33 | abstract = {Fully Homomorphic Encryption (FHE) is a technique that allows computation on encrypted data. It has the potential to drastically change privacy considerations in the cloud, but high computational and memory overheads are preventing its broad adoption. TFHE is a promising Torus-based FHE scheme that heavily relies on bootstrapping, the noise-removal tool invoked after each encrypted logical/arithmetical operation. We present FPT, a Fixed-Point FPGA accelerator for TFHE bootstrapping. FPT is the first hardware accelerator to heavily exploit the inherent noise present in FHE calculations. Instead of double or single-precision floating-point arithmetic, it implements TFHE bootstrapping entirely with approximate fixed-point arithmetic. Using an in-depth analysis of noise propagation in bootstrapping FFT computations, FPT is able to use noise-trimmed fixed-point representations that are up to 50\% smaller than prior implementations that prefer floating-point or integer FFTs. FPT is built as a streaming processor inspired by traditional streaming DSPs: it instantiates directly cascaded high-throughput computational stages, with minimal control logic and routing networks. We explore different throughput-balanced compositions of streaming kernels with a user-configurable streaming width in order to construct a full bootstrapping pipeline. Our proposed approach allows 100\% utilization of arithmetic units and requires only small bootstrapping key cache, enabling an entirely compute-bound bootstrapping throughput of 1 BS / 35us. This is in stark contrast to the established classical CPU approach to FHE bootstrapping acceleration, which is typically constrained by memory and bandwidth. FPT is fully implemented and evaluated as a bootstrapping FPGA kernel for an Alveo U280 datacenter accelerator card. FPT achieves two to three orders of magnitude higher bootstrapping throughput than existing CPU-based implementations, and 2.5x higher throughput compared to recent ASIC emulation experiments.}, 34 | booktitle = {Proceedings of the 2023 ACM SIGSAC Conference on Computer and Communications Security}, 35 | pages = {741–755}, 36 | numpages = {15}, 37 | keywords = {hardware accelerator, fpga, tfhe, fully homomorphic encryption}, 38 | location = {, Copenhagen, Denmark, }, 39 | series = {CCS '23} 40 | } 41 | ``` 42 |
43 | 44 | ## Abstract 45 | 46 | Fully Homomorphic Encryption (FHE) is a technique that allows computation on encrypted data. It has the potential to drastically change privacy considerations in the cloud, but high computational and memory overheads are preventing its broad adoption. TFHE is a promising Torus-based FHE scheme that heavily relies on bootstrapping, the noise-removal tool invoked after each encrypted logical/arithmetical operation. 47 | 48 | We present FPT, a Fixed-Point FPGA accelerator for TFHE bootstrapping. FPT is the first hardware accelerator to heavily exploit the inherent noise present in FHE calculations. Instead of double or single-precision floating-point arithmetic, it implements TFHE bootstrapping entirely with approximate fixed-point arithmetic. Using an in-depth analysis of noise propagation in bootstrapping FFT computations, FPT is able to use noise-trimmed fixed-point representations that are up to 50% smaller than prior implementations that prefer floating-point or integer FFTs. 49 | 50 | FPT is built as a streaming processor inspired by traditional streaming DSPs: it instantiates directly cascaded high-throughput computational stages, with minimal control logic and routing networks. We explore different throughput-balanced compositions of streaming kernels with a user-configurable streaming width in order to construct a full bootstrapping pipeline. Our proposed approach allows 100% utilization of arithmetic units and requires only small bootstrapping key cache, enabling an entirely compute-bound bootstrapping throughput of 1 BS / 35us. This is in stark contrast to the established classical CPU approach to FHE bootstrapping acceleration, which is typically constrained by memory and bandwidth. 51 | 52 | FPT is fully implemented and evaluated as a bootstrapping FPGA kernel for an Alveo U280 datacenter accelerator card. FPT achieves two to three orders of magnitude higher bootstrapping throughput than existing CPU-based implementations, and 2.5 times higher throughput compared to recent ASIC emulation experiments. 53 | 54 | ## Demo of Conway's Game of Life 55 | 56 | This demo (`/demos/game-of-life/`) runs the Game of Life over TFHE with or without FPGA acceleration. 57 | 58 | The screen recording below shows the acceleration of FPT (on the right) over the native version on `TFHE-rs` (on the left). Note that, FPT's acceleration is more capable than seen in this video; however, a reduced parameter set is preferred here to have the software version update the frames in acceptable time. 59 | 60 | https://github.com/KULeuven-COSIC/fpt-demo/assets/4849663/73bd6242-6e77-44d4-9287-ad7d17e965f7 61 | 62 | In running Game of Life over TFHE, the server receives an encrypted initial board configuration with encrypted cell states. Update rules are translated into Boolean equations, which are calculated by the server using encrypted gate arithmetic. Updating a single cell state requires exactly 44 encrypted gate computations, disregarding the cheaper NOT gates that do not include a bootstrap. As a whole, the encrypted Game of Life consists of a mix of homomorphic AND, XOR, OR, and NOT gates. These operations and their parallel computation should help estimating FPT's performance on a variety of applications. In addition, this is an application which demonstrates the performance improvements live: the FPGA-accelerated board updates visually appear much quicker than the software counterpart. 63 | 64 | ## Running the Demo of Conway's Game of Life 65 | 66 | To run the demo yourself, FPT is made available on [AWS F1 instances](https://aws.amazon.com/ec2/instance-types/f1/). As noted above, this demo version prefers a reduced parameter set to have the software version of the board update the frames in acceptable time. 67 | 68 | The description of how to run this demo yourself is detailed in [`demos/readme.md`](demos/readme.md). 69 | 70 | --- 71 | 72 | Michiel Van Beirendonck, Jan-Pieter D'Anvers, Furkan Turan, and Ingrid Verbauwhede. 2023. FPT: A Fixed-Point Accelerator for Torus Fully Homomorphic Encryption. In Proceedings of the 2023 ACM SIGSAC Conference on Computer and Communications Security (CCS '23). Association for Computing Machinery, New York, NY, USA, 741–755. https://doi.org/10.1145/3576915.3623159 73 | 74 | -------------------------------------------------------------------------------- /demos/game-of-life/src/gol/board.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use tfhe::boolean::prelude::*; 4 | 5 | use super::operators::is_alive; 6 | use tui::buffer::Buffer; 7 | use tui::layout::Rect; 8 | use tui::style::{Color, Style}; 9 | 10 | use tui::widgets::Widget; 11 | 12 | /// a Game of Life board structure 13 | /// 14 | /// # Fields 15 | /// 16 | /// * `dimensions`: the dimensions of the board 17 | /// * `states`: encrypted states of the cells 18 | #[derive(Clone)] 19 | pub struct Board { 20 | pub dimensions: (usize, usize), 21 | pub states: Vec, 22 | pub client_key: ClientKey, 23 | pub tick_time: Duration, 24 | } 25 | 26 | impl Board { 27 | /// create a new board 28 | /// 29 | /// # Arguments 30 | /// 31 | /// * `n_cols`: number of columns 32 | /// * `states`: encrypted states of the cells in the initial configuration 33 | /// 34 | /// # Example 35 | /// 36 | /// ``` 37 | /// use homomorphic_game_of_life::*; 38 | /// 39 | /// // numbers of rows and columns in the board 40 | /// let (n_rows, n_cols): (usize, usize) = (6,6); 41 | /// 42 | /// // generate the client key 43 | /// let (client_key, _) = gen_keys(); 44 | /// 45 | /// // initial configuration 46 | /// let states = vec![true, false, false, false, false, false, 47 | /// false, true, true, false, false, false, 48 | /// true, true, false, false, false, false, 49 | /// false, false, false, false, false, false, 50 | /// false, false, false, false, false, false, 51 | /// false, false, false, false, false, false]; 52 | /// 53 | /// // encrypt the initial configuration 54 | /// let states: Vec = states.into_iter().map(|x| client_key.encrypt(x)).collect(); 55 | /// 56 | /// // build the board 57 | /// let mut board = Board::new(n_cols, states); 58 | /// ``` 59 | pub fn new(n_cols: usize, states: Vec, client_key: ClientKey) -> Board { 60 | let n_rows = states.len() / n_cols; 61 | Board { 62 | dimensions: (n_rows, n_cols), 63 | states, 64 | client_key, 65 | tick_time: Duration::default(), 66 | } 67 | } 68 | 69 | /// update the board 70 | /// 71 | /// # Arguments 72 | /// 73 | /// * `server_key`: the server key 74 | /// * `zeros`: three encryption of `false` 75 | /// 76 | /// # Example 77 | /// 78 | /// ``` 79 | /// use homomorphic_game_of_life::*; 80 | /// 81 | /// // numbers of rows and columns in the board 82 | /// let (n_rows, n_cols): (usize, usize) = (6,6); 83 | /// 84 | /// // generate the keys 85 | /// let (client_key, server_key) = gen_keys(); 86 | /// 87 | /// // encrypt false three times 88 | /// let zeros = (client_key.encrypt(false), client_key.encrypt(false), client_key.encrypt(false)); 89 | /// 90 | /// // initial configuration 91 | /// let states = vec![true, false, false, false, false, false, 92 | /// false, true, true, false, false, false, 93 | /// true, true, false, false, false, false, 94 | /// false, false, false, false, false, false, 95 | /// false, false, false, false, false, false, 96 | /// false, false, false, false, false, false]; 97 | /// 98 | /// // encrypt the initial configuration 99 | /// let states: Vec = states.into_iter().map(|x| client_key.encrypt(x)).collect(); 100 | /// 101 | /// // build the board 102 | /// let mut board = Board::new(n_cols, states); 103 | /// 104 | /// // update the board 105 | /// board.update(&server_key, &zeros); 106 | /// 107 | /// // decrypt and show the board 108 | /// for i in 0..n_rows { 109 | /// println!(""); 110 | /// for j in 0..n_rows { 111 | /// if client_key.decrypt(&board.states[i*n_cols+j]) { 112 | /// print!("█"); 113 | /// } else { 114 | /// print!("░"); 115 | /// } 116 | /// } 117 | /// } 118 | /// println!(""); 119 | /// ``` 120 | pub fn update( 121 | &mut self, 122 | server_key: &ServerKey, 123 | zeros: &(Vec, Vec, Vec), 124 | ) { 125 | let nx = self.dimensions.0; 126 | let ny = self.dimensions.1; 127 | 128 | let mut new_states = Vec::::new(); 129 | 130 | for k1 in (0..nx * ny).step_by(FPGA_BOOTSTRAP_PACKING) { 131 | let mut cell_p = Vec::::new(); 132 | let mut neighbours_p = [ 133 | Vec::::new(), 134 | Vec::::new(), 135 | Vec::::new(), 136 | Vec::::new(), 137 | Vec::::new(), 138 | Vec::::new(), 139 | Vec::::new(), 140 | Vec::::new(), 141 | ]; 142 | let end = std::cmp::min(FPGA_BOOTSTRAP_PACKING, nx * ny - k1); 143 | 144 | for k2 in 0..end { 145 | let i = (k1 + k2) / ny; 146 | let j = (k1 + k2) % ny; 147 | 148 | let im = if i == 0 { nx - 1 } else { i - 1 }; 149 | let ip = if i == nx - 1 { 0 } else { i + 1 }; 150 | let jm = if j == 0 { ny - 1 } else { j - 1 }; 151 | let jp = if j == ny - 1 { 0 } else { j + 1 }; 152 | 153 | // get the neighbours, with periodic boundary conditions 154 | neighbours_p[0].push(self.states[im * ny + jm].clone()); 155 | neighbours_p[1].push(self.states[im * ny + j].clone()); 156 | neighbours_p[2].push(self.states[im * ny + jp].clone()); 157 | neighbours_p[3].push(self.states[i * ny + jm].clone()); 158 | neighbours_p[4].push(self.states[i * ny + jp].clone()); 159 | neighbours_p[5].push(self.states[ip * ny + jm].clone()); 160 | neighbours_p[6].push(self.states[ip * ny + j].clone()); 161 | neighbours_p[7].push(self.states[ip * ny + jp].clone()); 162 | 163 | cell_p.push(self.states[i * ny + j].clone()); 164 | } 165 | 166 | is_alive(&server_key, cell_p, neighbours_p, zeros, &mut new_states); 167 | } 168 | 169 | // update the board 170 | self.states = new_states; 171 | } 172 | } 173 | 174 | // config structure 175 | pub struct Config { 176 | pub wait_time_micros: u64, 177 | pub pixel_size: usize, 178 | pub dimensions: (usize, usize), 179 | pub col1: (f32, f32, f32), 180 | pub col2: (f32, f32, f32), 181 | } 182 | 183 | impl Config { 184 | pub fn read(fname: &str) -> Result> { 185 | let err_message = "Missing argument in the config file"; 186 | 187 | // load the content 188 | let content = std::fs::read_to_string(fname)?; 189 | 190 | // separate the nubers 191 | let mut content = content.split(' '); 192 | 193 | // read the content 194 | Ok(Config { 195 | wait_time_micros: content.next().ok_or(err_message)?.parse::()?, 196 | pixel_size: content.next().ok_or(err_message)?.parse::()?, 197 | dimensions: ( 198 | content.next().ok_or(err_message)?.parse::()?, 199 | content.next().ok_or(err_message)?.parse::()?, 200 | ), 201 | col1: ( 202 | content.next().ok_or(err_message)?.parse::()?, 203 | content.next().ok_or(err_message)?.parse::()?, 204 | content.next().ok_or(err_message)?.parse::()?, 205 | ), 206 | col2: ( 207 | content.next().ok_or(err_message)?.parse::()?, 208 | content.next().ok_or(err_message)?.parse::()?, 209 | content.next().ok_or(err_message)?.parse::()?, 210 | ), 211 | }) 212 | } 213 | } 214 | 215 | /// read a state file, space- and newline-separated 216 | /// 217 | /// The file must contain only 0s and 1s and all rows need to have the same length. 218 | #[allow(clippy::type_complexity)] 219 | pub fn read_csv(fname: &str) -> Result<((usize, usize), Vec), Box> { 220 | // load the content 221 | let content = std::fs::read_to_string(fname)?; 222 | 223 | // separate in rows 224 | let content = content.split('\n').collect::>(); 225 | let n_rows = content.len() - 1; 226 | 227 | // number of columns 228 | let n_cols = content[0].split(' ').count() - 1; 229 | 230 | // load the data 231 | let mut data = Vec::::new(); 232 | for row in content { 233 | for el in row.split(' ') { 234 | if let Ok(x) = el.parse::() { 235 | data.push(x == 1) 236 | }; 237 | } 238 | } 239 | 240 | Ok(((n_rows, n_cols), data)) 241 | } 242 | 243 | impl Widget for Board { 244 | fn render(self, _area: Rect, buf: &mut Buffer) { 245 | for i in 0..self.dimensions.0 { 246 | for j in 0..self.dimensions.1 { 247 | if self 248 | .client_key 249 | .decrypt(&self.states[i * self.dimensions.1 + j]) 250 | { 251 | buf 252 | .get_mut(2 + i as u16, 2 + j as u16) 253 | .set_style(Style::default().bg(Color::White)); 254 | } 255 | } 256 | } 257 | 258 | buf.set_string( 259 | 1, 260 | (self.dimensions.1 + 3) as u16, 261 | format!( 262 | " Board update: {:?}", 263 | self.tick_time 264 | ), 265 | Style::default(), 266 | ); 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL:=$(shell /usr/bin/env which bash) 2 | OS:=$(shell uname) 3 | RS_CHECK_TOOLCHAIN:=$(shell cat toolchain.txt | tr -d '\n') 4 | CARGO_RS_CHECK_TOOLCHAIN:=+$(RS_CHECK_TOOLCHAIN) 5 | TARGET_ARCH_FEATURE:=$(shell ./scripts/get_arch_feature.sh) 6 | RS_BUILD_TOOLCHAIN:=$(shell \ 7 | ( (echo $(TARGET_ARCH_FEATURE) | grep -q x86) && echo stable) || echo $(RS_CHECK_TOOLCHAIN)) 8 | CARGO_RS_BUILD_TOOLCHAIN:=+$(RS_BUILD_TOOLCHAIN) 9 | CARGO_PROFILE?=release 10 | MIN_RUST_VERSION:=$(shell grep rust-version tfhe/Cargo.toml | cut -d '=' -f 2 | xargs) 11 | AVX512_SUPPORT?=OFF 12 | WASM_RUSTFLAGS:= 13 | BIG_TESTS_INSTANCE?=FALSE 14 | GEN_KEY_CACHE_MULTI_BIT_ONLY?=FALSE 15 | PARSE_INTEGER_BENCH_CSV_FILE?=tfhe_rs_integer_benches.csv 16 | FAST_TESTS?=FALSE 17 | FAST_BENCH?=FALSE 18 | BENCH_OP_FLAVOR?=DEFAULT 19 | # This is done to avoid forgetting it, we still precise the RUSTFLAGS in the commands to be able to 20 | # copy paste the command in the terminal and change them if required without forgetting the flags 21 | export RUSTFLAGS?=-C target-cpu=native 22 | 23 | ifeq ($(AVX512_SUPPORT),ON) 24 | AVX512_FEATURE=nightly-avx512 25 | else 26 | AVX512_FEATURE= 27 | endif 28 | 29 | ifeq ($(GEN_KEY_CACHE_MULTI_BIT_ONLY),TRUE) 30 | MULTI_BIT_ONLY=--multi-bit-only 31 | else 32 | MULTI_BIT_ONLY= 33 | endif 34 | 35 | # Variables used only for regex_engine example 36 | REGEX_STRING?='' 37 | REGEX_PATTERN?='' 38 | 39 | .PHONY: rs_check_toolchain # Echo the rust toolchain used for checks 40 | rs_check_toolchain: 41 | @echo $(RS_CHECK_TOOLCHAIN) 42 | 43 | .PHONY: rs_build_toolchain # Echo the rust toolchain used for builds 44 | rs_build_toolchain: 45 | @echo $(RS_BUILD_TOOLCHAIN) 46 | 47 | .PHONY: install_rs_check_toolchain # Install the toolchain used for checks 48 | install_rs_check_toolchain: 49 | @rustup toolchain list | grep -q "$(RS_CHECK_TOOLCHAIN)" || \ 50 | rustup toolchain install --profile default "$(RS_CHECK_TOOLCHAIN)" || \ 51 | ( echo "Unable to install $(RS_CHECK_TOOLCHAIN) toolchain, check your rustup installation. \ 52 | Rustup can be downloaded at https://rustup.rs/" && exit 1 ) 53 | 54 | .PHONY: install_rs_build_toolchain # Install the toolchain used for builds 55 | install_rs_build_toolchain: 56 | @( rustup toolchain list | grep -q "$(RS_BUILD_TOOLCHAIN)" && \ 57 | ./scripts/check_cargo_min_ver.sh \ 58 | --rust-toolchain "$(CARGO_RS_BUILD_TOOLCHAIN)" \ 59 | --min-rust-version "$(MIN_RUST_VERSION)" ) || \ 60 | rustup toolchain install --profile default "$(RS_BUILD_TOOLCHAIN)" || \ 61 | ( echo "Unable to install $(RS_BUILD_TOOLCHAIN) toolchain, check your rustup installation. \ 62 | Rustup can be downloaded at https://rustup.rs/" && exit 1 ) 63 | 64 | .PHONY: install_cargo_nextest # Install cargo nextest used for shortint tests 65 | install_cargo_nextest: install_rs_build_toolchain 66 | @cargo nextest --version > /dev/null 2>&1 || \ 67 | cargo $(CARGO_RS_BUILD_TOOLCHAIN) install cargo-nextest --locked || \ 68 | ( echo "Unable to install cargo nextest, unknown error." && exit 1 ) 69 | 70 | .PHONY: install_wasm_pack # Install wasm-pack to build JS packages 71 | install_wasm_pack: install_rs_build_toolchain 72 | @wasm-pack --version > /dev/null 2>&1 || \ 73 | cargo $(CARGO_RS_BUILD_TOOLCHAIN) install wasm-pack || \ 74 | ( echo "Unable to install cargo wasm-pack, unknown error." && exit 1 ) 75 | 76 | .PHONY: install_node # Install last version of NodeJS via nvm 77 | install_node: 78 | curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | $(SHELL) 79 | source ~/.bashrc 80 | $(SHELL) -i -c 'nvm install node' || \ 81 | ( echo "Unable to install node, unknown error." && exit 1 ) 82 | 83 | .PHONY: fmt # Format rust code 84 | fmt: install_rs_check_toolchain 85 | cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" fmt 86 | 87 | .PHONT: check_fmt # Check rust code format 88 | check_fmt: install_rs_check_toolchain 89 | cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" fmt --check 90 | 91 | .PHONY: clippy_core # Run clippy lints on core_crypto with and without experimental features 92 | clippy_core: install_rs_check_toolchain 93 | RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \ 94 | --features=$(TARGET_ARCH_FEATURE) \ 95 | -p tfhe -- --no-deps -D warnings 96 | RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \ 97 | --features=$(TARGET_ARCH_FEATURE),experimental \ 98 | -p tfhe -- --no-deps -D warnings 99 | 100 | .PHONY: clippy_boolean # Run clippy lints enabling the boolean features 101 | clippy_boolean: install_rs_check_toolchain 102 | RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \ 103 | --features=$(TARGET_ARCH_FEATURE),boolean \ 104 | -p tfhe -- --no-deps -D warnings 105 | 106 | .PHONY: clippy_shortint # Run clippy lints enabling the shortint features 107 | clippy_shortint: install_rs_check_toolchain 108 | RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \ 109 | --features=$(TARGET_ARCH_FEATURE),shortint \ 110 | -p tfhe -- --no-deps -D warnings 111 | 112 | .PHONY: clippy_integer # Run clippy lints enabling the integer features 113 | clippy_integer: install_rs_check_toolchain 114 | RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \ 115 | --features=$(TARGET_ARCH_FEATURE),integer \ 116 | -p tfhe -- --no-deps -D warnings 117 | 118 | .PHONY: clippy # Run clippy lints enabling the boolean, shortint, integer 119 | clippy: install_rs_check_toolchain 120 | RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy --all-targets \ 121 | --features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer \ 122 | -p tfhe -- --no-deps -D warnings 123 | 124 | .PHONY: clippy_c_api # Run clippy lints enabling the boolean, shortint and the C API 125 | clippy_c_api: install_rs_check_toolchain 126 | RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \ 127 | --features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api \ 128 | -p tfhe -- --no-deps -D warnings 129 | 130 | .PHONY: clippy_js_wasm_api # Run clippy lints enabling the boolean, shortint, integer and the js wasm API 131 | clippy_js_wasm_api: install_rs_check_toolchain 132 | RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \ 133 | --features=boolean-client-js-wasm-api,shortint-client-js-wasm-api,integer-client-js-wasm-api \ 134 | -p tfhe -- --no-deps -D warnings 135 | 136 | .PHONY: clippy_tasks # Run clippy lints on helper tasks crate. 137 | clippy_tasks: 138 | RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy \ 139 | -p tasks -- --no-deps -D warnings 140 | 141 | .PHONY: clippy_all_targets # Run clippy lints on all targets (benches, examples, etc.) 142 | clippy_all_targets: 143 | RUSTFLAGS="$(RUSTFLAGS)" cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" clippy --all-targets \ 144 | --features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer \ 145 | -p tfhe -- --no-deps -D warnings 146 | 147 | .PHONY: clippy_all # Run all clippy targets 148 | clippy_all: clippy clippy_boolean clippy_shortint clippy_integer clippy_all_targets clippy_c_api \ 149 | clippy_js_wasm_api clippy_tasks clippy_core 150 | 151 | .PHONY: clippy_fast # Run main clippy targets 152 | clippy_fast: clippy clippy_all_targets clippy_c_api clippy_js_wasm_api clippy_tasks clippy_core 153 | 154 | .PHONY: gen_key_cache # Run the script to generate keys and cache them for shortint tests 155 | gen_key_cache: install_rs_build_toolchain 156 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) run --profile $(CARGO_PROFILE) \ 157 | --example generates_test_keys \ 158 | --features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache -p tfhe -- \ 159 | $(MULTI_BIT_ONLY) 160 | 161 | .PHONY: build_core # Build core_crypto without experimental features 162 | build_core: install_rs_build_toolchain install_rs_check_toolchain 163 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --profile $(CARGO_PROFILE) \ 164 | --features=$(TARGET_ARCH_FEATURE) -p tfhe 165 | @if [[ "$(AVX512_SUPPORT)" == "ON" ]]; then \ 166 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) build --profile $(CARGO_PROFILE) \ 167 | --features=$(TARGET_ARCH_FEATURE),$(AVX512_FEATURE) -p tfhe; \ 168 | fi 169 | 170 | .PHONY: build_core_experimental # Build core_crypto with experimental features 171 | build_core_experimental: install_rs_build_toolchain install_rs_check_toolchain 172 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --profile $(CARGO_PROFILE) \ 173 | --features=$(TARGET_ARCH_FEATURE),experimental -p tfhe 174 | @if [[ "$(AVX512_SUPPORT)" == "ON" ]]; then \ 175 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) build --profile $(CARGO_PROFILE) \ 176 | --features=$(TARGET_ARCH_FEATURE),experimental,$(AVX512_FEATURE) -p tfhe; \ 177 | fi 178 | 179 | .PHONY: build_boolean # Build with boolean enabled 180 | build_boolean: install_rs_build_toolchain 181 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --profile $(CARGO_PROFILE) \ 182 | --features=$(TARGET_ARCH_FEATURE),boolean -p tfhe --all-targets 183 | 184 | .PHONY: build_shortint # Build with shortint enabled 185 | build_shortint: install_rs_build_toolchain 186 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --profile $(CARGO_PROFILE) \ 187 | --features=$(TARGET_ARCH_FEATURE),shortint -p tfhe --all-targets 188 | 189 | .PHONY: build_integer # Build with integer enabled 190 | build_integer: install_rs_build_toolchain 191 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --profile $(CARGO_PROFILE) \ 192 | --features=$(TARGET_ARCH_FEATURE),integer -p tfhe --all-targets 193 | 194 | .PHONY: build_tfhe_full # Build with boolean, shortint and integer enabled 195 | build_tfhe_full: install_rs_build_toolchain 196 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) build --profile $(CARGO_PROFILE) \ 197 | --features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer -p tfhe --all-targets 198 | 199 | .PHONY: build_c_api # Build the C API for boolean, shortint and integer 200 | build_c_api: install_rs_check_toolchain 201 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) build --profile $(CARGO_PROFILE) \ 202 | --features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api,high-level-c-api, \ 203 | -p tfhe 204 | 205 | .PHONY: build_c_api_experimental_deterministic_fft # Build the C API for boolean, shortint and integer with experimental deterministic FFT 206 | build_c_api_experimental_deterministic_fft: install_rs_check_toolchain 207 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) build --profile $(CARGO_PROFILE) \ 208 | --features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api,high-level-c-api,experimental-force_fft_algo_dif4 \ 209 | -p tfhe 210 | 211 | .PHONY: build_web_js_api # Build the js API targeting the web browser 212 | build_web_js_api: install_rs_build_toolchain install_wasm_pack 213 | cd tfhe && \ 214 | RUSTFLAGS="$(WASM_RUSTFLAGS)" rustup run "$(RS_BUILD_TOOLCHAIN)" \ 215 | wasm-pack build --release --target=web \ 216 | -- --features=boolean-client-js-wasm-api,shortint-client-js-wasm-api,integer-client-js-wasm-api 217 | 218 | .PHONY: build_web_js_api_parallel # Build the js API targeting the web browser with parallelism support 219 | build_web_js_api_parallel: install_rs_check_toolchain install_wasm_pack 220 | cd tfhe && \ 221 | rustup component add rust-src --toolchain $(RS_CHECK_TOOLCHAIN) && \ 222 | RUSTFLAGS="$(WASM_RUSTFLAGS) -C target-feature=+atomics,+bulk-memory,+mutable-globals" rustup run $(RS_CHECK_TOOLCHAIN) \ 223 | wasm-pack build --release --target=web \ 224 | -- --features=boolean-client-js-wasm-api,shortint-client-js-wasm-api,integer-client-js-wasm-api,parallel-wasm-api \ 225 | -Z build-std=panic_abort,std 226 | 227 | .PHONY: build_node_js_api # Build the js API targeting nodejs 228 | build_node_js_api: install_rs_build_toolchain install_wasm_pack 229 | cd tfhe && \ 230 | RUSTFLAGS="$(WASM_RUSTFLAGS)" rustup run "$(RS_BUILD_TOOLCHAIN)" \ 231 | wasm-pack build --release --target=nodejs \ 232 | -- --features=boolean-client-js-wasm-api,shortint-client-js-wasm-api,integer-client-js-wasm-api 233 | 234 | .PHONY: test_core_crypto # Run the tests of the core_crypto module including experimental ones 235 | test_core_crypto: install_rs_build_toolchain install_rs_check_toolchain 236 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \ 237 | --features=$(TARGET_ARCH_FEATURE),experimental -p tfhe -- core_crypto:: 238 | @if [[ "$(AVX512_SUPPORT)" == "ON" ]]; then \ 239 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) test --profile $(CARGO_PROFILE) \ 240 | --features=$(TARGET_ARCH_FEATURE),experimental,$(AVX512_FEATURE) -p tfhe -- core_crypto::; \ 241 | fi 242 | 243 | .PHONY: test_boolean # Run the tests of the boolean module 244 | test_boolean: install_rs_build_toolchain 245 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \ 246 | --features=$(TARGET_ARCH_FEATURE),boolean -p tfhe -- boolean:: 247 | 248 | .PHONY: test_c_api_rs # Run the rust tests for the C API 249 | test_c_api_rs: install_rs_check_toolchain 250 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) test --profile $(CARGO_PROFILE) \ 251 | --features=$(TARGET_ARCH_FEATURE),boolean-c-api,shortint-c-api,high-level-c-api \ 252 | -p tfhe \ 253 | c_api 254 | 255 | .PHONY: test_c_api_c # Run the C tests for the C API 256 | test_c_api_c: build_c_api 257 | ./scripts/c_api_tests.sh 258 | 259 | .PHONY: test_c_api # Run all the tests for the C API 260 | test_c_api: test_c_api_rs test_c_api_c 261 | 262 | .PHONY: test_shortint_ci # Run the tests for shortint ci 263 | test_shortint_ci: install_rs_build_toolchain install_cargo_nextest 264 | BIG_TESTS_INSTANCE="$(BIG_TESTS_INSTANCE)" \ 265 | FAST_TESTS="$(FAST_TESTS)" \ 266 | ./scripts/shortint-tests.sh --rust-toolchain $(CARGO_RS_BUILD_TOOLCHAIN) \ 267 | --cargo-profile "$(CARGO_PROFILE)" 268 | 269 | .PHONY: test_shortint_multi_bit_ci # Run the tests for shortint ci running only multibit tests 270 | test_shortint_multi_bit_ci: install_rs_build_toolchain install_cargo_nextest 271 | BIG_TESTS_INSTANCE="$(BIG_TESTS_INSTANCE)" \ 272 | FAST_TESTS="$(FAST_TESTS)" \ 273 | ./scripts/shortint-tests.sh --rust-toolchain $(CARGO_RS_BUILD_TOOLCHAIN) \ 274 | --cargo-profile "$(CARGO_PROFILE)" --multi-bit 275 | 276 | .PHONY: test_shortint # Run all the tests for shortint 277 | test_shortint: install_rs_build_toolchain 278 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \ 279 | --features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache -p tfhe -- shortint:: 280 | 281 | .PHONY: test_integer_ci # Run the tests for integer ci 282 | test_integer_ci: install_rs_build_toolchain install_cargo_nextest 283 | BIG_TESTS_INSTANCE="$(BIG_TESTS_INSTANCE)" \ 284 | FAST_TESTS="$(FAST_TESTS)" \ 285 | ./scripts/integer-tests.sh --rust-toolchain $(CARGO_RS_BUILD_TOOLCHAIN) \ 286 | --cargo-profile "$(CARGO_PROFILE)" 287 | 288 | .PHONY: test_integer_multi_bit_ci # Run the tests for integer ci running only multibit tests 289 | test_integer_multi_bit_ci: install_rs_build_toolchain install_cargo_nextest 290 | BIG_TESTS_INSTANCE="$(BIG_TESTS_INSTANCE)" \ 291 | FAST_TESTS="$(FAST_TESTS)" \ 292 | ./scripts/integer-tests.sh --rust-toolchain $(CARGO_RS_BUILD_TOOLCHAIN) \ 293 | --cargo-profile "$(CARGO_PROFILE)" --multi-bit 294 | 295 | .PHONY: test_integer # Run all the tests for integer 296 | test_integer: install_rs_build_toolchain 297 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \ 298 | --features=$(TARGET_ARCH_FEATURE),integer,internal-keycache -p tfhe -- integer:: 299 | 300 | .PHONY: test_high_level_api # Run all the tests for high_level_api 301 | test_high_level_api: install_rs_build_toolchain 302 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \ 303 | --features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer,internal-keycache -p tfhe \ 304 | -- high_level_api:: 305 | 306 | .PHONY: test_user_doc # Run tests from the .md documentation 307 | test_user_doc: install_rs_build_toolchain 308 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) --doc \ 309 | --features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer,internal-keycache -p tfhe \ 310 | -- test_user_docs:: 311 | 312 | .PHONY: test_regex_engine # Run tests for regex_engine example 313 | test_regex_engine: install_rs_build_toolchain 314 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \ 315 | --example regex_engine \ 316 | --features=$(TARGET_ARCH_FEATURE),integer 317 | 318 | .PHONY: test_sha256_bool # Run tests for sha256_bool example 319 | test_sha256_bool: install_rs_build_toolchain 320 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \ 321 | --example sha256_bool \ 322 | --features=$(TARGET_ARCH_FEATURE),boolean 323 | 324 | .PHONY: test_examples # Run tests for examples 325 | test_examples: test_sha256_bool test_regex_engine 326 | 327 | .PHONY: test_trivium # Run tests for trivium 328 | test_trivium: install_rs_build_toolchain 329 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \ 330 | trivium --features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer \ 331 | -- --test-threads=1 332 | 333 | .PHONY: test_kreyvium # Run tests for kreyvium 334 | test_kreyvium: install_rs_build_toolchain 335 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \ 336 | kreyvium --features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer \ 337 | -- --test-threads=1 338 | 339 | .PHONY: doc # Build rust doc 340 | doc: install_rs_check_toolchain 341 | RUSTDOCFLAGS="--html-in-header katex-header.html" \ 342 | cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" doc \ 343 | --features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer --no-deps 344 | 345 | .PHONY: docs # Build rust doc alias for doc 346 | docs: doc 347 | 348 | .PHONY: lint_doc # Build rust doc with linting enabled 349 | lint_doc: install_rs_check_toolchain 350 | RUSTDOCFLAGS="--html-in-header katex-header.html -Dwarnings" \ 351 | cargo "$(CARGO_RS_CHECK_TOOLCHAIN)" doc \ 352 | --features=$(TARGET_ARCH_FEATURE),boolean,shortint,integer --no-deps 353 | 354 | .PHONY: lint_docs # Build rust doc with linting enabled alias for lint_doc 355 | lint_docs: lint_doc 356 | 357 | .PHONY: format_doc_latex # Format the documentation latex equations to avoid broken rendering. 358 | format_doc_latex: 359 | cargo xtask format_latex_doc 360 | @"$(MAKE)" --no-print-directory fmt 361 | @printf "\n===============================\n\n" 362 | @printf "Please manually inspect changes made by format_latex_doc, rustfmt can break equations \ 363 | if the line length is exceeded\n" 364 | @printf "\n===============================\n" 365 | 366 | .PHONY: check_compile_tests # Build tests in debug without running them 367 | check_compile_tests: 368 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --no-run \ 369 | --features=$(TARGET_ARCH_FEATURE),experimental,boolean,shortint,integer,internal-keycache \ 370 | -p tfhe 371 | 372 | @if [[ "$(OS)" == "Linux" || "$(OS)" == "Darwin" ]]; then \ 373 | "$(MAKE)" build_c_api; \ 374 | ./scripts/c_api_tests.sh --build-only; \ 375 | fi 376 | 377 | .PHONY: build_nodejs_test_docker # Build a docker image with tools to run nodejs tests for wasm API 378 | build_nodejs_test_docker: 379 | DOCKER_BUILDKIT=1 docker build --build-arg RUST_TOOLCHAIN="$(RS_BUILD_TOOLCHAIN)" \ 380 | -f docker/Dockerfile.wasm_tests -t tfhe-wasm-tests . 381 | 382 | .PHONY: test_nodejs_wasm_api_in_docker # Run tests for the nodejs on wasm API in a docker container 383 | test_nodejs_wasm_api_in_docker: build_nodejs_test_docker 384 | if [[ -t 1 ]]; then RUN_FLAGS="-it"; else RUN_FLAGS="-i"; fi && \ 385 | docker run --rm "$${RUN_FLAGS}" \ 386 | -v "$$(pwd)":/tfhe-wasm-tests/tfhe-rs \ 387 | -v tfhe-rs-root-target-cache:/root/tfhe-rs-target \ 388 | -v tfhe-rs-pkg-cache:/tfhe-wasm-tests/tfhe-rs/tfhe/pkg \ 389 | -v tfhe-rs-root-cargo-registry-cache:/root/.cargo/registry \ 390 | -v tfhe-rs-root-cache:/root/.cache \ 391 | tfhe-wasm-tests /bin/bash -i -c 'make test_nodejs_wasm_api' 392 | 393 | .PHONY: test_nodejs_wasm_api # Run tests for the nodejs on wasm API 394 | test_nodejs_wasm_api: build_node_js_api 395 | cd tfhe && node --test js_on_wasm_tests 396 | 397 | .PHONY: test_web_js_api_parallel # Run tests for the web wasm api 398 | test_web_js_api_parallel: build_web_js_api_parallel 399 | $(MAKE) -C tfhe/web_wasm_parallel_tests test 400 | 401 | .PHONY: ci_test_web_js_api_parallel # Run tests for the web wasm api 402 | ci_test_web_js_api_parallel: build_web_js_api_parallel 403 | source ~/.nvm/nvm.sh && \ 404 | nvm use node && \ 405 | $(MAKE) -C tfhe/web_wasm_parallel_tests test-ci 406 | 407 | .PHONY: no_tfhe_typo # Check we did not invert the h and f in tfhe 408 | no_tfhe_typo: 409 | @./scripts/no_tfhe_typo.sh 410 | 411 | .PHONY: no_dbg_log # Check we did not leave dbg macro calls in the rust code 412 | no_dbg_log: 413 | @./scripts/no_dbg_calls.sh 414 | 415 | # 416 | # Benchmarks 417 | # 418 | 419 | .PHONY: bench_integer # Run benchmarks for integer 420 | bench_integer: install_rs_check_toolchain 421 | RUSTFLAGS="$(RUSTFLAGS)" __TFHE_RS_BENCH_OP_FLAVOR=$(BENCH_OP_FLAVOR) __TFHE_RS_FAST_BENCH=$(FAST_BENCH) \ 422 | cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \ 423 | --bench integer-bench \ 424 | --features=$(TARGET_ARCH_FEATURE),integer,internal-keycache,$(AVX512_FEATURE) -p tfhe -- 425 | 426 | .PHONY: bench_integer_multi_bit # Run benchmarks for integer using multi-bit parameters 427 | bench_integer_multi_bit: install_rs_check_toolchain 428 | RUSTFLAGS="$(RUSTFLAGS)" __TFHE_RS_BENCH_TYPE=MULTI_BIT \ 429 | __TFHE_RS_BENCH_OP_FLAVOR=$(BENCH_OP_FLAVOR) __TFHE_RS_FAST_BENCH=$(FAST_BENCH) \ 430 | cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \ 431 | --bench integer-bench \ 432 | --features=$(TARGET_ARCH_FEATURE),integer,internal-keycache,$(AVX512_FEATURE) -p tfhe -- 433 | 434 | .PHONY: bench_shortint # Run benchmarks for shortint 435 | bench_shortint: install_rs_check_toolchain 436 | RUSTFLAGS="$(RUSTFLAGS)" __TFHE_RS_BENCH_OP_FLAVOR=$(BENCH_OP_FLAVOR) \ 437 | cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \ 438 | --bench shortint-bench \ 439 | --features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache,$(AVX512_FEATURE) -p tfhe 440 | 441 | .PHONY: bench_boolean # Run benchmarks for boolean 442 | bench_boolean: install_rs_check_toolchain 443 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \ 444 | --bench boolean-bench \ 445 | --features=$(TARGET_ARCH_FEATURE),boolean,internal-keycache,$(AVX512_FEATURE) -p tfhe 446 | 447 | .PHONY: bench_pbs # Run benchmarks for PBS 448 | bench_pbs: install_rs_check_toolchain 449 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \ 450 | --bench pbs-bench \ 451 | --features=$(TARGET_ARCH_FEATURE),boolean,shortint,internal-keycache,$(AVX512_FEATURE) -p tfhe 452 | 453 | .PHONY: bench_web_js_api_parallel # Run benchmarks for the web wasm api 454 | bench_web_js_api_parallel: build_web_js_api_parallel 455 | $(MAKE) -C tfhe/web_wasm_parallel_tests bench 456 | 457 | .PHONY: ci_bench_web_js_api_parallel # Run benchmarks for the web wasm api 458 | ci_bench_web_js_api_parallel: build_web_js_api_parallel 459 | source ~/.nvm/nvm.sh && \ 460 | nvm use node && \ 461 | $(MAKE) -C tfhe/web_wasm_parallel_tests bench-ci 462 | 463 | # 464 | # Utility tools 465 | # 466 | 467 | .PHONY: measure_hlapi_compact_pk_ct_sizes # Measure sizes of public keys and ciphertext for high-level API 468 | measure_hlapi_compact_pk_ct_sizes: install_rs_check_toolchain 469 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run --profile $(CARGO_PROFILE) \ 470 | --example hlapi_compact_pk_ct_sizes \ 471 | --features=$(TARGET_ARCH_FEATURE),integer,internal-keycache 472 | 473 | .PHONY: measure_shortint_key_sizes # Measure sizes of bootstrapping and key switching keys for shortint 474 | measure_shortint_key_sizes: install_rs_check_toolchain 475 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run --profile $(CARGO_PROFILE) \ 476 | --example shortint_key_sizes \ 477 | --features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache 478 | 479 | .PHONY: measure_boolean_key_sizes # Measure sizes of bootstrapping and key switching keys for boolean 480 | measure_boolean_key_sizes: install_rs_check_toolchain 481 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run --profile $(CARGO_PROFILE) \ 482 | --example boolean_key_sizes \ 483 | --features=$(TARGET_ARCH_FEATURE),boolean,internal-keycache 484 | 485 | .PHONY: parse_integer_benches # Run python parser to output a csv containing integer benches data 486 | parse_integer_benches: 487 | python3 ./ci/parse_integer_benches_to_csv.py \ 488 | --criterion-dir target/criterion \ 489 | --output-file "$(PARSE_INTEGER_BENCH_CSV_FILE)" 490 | 491 | .PHONY: parse_wasm_benchmarks # Parse benchmarks performed with WASM web client into a CSV file 492 | parse_wasm_benchmarks: install_rs_check_toolchain 493 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run --profile $(CARGO_PROFILE) \ 494 | --example wasm_benchmarks_parser \ 495 | --features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache \ 496 | -- web_wasm_parallel_tests/test/benchmark_results 497 | 498 | .PHONY: write_params_to_file # Gather all crypto parameters into a file with a Sage readable format. 499 | write_params_to_file: install_rs_check_toolchain 500 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run --profile $(CARGO_PROFILE) \ 501 | --example write_params_to_file \ 502 | --features=$(TARGET_ARCH_FEATURE),boolean,shortint,internal-keycache 503 | 504 | # 505 | # Real use case examples 506 | # 507 | 508 | .PHONY: regex_engine # Run regex_engine example 509 | regex_engine: install_rs_check_toolchain 510 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run --profile $(CARGO_PROFILE) \ 511 | --example regex_engine \ 512 | --features=$(TARGET_ARCH_FEATURE),integer \ 513 | -- $(REGEX_STRING) $(REGEX_PATTERN) 514 | 515 | .PHONY: dark_market # Run dark market example 516 | dark_market: install_rs_check_toolchain 517 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run --profile $(CARGO_PROFILE) \ 518 | --example dark_market \ 519 | --features=$(TARGET_ARCH_FEATURE),integer,internal-keycache \ 520 | -- fhe-modified fhe-parallel plain fhe 521 | 522 | .PHONY: sha256_bool # Run sha256_bool example 523 | sha256_bool: install_rs_check_toolchain 524 | RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run --profile $(CARGO_PROFILE) \ 525 | --example sha256_bool \ 526 | --features=$(TARGET_ARCH_FEATURE),boolean 527 | 528 | .PHONY: pcc # pcc stands for pre commit checks 529 | pcc: no_tfhe_typo no_dbg_log check_fmt lint_doc clippy_all check_compile_tests 530 | 531 | .PHONY: fpcc # pcc stands for pre commit checks, the f stands for fast 532 | fpcc: no_tfhe_typo no_dbg_log check_fmt lint_doc clippy_fast check_compile_tests 533 | 534 | .PHONY: conformance # Automatically fix problems that can be fixed 535 | conformance: fmt 536 | 537 | .PHONY: help # Generate list of targets with descriptions 538 | help: 539 | @grep '^\.PHONY: .* #' Makefile | sed 's/\.PHONY: \(.*\) # \(.*\)/\1\t\2/' | expand -t30 | sort 540 | --------------------------------------------------------------------------------