diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 6901a4b..aa54485 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -31,7 +31,7 @@ jobs: uses: dtolnay/rust-toolchain@master id: toolchain with: - toolchain: nightly-2024-03-19 + toolchain: nightly-2024-05-03 - name: Override default toolchain run: rustup override set ${{steps.toolchain.outputs.name}} - run: cargo --version @@ -55,7 +55,7 @@ jobs: uses: dtolnay/rust-toolchain@master id: toolchain with: - toolchain: nightly-2024-03-19 + toolchain: nightly-2024-05-03 components: "clippy, rustfmt" - name: Override default toolchain run: rustup override set ${{steps.toolchain.outputs.name}} diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index dee3ce4..93c4334 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -1,4 +1,4 @@ -name: Push +name: CI on: [ "push" ] @@ -17,7 +17,7 @@ jobs: uses: dtolnay/rust-toolchain@master id: toolchain with: - toolchain: nightly-2024-03-19 + toolchain: nightly-2024-05-03 - name: Override default toolchain run: rustup override set ${{steps.toolchain.outputs.name}} - run: cargo --version diff --git a/.github/workflows/rustdoc.yml b/.github/workflows/rustdoc.yml index a6d99ed..5fb5e4d 100644 --- a/.github/workflows/rustdoc.yml +++ b/.github/workflows/rustdoc.yml @@ -2,13 +2,16 @@ name: rustdoc on: push: -# branches: -# - "main" + branches: + - "main" jobs: # Build job build: runs-on: "ubuntu-latest" + env: + # deny rustdoc warnings + RUSTDOCFLAGS: -D warnings steps: - name: Check out repository uses: actions/checkout@v4 @@ -18,14 +21,14 @@ jobs: uses: dtolnay/rust-toolchain@master id: toolchain with: - toolchain: nightly-2024-01-12 + toolchain: nightly-2024-05-03 - name: Override default toolchain run: rustup override set ${{steps.toolchain.outputs.name}} - run: cargo --version - name: Cache uses: Swatinem/rust-cache@v2 - name: Create Docs - run: cargo doc --workspace --verbose --all-features --no-deps + run: cargo doc --workspace --verbose --all-features --no-deps -Zunstable-options -Zrustdoc-scrape-examples - name: Fix permissions run: | chmod -c -R +rX "target/doc/" | while read line; do diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d4033ba --- /dev/null +++ b/LICENSE @@ -0,0 +1,16 @@ +Copyright 2024 Robin Hundt + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the “Software”), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies +or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING +BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index b57b39e..29f8680 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,47 @@ -# SEEC +# SEEC Executes Enormous Circuits -This framework implements secure 2-party secret-sharing based multi party computation protocols. Currently, we implement the Boolean and arithmetic versions of GMW87 with multiplication triple preprocessing. Additionally, we implement the Boolean part of the ABY2.0 protocol. +![ci badge](https://github.com/encryptogroup/SEEC/actions/workflows/push.yml/badge.svg?branch=main) [![rustdoc](https://github.com/encryptogroup/SEEC/actions/workflows/rustdoc.yml/badge.svg)](https://encryptogroup.github.io/SEEC/seec/) + +This framework implements secure 2-party secret-sharing-based multi party computation protocols. Currently, we implement +the Boolean and arithmetic versions of GMW87 with multiplication triple preprocessing. Additionally, we implement the +Boolean part of the ABY2.0 protocol. + +## Secure Multi-Party Computation + +In secure multi-party computation (MPC), there are n parties, each with their private input x_i. Given a public function +f(x_1, ..., x_n), the parties execute a protocol π that correctly and securely realizes the functionality f. In other +words, at the end of the protocol, the parties know the output of f, but have no information about the input of the +other parties other than what is revealed by the output itself. Currently, SEEC is limited to the n = 2 party case, also +known as secure two-party computation. We hope to extend this in the future to n parties. + +### Security + +The two most prevalent security models are + +- semi-honest security, where an attacker can corrupt parties, but they follow the protocol as specified. +- malicious security, where corrupted parties can arbitrarily deviate from the protocol. + +SEEC currently only implements semi-honestly secure protocols (GMW, ABY2.0). + +## Using SEEC + +SEEC can be used as a library by adding it to the `Cargo.toml` file of an existing project. + +```toml +seec = { } +``` + +## Documentation + +Documentation for the main branch is hosted [here](https://encryptogroup.github.io/SEEC/seec/). ## Development ### Installing Rust -The project is implemented in the [Rust](https://www.rust-lang.org/) programming language. To compile it, the latest stable toolchain is needed (older toolchains might work but are not guaranteed). The recommended way to install it, is via the toolchain manager [rustup](https://rustup.rs/). +The project is implemented in the [Rust](https://www.rust-lang.org/) programming language. To compile it, the latest +stable toolchain is needed (older toolchains might work but are not guaranteed). The recommended way to install it, is +via the toolchain manager [rustup](https://rustup.rs/). One way of installing `rustup`: @@ -14,6 +49,10 @@ One way of installing `rustup`: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` +As a starting point to learn Rust, have a look at the superb +official [learning material](https://www.rust-lang.org/learn). To quickly look up syntax and idioms, we +recommend https://cheats.rs/. + ### Checking for compilation errors To simply check the code for error and warnings, execute: @@ -27,10 +66,11 @@ cargo check The tests can be run with the following command: ```shell -cargo test [--release] +cargo test [--release] [--all-features] ``` The `--release` flag is optional, but can decrease the runtime of the tests, at the cost of increased compilation time. +The `--all-features` flag enables all optional features. ### Formatting @@ -41,26 +81,75 @@ cargo fmt ``` ## ARM Support -SEEC has (WIP) ARM support. Because we use the unstable `std::simd` feature of Rust for portable SIMD transport, a recent nightly toolchain is needed. The easiest way to install this is via `rustup` (https://rustup.rs/). + +SEEC has (WIP) ARM support. Because we use the unstable `std::simd` feature of Rust for portable SIMD transport, a +recent nightly toolchain is needed. The easiest way to install this is via `rustup` (https://rustup.rs/). + ```shell rustup toolchain install nightly ``` + Then set the toolchain to use for this project to nightly. + ```shell rustup override set nightly ``` + And verify that nightly is used. + ```shell cargo --version # output should include nightly ``` +If on an ARM platform, building and testing all crates in this repository won't work, as some ( +e.g. `crates/bitpolymul`), require x86_64 intrinsics. Offending packages can be `--exclude`d or you can simply change +into the main `crates/seec` directory and run cargo there. + ## Silent-OT -Our OT library [ZappOT](./crates/zappot) has optional support for Silent-OT. -Using the quasi-cyclic code (https://eprint.iacr.org/2019/1159.pdf) requires an x86-64 CPU with AVX2 support. ZappOT has WIP support for newer codes offered by libOTe (https://github.com/osu-crypto/libOTe). For these, we build and link to the code implementations in libOTe. These should work on other architectures, e.g., ARM, but we currently do not test this in CI. - Concretely, via libOTe, we support + +Our OT library [ZappOT](./crates/zappot) has optional support for Silent-OT. +Using the quasi-cyclic code (https://eprint.iacr.org/2019/1159.pdf) requires an x86-64 CPU with AVX2 support. ZappOT has +WIP support for newer codes offered by libOTe (https://github.com/osu-crypto/libOTe). For these, we build and link to +the code implementations in libOTe. These should work on other architectures, e.g., ARM, but we currently do not test +this in CI. +Concretely, via libOTe, we support + - Silver (INSECURE! https://eprint.iacr.org/2021/1150, see https://eprint.iacr.org/2023/882 for attack) - ExpandAccumulate (https://eprint.iacr.org/2022/1014) - ExpandConvolute (https://eprint.iacr.org/2023/882). -SEEC currently supports generating Boolean MTs with Silent-OT when enabling the `silent-ot` feature. \ No newline at end of file +SEEC currently supports generating Boolean MTs with Silent-OT when enabling the `silent-ot` feature. + +> [!NOTE] +> Silent-OT with the quasi-cyclic code (`--feature silent-ot-quasi-cyclic` in SEEC) only works on x86_64 linux with AVX2 +> support. The libOTe codes (`--feature silent-ot`) currently work on x86_64 linux and aarch64 ARM (M1 Macs). Other +> targets might work, but are not tested. + +## Organization + +This project is organized as a Cargo workspace with multiple crates in the `crates/` directory. The main crate is +located at `crates/seec` and it depends on most of the other crates. + +Also of interest is the `crates/zappot` library, which implements several oblivious transfer (OT) protocols. These are +used by SEEC to compute setup data such as Beaver multiplication triples, but they can also be used independently. + +Main Crates: + +- seec: The main library which implements several MPC protocols. +- seec-macros: Offers the `#[sub_circuit]` proc-macro that turns functions into reusable sub-circuits. +- seec-channel: A convenient wrapper over a fork of [remoc](https://github.com/ENQT-GmbH/remoc). +- seec-bitmatrix: A bitmatrix implementation including portable SIMD matrix transpose (needs Rust nightly). +- zappot: Our OT library, including support for Silent-OT. + +We also provide an additional library at `libs/libote-rs` which builds and provides bindings to the codes used in libOTe +for its implementation of Silent-OT. These can be optionally used by ZappOT. + +### Architecture + +The figure below shows a simplified version of the main traits and types of SEEC. +![](figures/architecture.svg) + +## Benchmarking + +Alongside SEEC, we're developing an MPC [benchmarking tool](https://github.com/encryptogroup/mpc-bench). \ No newline at end of file diff --git a/crates/aligned-vec/Cargo.toml b/crates/aligned-vec/Cargo.toml index af86488..997506b 100644 --- a/crates/aligned-vec/Cargo.toml +++ b/crates/aligned-vec/Cargo.toml @@ -2,6 +2,8 @@ name = "aligned-vec" version = "0.1.0" edition = "2021" +license = "MIT" +authors = ["Robin Hundt"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/crates/bitpolymul-sys/Cargo.toml b/crates/bitpolymul-sys/Cargo.toml index 3d8a0ff..fe31ea9 100644 --- a/crates/bitpolymul-sys/Cargo.toml +++ b/crates/bitpolymul-sys/Cargo.toml @@ -3,6 +3,8 @@ name = "bitpolymul-sys" version = "0.1.0" edition = "2021" links = "bitpolymul" +license = "MIT" +authors = ["Robin Hundt"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/crates/bitpolymul/Cargo.toml b/crates/bitpolymul/Cargo.toml index 47ed6dc..3de3050 100644 --- a/crates/bitpolymul/Cargo.toml +++ b/crates/bitpolymul/Cargo.toml @@ -2,6 +2,8 @@ name = "bitpolymul" version = "0.1.0" edition = "2021" +license = "MIT" +authors = ["Robin Hundt"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/crates/seec-bitmatrix/Cargo.toml b/crates/seec-bitmatrix/Cargo.toml index 840018f..aabb42f 100644 --- a/crates/seec-bitmatrix/Cargo.toml +++ b/crates/seec-bitmatrix/Cargo.toml @@ -2,6 +2,8 @@ name = "seec-bitmatrix" version = "0.1.0" edition = "2021" +license = "MIT" +authors = ["Robin Hundt"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/crates/seec-bitmatrix/src/lib.rs b/crates/seec-bitmatrix/src/lib.rs index 3bad4fe..6f8a6c1 100644 --- a/crates/seec-bitmatrix/src/lib.rs +++ b/crates/seec-bitmatrix/src/lib.rs @@ -197,6 +197,8 @@ where self.cols, rhs.rows, "Illegal dimensions for matrix multiplication" ); + // TODO this can likely be heavily optimized. One option is to use the raw_rows iterator + // if possible to do the dotp on the raw elements. This should be significantly faster. let dotp = |l_row: &BitSlice, r_row| -> bool { let and = l_row.to_bitvec() & r_row; and.iter().by_vals().reduce(BitXor::bitxor).unwrap() diff --git a/crates/seec-channel-macros/Cargo.toml b/crates/seec-channel-macros/Cargo.toml index 149b21e..e271e1f 100644 --- a/crates/seec-channel-macros/Cargo.toml +++ b/crates/seec-channel-macros/Cargo.toml @@ -2,6 +2,8 @@ name = "seec-channel-macros" version = "0.1.0" edition = "2021" +license = "MIT" +authors = ["Robin Hundt"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/crates/seec-channel/Cargo.toml b/crates/seec-channel/Cargo.toml index cde9886..4e2eae8 100644 --- a/crates/seec-channel/Cargo.toml +++ b/crates/seec-channel/Cargo.toml @@ -2,6 +2,8 @@ name = "seec-channel" version = "0.1.0" edition = "2021" +license = "MIT" +authors = ["Robin Hundt"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/crates/seec-macros/Cargo.toml b/crates/seec-macros/Cargo.toml index 9254630..bd7a7d6 100644 --- a/crates/seec-macros/Cargo.toml +++ b/crates/seec-macros/Cargo.toml @@ -2,6 +2,8 @@ name = "seec-macros" version = "0.1.0" edition = "2021" +license = "MIT" +authors = ["Robin Hundt"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/crates/seec/Cargo.toml b/crates/seec/Cargo.toml index 0b60977..c9df1e1 100644 --- a/crates/seec/Cargo.toml +++ b/crates/seec/Cargo.toml @@ -2,21 +2,26 @@ name = "seec" version = "0.1.0" edition = "2021" +license = "MIT" +authors = ["Robin Hundt"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] _integration_tests = ["tracing-subscriber", "anyhow", "funty"] +# Enables the benchmarking API bench-api = ["anyhow"] +# Enables the ABY2 implementation aby2 = [] -# depends on AVX2, therefore not platform idepedent +# Enables the Silent-OT QuasiCyclic code. depends on AVX2, therefore not platform independent. silent-ot-quasi-cyclic = ["zappot/silent-ot-quasi-cyclic-code"] -# using the libote codes should work on ARM +# Enables the Silent-OT codes from libOTe. These work on ARM. silent-ot = ["zappot/silent-ot-libote-codes"] [[example]] name = "bristol" required-features = ["bench-api"] +doc-scrape-examples = true [[example]] name = "aes_cbc" @@ -28,51 +33,51 @@ required-features = ["bench-api"] [dependencies] -ahash = "0.8.11" -async-trait = "0.1.79" +ahash = "0.8.7" +async-trait = "0.1.77" async-stream = "0.3.5" -bitvec = { version = "1.0.1", features = ["serde"]} -bytemuck = { version = "1.15.0", features = ["derive"]} -bincode = { version = "1.3.3"} -tokio = { version = "1.36.0", features = ["full"]} +bitvec = { version = "1.0.1", features = ["serde"] } +bytemuck = { version = "1.15.0", features = ["derive"] } +bincode = { version = "1.3.3" } +tokio = { version = "1.36.0", features = ["full"] } futures = "0.3.30" thiserror = "1.0.58" pin-project = "1.1.5" nom = "7.1.3" -petgraph = { version = "0.6.4", features = ["serde-1"]} -smallvec = { version = "1.13.2", features = ["union", "const_generics", "serde"]} +petgraph = { version = "0.6.4", features = ["serde-1"] } +smallvec = { version = "1.13.2", features = ["union", "const_generics", "serde"] } itertools = "0.12.1" -tokio-serde = { version = "0.9.0", features = ["bincode"]} -tokio-util = { version = "0.7.10", features = ["codec"]} -serde = { version = "1.0.197", features = ["derive"]} +tokio-serde = { version = "0.9.0", features = ["bincode"] } +tokio-util = { version = "0.7.10", features = ["codec"] } +serde = { version = "1.0.197", features = ["derive"] } tracing = "0.1.40" itoa = "1.0.10" -tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json"], optional = true} -anyhow = { version = "1.0.81", optional = true} -funty = { version = "2.0.0", optional = true} -parking_lot = { version = "0.12.1", features = ["arc_lock"]} +tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json"], optional = true } +anyhow = { version = "1.0.81", optional = true } +funty = { version = "2.0.0", optional = true } +parking_lot = { version = "0.12.1", features = ["arc_lock"] } rayon = "1.10.0" -rand = { version = "0.8.5", features = ["std"]} +rand = { version = "0.8.5", features = ["std"] } rand_chacha = "0.3.1" num-integer = "0.1.46" num-traits = "0.2.18" rangemap = "1.5.1" once_cell = "1.19.0" -seec-macros = {path = "../seec-macros"} -seec-channel = {path = "../seec-channel"} +seec-macros = { path = "../seec-macros" } +seec-channel = { path = "../seec-channel" } remoc = { workspace = true } -zappot = {path = "../zappot"} +zappot = { path = "../zappot" } typemap = "0.3.3" -seec-bitmatrix = {path = "../seec-bitmatrix"} +seec-bitmatrix = { path = "../seec-bitmatrix" } either = "1.10.0" flatbuffers = "23.5.26" [dev-dependencies] aes = "0.8.4" -cbc = { version = "0.1.2", features = ["alloc", "block-padding"]} +cbc = { version = "0.1.2", features = ["alloc", "block-padding"] } hex = "0.4.3" hex-literal = "0.4.1" -seec = {path = ".", features = ["_integration_tests"]} +seec = { path = ".", features = ["_integration_tests"] } bincode = "1.3.3" clap = { workspace = true } tracing-appender = "0.2.3" diff --git a/crates/seec/README.md b/crates/seec/README.md new file mode 100644 index 0000000..0671c6f --- /dev/null +++ b/crates/seec/README.md @@ -0,0 +1,6 @@ +# SEEC Executes Enormous Circuits + +This is the main crate of SEEC, a framework for secure multi-party computation (MPC). For a high-level introduction to MPC and this project, have a look at [workspace root](https://github.com/encryptogroup/SEEC). + +## Examples +Examples are located in the `examples` folder. \ No newline at end of file diff --git a/crates/seec/examples/bristol.rs b/crates/seec/examples/bristol.rs index 5f54c75..5f2088a 100644 --- a/crates/seec/examples/bristol.rs +++ b/crates/seec/examples/bristol.rs @@ -2,7 +2,7 @@ //! optionally generating the multiplication triples via a third trusted party //! (see the `trusted_party_mts.rs` example). //! -//! It also demonstrates how to use the [`Statistics`] API to track the communication of +//! It also demonstrates how to use the [`BenchParty`] API to track the communication of //! different phases and write it to a file. use anyhow::{Context, Result}; diff --git a/crates/seec/examples/simple.rs b/crates/seec/examples/simple.rs index 1b3bac9..be37083 100644 --- a/crates/seec/examples/simple.rs +++ b/crates/seec/examples/simple.rs @@ -12,6 +12,7 @@ use bitvec::prelude::*; use tokio::time::sleep; use tracing_subscriber::EnvFilter; +use seec::channel::sub_channels_for; use seec::circuit::{dyn_layers::Circuit, DefaultIdx, ExecutableCircuit}; use seec::executor::{Executor, Input, Message}; use seec::mul_triple::boolean::insecure_provider::InsecureMTProvider; @@ -19,7 +20,6 @@ use seec::mul_triple::MTProvider; use seec::protocols::boolean_gmw::BooleanGmw; use seec::secret::inputs; use seec::CircuitBuilder; -use seec_channel::sub_channels_for; fn build_circuit() { // The `inputs` method is a convenience method to create n input gates for the circuit. diff --git a/crates/seec/src/bench.rs b/crates/seec/src/bench.rs index 1b9852a..289fa0a 100644 --- a/crates/seec/src/bench.rs +++ b/crates/seec/src/bench.rs @@ -1,3 +1,9 @@ +//! Insecure Benchmarking API - Do not use in Production! +//! +//! The [`BenchParty`] API provides an easy way of benchmarking an MPC application +//! or protocol implemented in SEEC. An example of its usage is e.g. located in the +//! `crates/seec/examples/bristol.rs` binary. + use crate::circuit::{ExecutableCircuit, GateIdx}; use crate::executor::{Executor, Input, Message}; use crate::mul_triple::storage::MTStorage; diff --git a/crates/seec/src/circuit/base_circuit.rs b/crates/seec/src/circuit/base_circuit.rs index 23e5217..482f2de 100644 --- a/crates/seec/src/circuit/base_circuit.rs +++ b/crates/seec/src/circuit/base_circuit.rs @@ -1,3 +1,4 @@ +//! Base-Circuits are a graph representation of a circuit. #![allow(clippy::extra_unused_type_parameters)] // false positive in current nightly use ahash::HashMap; @@ -190,7 +191,7 @@ impl BaseCircuit { } impl BaseCircuit { - #[tracing::instrument(level="trace", skip(self), fields(%from, %to))] + #[tracing::instrument(level = "trace", skip(self), fields(% from, % to))] pub fn add_wire(&mut self, from: GateId, to: GateId) { self.graph.add_edge(from.into(), to.into(), ()); trace!("Added wire"); @@ -512,6 +513,7 @@ pub struct BaseLayerIter<'a, G, Idx: GateIdx, W> { visited: as Visitable>::Map, added_to_next: as Visitable>::Map, // only used for SIMD circuits + // TODO remove entries from hashmap when count recheas 0 inputs_left_to_provide: HashMap, u32>, // (non_interactive, interactive) last_layer_size: (usize, usize), diff --git a/crates/seec/src/circuit/builder.rs b/crates/seec/src/circuit/builder.rs index fe57241..23fd025 100644 --- a/crates/seec/src/circuit/builder.rs +++ b/crates/seec/src/circuit/builder.rs @@ -1,3 +1,4 @@ +//! [`CircuitBuilder`] is used to build an aggregate circuit of [`BaseCircuit`]s. use std::collections::hash_map::Entry; use std::collections::{HashMap, HashSet}; use std::fmt::{Debug, Display, Formatter}; @@ -28,14 +29,13 @@ use crate::utils::ByAddress; pub type SharedCircuit = Arc>>; -// /// Lazily initialized global CircuitBuilder. This is used by the Secret API's and the -// /// #[sub_circuit] macro to construct a circuit without having direct access to the circuits. -// pub(crate) static CIRCUIT_BUILDER: Lazy>> = -// Lazy::new(|| Mutex::new(CircuitBuilder::new())); - +/// Lazily initialized global CircuitBuilders. This is used by the Secret API's and the +/// #[sub_circuit] macro to construct a circuit without having direct access to the circuits. +/// The ShareMap stores one builder per Gate type. pub(crate) static CIRCUIT_BUILDER_MAP: Lazy> = Lazy::new(|| Mutex::new(ShareMap::custom())); +/// Needed for the ShareMap. struct KeyWrapper(PhantomData<(G, Idx)>); impl Key for KeyWrapper { diff --git a/crates/seec/src/circuit/circuit_connections.rs b/crates/seec/src/circuit/circuit_connections.rs index 2260d92..69bcfde 100644 --- a/crates/seec/src/circuit/circuit_connections.rs +++ b/crates/seec/src/circuit/circuit_connections.rs @@ -1,3 +1,4 @@ +//! Compressed storage of cross-circuit connections. use crate::circuit::{CircuitId, GateIdx}; use crate::utils::RangeInclusiveStartWrapper; use crate::{GateId, SubCircuitGate}; diff --git a/crates/seec/src/circuit/dyn_layers.rs b/crates/seec/src/circuit/dyn_layers.rs index e8c6f20..8160f52 100644 --- a/crates/seec/src/circuit/dyn_layers.rs +++ b/crates/seec/src/circuit/dyn_layers.rs @@ -1,3 +1,4 @@ +//! Dynamic layer aggregate circuit. use crate::circuit::base_circuit::{BaseGate, Load}; use crate::circuit::circuit_connections::CrossCircuitConnections; use crate::circuit::{base_circuit, BaseCircuit, CircuitId, DefaultIdx, GateIdx, LayerIterable}; diff --git a/crates/seec/src/circuit/mod.rs b/crates/seec/src/circuit/mod.rs index eac705b..d412620 100644 --- a/crates/seec/src/circuit/mod.rs +++ b/crates/seec/src/circuit/mod.rs @@ -1,3 +1,4 @@ +//! Circuit builder and executable circuit types. use crate::SubCircuitGate; pub use builder::SubCircCache; use bytemuck::Pod; diff --git a/crates/seec/src/circuit/static_layers.rs b/crates/seec/src/circuit/static_layers.rs index a0a39a7..d51201f 100644 --- a/crates/seec/src/circuit/static_layers.rs +++ b/crates/seec/src/circuit/static_layers.rs @@ -1,3 +1,4 @@ +//! Static layer aggregate circuit. use crate::circuit::circuit_connections::CrossCircuitConnections; use crate::circuit::{base_circuit, dyn_layers::CircuitLayerIter, CircuitId, GateIdx}; use crate::common::BitVec; diff --git a/crates/seec/src/common.rs b/crates/seec/src/common.rs index 4114672..aa907c4 100644 --- a/crates/seec/src/common.rs +++ b/crates/seec/src/common.rs @@ -1,3 +1,4 @@ +//! Common type aliases. type BitStorage = u8; pub type BitSlice = bitvec::slice::BitSlice; pub type BitVec = bitvec::vec::BitVec; diff --git a/crates/seec/src/errors.rs b/crates/seec/src/errors.rs index a20b089..58dd503 100644 --- a/crates/seec/src/errors.rs +++ b/crates/seec/src/errors.rs @@ -1,3 +1,4 @@ +//! Error types used in SEEC. use remoc::rch::base; use std::io; diff --git a/crates/seec/src/executor.rs b/crates/seec/src/executor.rs index 3500429..ebcfdbf 100644 --- a/crates/seec/src/executor.rs +++ b/crates/seec/src/executor.rs @@ -1,3 +1,4 @@ +//! Executor for a generic protocol. use serde::{Deserialize, Serialize}; #[cfg(debug_assertions)] use std::collections::HashSet; diff --git a/crates/seec/src/lib.rs b/crates/seec/src/lib.rs index d20dc00..2757108 100644 --- a/crates/seec/src/lib.rs +++ b/crates/seec/src/lib.rs @@ -1,3 +1,50 @@ +//! # SEEC Executes Enormous Circuits +//! +//! This framework implements secure 2-party secret-sharing-based multi party computation protocols. +//! Currently, we implement the Boolean and arithmetic versions of GMW87 with multiplication triple preprocessing. +//! Additionally, we implement the Boolean part of the ABY2.0 protocol. +//! +//! ## Secure Multi-Party Computation +//! In secure multi-party computation (MPC), there are n parties, each with their private input x_i. +//! Given a public function f(x_1, ..., x_n), the parties execute a protocol π that correctly and +//! securely realizes the functionality f. In other words, at the end of the protocol, the parties know +//! the output of f, but have no information about the input of the other parties other than what is revealed +//! by the output itself. Currently, SEEC is limited to the n = 2 party case, also known as secure +//! two-party computation. We hope to extend this in the future to n parties. +//! +//! ### Security +//! The two most prevalent security models are +//! - semi-honest security, where an attacker can corrupt parties, but they follow the protocol as specified. +//! - malicious security, where corrupted parties can arbitrarily deviate from the protocol. +//! +//! SEEC currently only implements semi-honestly secure protocols (GMW, ABY2.0). +//! +//! ## Using SEEC +//! To use SEEC, you need a recent stable Rust toolchain (nightly on ARM[^nightly]). The easiest way to install Rust +//! is via the official toolchain installer [rustup](https://rustup.rs/). +//! +//! Create a new Rust project using cargo `cargo new --bin seec-test`, and add SEEC as a dependency to your +//! `Cargo.toml`. +//! ```toml +//! # in seec-test/Cargo.toml +//! [dependencies] +//! seec = { git = "https://github.com/encryptogroup/SEEC.git", features = ["..."]} +//! ``` +//! +//! ### Cargo features +//! - "bench-api": Enables the benchmarking API +//! - "aby2": Enables the ABY2 implementation +//! - "silent-ot-quasi-cyclic": Enables the Silent-OT QuasiCyclic code. depends on AVX2, therefore not platform independent. +//! - "silent-ot": Enables the Silent-OT codes from libOTe. These work on ARM. +//! +//! The following annotated example of using SEEC is also located at `crates/seec/examples/simple.rs`. +//! You can run it using `cargo run --example simple`. +//!```ignore,rust +#![doc = include_str!("../examples/simple.rs")] +//!``` +//! +//! [^nightly]: If you are on an ARM platform, you need to install a recent nightly toolchain. After +//! installing, we advise to issue `rustup override set nightly` in the top-level directory. pub use circuit::builder::{ CircuitBuilder, SharedCircuit, SubCircuitGate, SubCircuitInput, SubCircuitOutput, }; @@ -5,6 +52,7 @@ pub use circuit::dyn_layers::Circuit; pub use circuit::GateId; pub use parse::bristol; pub use protocols::boolean_gmw::BooleanGate; +pub use seec_channel as channel; pub use seec_macros::sub_circuit; #[cfg(feature = "bench-api")] @@ -12,7 +60,7 @@ pub mod bench; pub mod circuit; pub mod common; pub mod errors; -pub mod evaluate; +pub(crate) mod evaluate; pub mod executor; pub mod mul_triple; pub mod parse; diff --git a/crates/seec/src/mul_triple/arithmetic/mod.rs b/crates/seec/src/mul_triple/arithmetic/mod.rs index 77dc09f..7f1cbba 100644 --- a/crates/seec/src/mul_triple/arithmetic/mod.rs +++ b/crates/seec/src/mul_triple/arithmetic/mod.rs @@ -1,3 +1,5 @@ +//! Arithmetic MTs and providers. + use crate::protocols::{Ring, SetupStorage}; use serde::{Deserialize, Serialize}; diff --git a/crates/seec/src/mul_triple/boolean/mod.rs b/crates/seec/src/mul_triple/boolean/mod.rs index f309f69..59f58a1 100644 --- a/crates/seec/src/mul_triple/boolean/mod.rs +++ b/crates/seec/src/mul_triple/boolean/mod.rs @@ -1,3 +1,4 @@ +//! Boolean MTs and providers. use crate::common::BitVec; use crate::protocols::SetupStorage; use crate::utils; diff --git a/crates/seec/src/mul_triple/boolean/silent_ot.rs b/crates/seec/src/mul_triple/boolean/silent_ot.rs index 8ebaba1..4322273 100644 --- a/crates/seec/src/mul_triple/boolean/silent_ot.rs +++ b/crates/seec/src/mul_triple/boolean/silent_ot.rs @@ -1,3 +1,4 @@ +//! Needs either "silent-ot" or "silent-ot-quasi-cyclic" feature. use crate::common::BitVec; use crate::mul_triple::boolean::MulTriples; use crate::mul_triple::MTProvider; @@ -26,9 +27,9 @@ pub struct SilentMtProvider { impl SilentMtProvider { /// Executes base OTs for silent OT but not num_ots silentOT itself. `Rng` is used to seed /// ChaChaRng's. - /// When the `silent-ot` feature is enabled, the [`MultType::ExConv7x25`] code from libOTe is used by default. + /// When the `silent-ot` feature is enabled, the [`MultType::ExConv7x24`] code from libOTe is used by default. /// If only `silent-ot-quasi-cyclic` is enabled, ZappOT wil not depend on libOTe and the [`MultType::QuasiCyclic`] - /// code is used. Note that this code depends on AVX2 support, and therefore can't be use on ARM. + /// code is used. Note that this code depends on AVX2 support, and therefore can't be used on ARM. pub async fn new( num_ots: usize, rng: Rng, diff --git a/crates/seec/src/mul_triple/mod.rs b/crates/seec/src/mul_triple/mod.rs index ebf54fd..c5c0e75 100644 --- a/crates/seec/src/mul_triple/mod.rs +++ b/crates/seec/src/mul_triple/mod.rs @@ -1,4 +1,4 @@ -//! Multiplication triples. +//! Multiplication triples and providers. use crate::circuit::ExecutableCircuit; use crate::executor::GateOutputs; diff --git a/crates/seec/src/mul_triple/storage.rs b/crates/seec/src/mul_triple/storage.rs index 5c84124..82ca858 100644 --- a/crates/seec/src/mul_triple/storage.rs +++ b/crates/seec/src/mul_triple/storage.rs @@ -1,3 +1,4 @@ +//! Multiplication triple storage of pre-computed MTs. use crate::mul_triple::MTProvider; use crate::protocols::SetupStorage; use async_trait::async_trait; diff --git a/crates/seec/src/parse/mod.rs b/crates/seec/src/parse/mod.rs index 6fd2196..94e07c4 100644 --- a/crates/seec/src/parse/mod.rs +++ b/crates/seec/src/parse/mod.rs @@ -1,3 +1,4 @@ +//! Parses for different circuit formats (Bristol/FUSE). use nom::character::complete::{digit1, multispace0}; use nom::combinator::map_res; use nom::error::{ErrorKind, FromExternalError, ParseError}; diff --git a/crates/seec/src/private_test_utils.rs b/crates/seec/src/private_test_utils.rs index 9fba47c..7bdc6a4 100644 --- a/crates/seec/src/private_test_utils.rs +++ b/crates/seec/src/private_test_utils.rs @@ -1,3 +1,7 @@ +//! Private test utilities - Do Not Use! +//! +//! This module is activated by the "_integration_tests" feature and should not be used by +//! downstream code. It can change in any version. use std::convert::Infallible; use std::fmt::Debug; use std::path::Path; diff --git a/crates/seec/src/secret.rs b/crates/seec/src/secret.rs index 26f9d89..ce6e438 100644 --- a/crates/seec/src/secret.rs +++ b/crates/seec/src/secret.rs @@ -1,3 +1,5 @@ +//! High-level [`Secret`] API to construct a circuit. + use std::borrow::Borrow; use std::cmp::Ordering; use std::fmt::{Debug, Formatter}; diff --git a/crates/seec/src/utils.rs b/crates/seec/src/utils.rs index 0e8fec4..4be873e 100644 --- a/crates/seec/src/utils.rs +++ b/crates/seec/src/utils.rs @@ -1,3 +1,5 @@ +//! Some utilities used in SEEC. + use crate::common::BitVec; use bitvec::prelude::BitStore; use num_integer::div_ceil; @@ -26,10 +28,6 @@ impl<'a, T: ?Sized> PartialEq for ByAddress<'a, T> { impl<'a, T: ?Sized> Eq for ByAddress<'a, T> {} -// -// RangeInclusive start wrapper -// - #[derive(Eq, Debug, Clone, Serialize, Deserialize)] pub(crate) struct RangeInclusiveStartWrapper { pub(crate) range: RangeInclusive, @@ -96,6 +94,7 @@ where bv } +/// Provides methods for fast bitwise AND and XOR on BitVecs. pub(crate) trait BitVecExt: Sized { fn fast_bit_xor_mut(&mut self, other: &Self) -> &mut Self; fn fast_bit_and_mut(&mut self, other: &Self) -> &mut Self; diff --git a/crates/zappot/Cargo.toml b/crates/zappot/Cargo.toml index 22f11f9..57393c6 100644 --- a/crates/zappot/Cargo.toml +++ b/crates/zappot/Cargo.toml @@ -6,13 +6,12 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -silent-ot = ["aligned-vec"] -silent-ot-quasi-cyclic-code = ["silent-ot", "bitpolymul"] +silent-ot-quasi-cyclic-code = ["aligned-vec", "bitpolymul"] # this feature needs libote which provides bindings to libOTe # but since gcc seems to not care about backwards compat, this is prone to break... -silent-ot-silver-code = ["libote", "silent-ot"] -silent-ot-ea-code = ["libote", "silent-ot"] -silent-ot-ex-conv-code = ["libote", "silent-ot"] +silent-ot-silver-code = ["libote", "aligned-vec"] +silent-ot-ea-code = ["libote", "aligned-vec"] +silent-ot-ex-conv-code = ["libote", "aligned-vec"] silent-ot-libote-codes = ["silent-ot-silver-code", "silent-ot-ea-code", "silent-ot-ex-conv-code"] [dependencies] @@ -64,8 +63,8 @@ cc = "1.0.90" [[bench]] name = "bench_main" harness = false -required-features = ["silent-ot", "silent-ot-quasi-cyclic-code"] +required-features = ["silent-ot-libote-codes", "silent-ot-quasi-cyclic-code"] [[example]] name = "silent_ot" -required-features = ["silent-ot", "silent-ot-quasi-cyclic-code"] +required-features = ["silent-ot-libote-codes", "silent-ot-quasi-cyclic-code"] diff --git a/crates/zappot/README.md b/crates/zappot/README.md index f01e5b9..d7aec87 100644 --- a/crates/zappot/README.md +++ b/crates/zappot/README.md @@ -54,4 +54,10 @@ Omitting the `--help` flag will run the example with default values. ## Cargo Features This library provides cargo feature flags to optionally enable additional functionality. Usage: `cargo test --features ` -- `silent_ot` This feature enables the SilentOT implementation. Only compiles with the `avx2` target feature enabled. \ No newline at end of file +- `silent-ot-quasi-cyclic-code`: Enables Silent-OT and the quasi-cyclic code, which requires the `avx2` target feature to be enabled. +- `silent-ot-silver-code`: Enables Silent-OT and the Silver code. This reuses parts of libOTe. (WARNING: Insecure code!) +- `silent-ot-ea-code`: Enables Silent-OT and Expand Accumulate code. This reuses parts of libOTe. +- `silent-ot-ex-conv-code`: Enables Silent-OT and the Expand Convolute code. This reuses parts of libOTe. +- `silent-ot-libote-codes`: Enables all three codes which are based on libOTe. + +These features are additive and can be combined with each other. \ No newline at end of file diff --git a/crates/zappot/benches/benchmarks/mod.rs b/crates/zappot/benches/benchmarks/mod.rs index 15df649..de64bb8 100644 --- a/crates/zappot/benches/benchmarks/mod.rs +++ b/crates/zappot/benches/benchmarks/mod.rs @@ -1,4 +1,3 @@ pub mod aes_rng; pub mod ot_ext; pub mod silent_ot; -pub mod transpose; diff --git a/crates/zappot/benches/benchmarks/transpose.rs b/crates/zappot/benches/benchmarks/transpose.rs deleted file mode 100644 index 8fb3f88..0000000 --- a/crates/zappot/benches/benchmarks/transpose.rs +++ /dev/null @@ -1,29 +0,0 @@ -use criterion::{criterion_group, BenchmarkId, Criterion}; -use rand::thread_rng; -use rand_core::RngCore; -#[cfg(feature = "c-sse")] -use zappot::util::transpose::transpose_c_sse; -use zappot::util::transpose::transpose_rs_sse; - -fn bench_transpose(c: &mut Criterion) { - let mut group = c.benchmark_group("transpose"); - group.sample_size(10); - let rows = 128; - let cols = 2_usize.pow(24); - let mut input = vec![0_u8; rows * cols / 8]; - thread_rng().fill_bytes(&mut input); - - group.bench_with_input( - BenchmarkId::new("c sse", "128 * 2^24 bits"), - &input, - |b, input| b.iter(|| transpose_c_sse(input, rows, cols)), - ); - - group.bench_with_input( - BenchmarkId::new("rs sse", "128 * 2^24 bits"), - &input, - |b, input| b.iter(|| transpose_rs_sse(input, rows, cols)), - ); -} - -criterion_group!(benches, bench_transpose); diff --git a/crates/zappot/src/lib.rs b/crates/zappot/src/lib.rs index 0996c98..c894120 100644 --- a/crates/zappot/src/lib.rs +++ b/crates/zappot/src/lib.rs @@ -14,7 +14,12 @@ use blake2::{ pub mod base_ot; pub mod ot_ext; -#[cfg(feature = "silent-ot")] +#[cfg(any( + feature = "silent-ot-quasi-cyclic-code", + feature = "silent-ot-silver-code", + feature = "silent-ot-ea-code", + feature = "silent-ot-ex-conv-code" +))] pub mod silent_ot; pub mod traits; pub mod util; diff --git a/crates/zappot/src/silent_ot/mod.rs b/crates/zappot/src/silent_ot/mod.rs index 7268ad6..68b8bd4 100644 --- a/crates/zappot/src/silent_ot/mod.rs +++ b/crates/zappot/src/silent_ot/mod.rs @@ -1,39 +1,36 @@ //! SilentOT extension protocol. #![allow(non_snake_case)] +#[cfg(feature = "silent-ot-ea-code")] +use crate::silent_ot::ex_acc_code::{ExAccConf, ExAccEncoder}; +#[cfg(feature = "silent-ot-ex-conv-code")] +use crate::silent_ot::ex_conv_code::{ExConvConf, ExConvEncoder}; use crate::silent_ot::pprf::{ChoiceBits, PprfConfig, PprfOutputFormat}; +#[cfg(feature = "silent-ot-quasi-cyclic-code")] +use crate::silent_ot::quasi_cyclic_encode::{QuasiCyclicConf, QuasiCyclicEncoder}; +#[cfg(feature = "silent-ot-silver-code")] +use crate::silent_ot::silver_code::{SilverConf, SilverEncoder}; use crate::traits::{BaseROTReceiver, BaseROTSender}; use crate::util::aes_hash::FIXED_KEY_HASH; - use crate::util::tokio_rayon::AsyncThreadPool; - use crate::util::Block; use crate::{base_ot, BASE_OT_COUNT}; -use aligned_vec::typenum::U16; -use aligned_vec::AlignedVec; - -use bitvec::order::Lsb0; -use bitvec::slice::BitSlice; +use aes::cipher::BlockEncrypt; +use aes::Aes128; +#[cfg(feature = "silent-ot-quasi-cyclic-code")] +use aligned_vec::{typenum::U16, AlignedVec}; use bitvec::vec::BitVec; +#[cfg(feature = "silent-ot-quasi-cyclic-code")] +use bitvec::{order::Lsb0, slice::BitSlice}; +#[cfg(feature = "silent-ot-quasi-cyclic-code")] use bytemuck::cast_slice; use ndarray::Array2; use num_integer::Integer; +use rand::distributions::Standard; use rand::Rng; use rand_core::{CryptoRng, RngCore}; -use seec_channel::CommunicationError; - -#[cfg(feature = "silent-ot-ea-code")] -use crate::silent_ot::ex_acc_code::{ExAccConf, ExAccEncoder}; -#[cfg(feature = "silent-ot-ex-conv-code")] -use crate::silent_ot::ex_conv_code::{ExConvConf, ExConvEncoder}; -#[cfg(feature = "silent-ot-quasi-cyclic-code")] -use crate::silent_ot::quasi_cyclic_encode::{QuasiCyclicConf, QuasiCyclicEncoder}; -#[cfg(feature = "silent-ot-silver-code")] -use crate::silent_ot::silver_code::{SilverConf, SilverEncoder}; -use aes::cipher::BlockEncrypt; -use aes::Aes128; -use rand::distributions::Standard; use rayon::{ThreadPool, ThreadPoolBuilder}; use remoc::RemoteSend; +use seec_channel::CommunicationError; use serde::{Deserialize, Serialize}; use std::cmp::max; use std::fmt::Debug; @@ -90,10 +87,10 @@ pub enum Encoder { #[derive(Debug, Copy, Clone, Eq, PartialEq)] /// -/// - QuasiCyclic (https://eprint.iacr.org/2019/1159.pdf) -/// - Silver (INSECURE! https://eprint.iacr.org/2021/1150, see https://eprint.iacr.org/2023/882 for attack) -/// - ExpandAccumulate (https://eprint.iacr.org/2022/1014) -/// - ExpandConvolute (https://eprint.iacr.org/2023/882) +/// - QuasiCyclic () +/// - Silver (INSECURE! , see for attack) +/// - ExpandAccumulate () +/// - ExpandConvolute () pub enum MultType { #[cfg(feature = "silent-ot-quasi-cyclic-code")] QuasiCyclic { scaler: usize }, @@ -638,6 +635,7 @@ impl Encoder { S: &[usize], choice_bit_packing: ChoiceBitPacking, ) -> (Vec, Option>) { + #[cfg(feature = "silent-ot-quasi-cyclic-code")] fn calc_sb_blocks<'a>( sb: &'a mut AlignedVec, N2: usize, @@ -955,7 +953,11 @@ mod test { } } - fn check_random(send_messages: &[[Block; 2]], recv_messages: &[Block], choice: &BitSlice) { + fn check_random( + send_messages: &[[Block; 2]], + recv_messages: &[Block], + choice: &bitvec::slice::BitSlice, + ) { let n = send_messages.len(); dbg!(&send_messages[..10]); dbg!(&recv_messages[..10]); diff --git a/crates/zappot/src/silent_ot/quasi_cyclic_encode.rs b/crates/zappot/src/silent_ot/quasi_cyclic_encode.rs index f2c6ea3..7156fcb 100644 --- a/crates/zappot/src/silent_ot/quasi_cyclic_encode.rs +++ b/crates/zappot/src/silent_ot/quasi_cyclic_encode.rs @@ -110,7 +110,7 @@ fn copy_out(dest: &mut [Block], c_mod_p1: &Array2) { #[derive(Copy, Clone, Debug)] /// Configuration options for the quasi cyclic silent OT implementation. Is created by -/// calling the [configure()](`configure`) function. +/// calling the [`QuasiCyclicConf::configure`] function. pub struct QuasiCyclicConf { /// The prime for QuasiCyclic encoding pub(crate) P: usize, diff --git a/crates/zappot/src/util/mod.rs b/crates/zappot/src/util/mod.rs index 7ccbe63..ad0e6f3 100644 --- a/crates/zappot/src/util/mod.rs +++ b/crates/zappot/src/util/mod.rs @@ -5,13 +5,13 @@ pub mod block; pub mod tokio_rayon; pub use block::Block; -#[cfg(any(feature = "silent-ot", test))] +#[allow(unused)] pub(crate) fn log2_floor(val: usize) -> u32 { assert!(val > 0); usize::BITS - val.leading_zeros() - 1 } -#[cfg(any(feature = "silent-ot", test))] +#[allow(unused)] pub(crate) fn log2_ceil(val: usize) -> u32 { let floor = log2_floor(val); if val > (1 << floor) { diff --git a/libs/libote-rs/README.md b/libs/libote-rs/README.md index 37a74a0..4ad1f3e 100644 --- a/libs/libote-rs/README.md +++ b/libs/libote-rs/README.md @@ -1,2 +1,19 @@ -# libOTe-sys +# libOTe-rs +This library builds and provides bindings to partial functionality of [libOTe](https://github.com/osu-crypto/libOTe). + +Currently, we offer bindings to the Silver, EACode, and ExConvCode codes of libOTe. + + +## Development +To compile this project, you need to have the git submodules at `libote` and those in `thirdparty/`cloned. + +libOTe-rs can be compiled, tested, and added as dependency using the normal Cargo tools. + + +## SIMD Intrinsics +libOTe can be compiled with or without usage of SIMD intrinsics such as SSE2, AVX, or AES-NI. We detect the target architecture and available features in our [build script](./build.rs), and pass the appropriate flags to the libOTe build. To ensure these intrinsics are used if supported by your CPU, set the `RUSTFLAGS` environment variable to +```shell +export RUSTFLAGS="-Ctarget-cpu=native" +``` +. This is not needed if you're within the SEEC workspace, as this option is set in the top-level `.cargo/config.toml`.