├── .cargo └── config.toml ├── .github └── workflows │ ├── ci.yaml │ ├── python-release.yml │ └── wheels.yml ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE.txt ├── README.md ├── cbindgen.toml ├── ciphercore-base ├── Cargo.toml ├── example_scripts │ ├── matrix_multiplication │ │ ├── build_graph.sh │ │ ├── inputs.json │ │ └── run.sh │ ├── millionaires_problem │ │ ├── build_graph.sh │ │ ├── inputs.json │ │ └── run.sh │ ├── minimum │ │ ├── build_graph.sh │ │ ├── inputs.json │ │ └── run.sh │ └── sort │ │ ├── build_graph.sh │ │ ├── inputs.json │ │ └── run.sh └── src │ ├── applications.rs │ ├── applications │ ├── matrix_multiplication.py │ ├── matrix_multiplication.rs │ ├── millionaires.py │ ├── millionaires.rs │ ├── minimum.py │ ├── minimum.rs │ └── sort.rs │ ├── bin │ ├── ciphercore_a2b.rs │ ├── ciphercore_compile.rs │ ├── ciphercore_evaluate.rs │ ├── ciphercore_gen_zero_input.rs │ ├── ciphercore_inspect.rs │ ├── ciphercore_matrix_multiplication.rs │ ├── ciphercore_millionaires.rs │ ├── ciphercore_minimum.rs │ ├── ciphercore_sort.rs │ ├── ciphercore_split_parties.rs │ └── ciphercore_visualize_context.rs │ ├── broadcast.rs │ ├── bytes.rs │ ├── constants.rs │ ├── csv │ ├── mod.rs │ ├── test_utils.rs │ └── utils.rs │ ├── custom_ops.rs │ ├── data_types.rs │ ├── data_values.rs │ ├── errors.rs │ ├── evaluators.rs │ ├── evaluators │ ├── get_result_util.rs │ ├── join.rs │ ├── join_test_utils.rs │ └── simple_evaluator.rs │ ├── graphs.rs │ ├── inline.rs │ ├── inline │ ├── associative_iterate_inliner.rs │ ├── data_structures.rs │ ├── empty_state_iterate_inliner.rs │ ├── exponential_inliner.rs │ ├── inline_common.rs │ ├── inline_ops.rs │ ├── inline_test_utils.rs │ └── simple_iterate_inliner.rs │ ├── join_utils.rs │ ├── lib.rs │ ├── mpc.rs │ ├── mpc │ ├── low_mc.rs │ ├── low_mc_constants │ │ ├── generate_matrices.py │ │ ├── key_matrices128.dat │ │ ├── key_matrices80.dat │ │ ├── linear_layer_matrices128.dat │ │ ├── linear_layer_matrices80.dat │ │ ├── round_constants128.dat │ │ └── round_constants80.dat │ ├── mpc_apply_permutation.rs │ ├── mpc_arithmetic.rs │ ├── mpc_compiler.rs │ ├── mpc_conversion.rs │ ├── mpc_equivalence_class.rs │ ├── mpc_psi.rs │ ├── mpc_radix_sort.rs │ ├── mpc_truncate.rs │ ├── resharing.rs │ └── utils.rs │ ├── ops.rs │ ├── ops │ ├── adder.rs │ ├── auc.rs │ ├── broadcast_test.rs │ ├── clip.rs │ ├── comparisons.rs │ ├── fixed_precision.rs │ ├── fixed_precision │ │ ├── fixed_multiply.rs │ │ └── fixed_precision_config.rs │ ├── goldschmidt_division.rs │ ├── integer_key_sort.rs │ ├── inverse_sqrt.rs │ ├── long_division.rs │ ├── min_max.rs │ ├── multiplexer.rs │ ├── newton_inversion.rs │ ├── pwl.rs │ ├── pwl │ │ ├── approx_exponent.rs │ │ ├── approx_gelu.rs │ │ ├── approx_gelu_derivative.rs │ │ ├── approx_pointwise.rs │ │ └── approx_sigmoid.rs │ ├── taylor_exponent.rs │ └── utils.rs │ ├── optimizer.rs │ ├── optimizer │ ├── constant_optimizer.rs │ ├── dangling_nodes_optimizer.rs │ ├── duplicates_optimizer.rs │ ├── meta_operation_optimizer.rs │ └── optimize.rs │ ├── random.rs │ ├── slices.rs │ ├── test_data │ └── version_testcase.txt │ ├── type_inference.rs │ ├── typed_value.rs │ ├── typed_value_operations.rs │ ├── typed_value_secret_shared.rs │ ├── typed_value_secret_shared │ └── replicated_shares.rs │ ├── typed_value_serialization.rs │ └── version.rs ├── ciphercore-utils ├── Cargo.toml └── src │ ├── execute_main.rs │ ├── lib.rs │ └── test_utils.rs ├── ciphercore-wrappers ├── python │ ├── CMakeLists.txt │ ├── Cargo.toml │ ├── Example.ipynb │ ├── LICENSE │ ├── MANIFEST.in │ ├── README.md │ ├── a.py │ ├── build_wheels.sh │ ├── include │ │ ├── CResults.h │ │ ├── CStr.h │ │ ├── CVec.h │ │ ├── Error.h │ │ ├── ciphercore_adapters.h │ │ ├── ciphercore_auxiliary_structs.h │ │ ├── ciphercore_context.h │ │ ├── ciphercore_data_types.h │ │ ├── ciphercore_graph.h │ │ ├── ciphercore_node.h │ │ └── ciphercore_structs.h │ ├── prepare.sh │ ├── py │ │ └── ciphercore │ │ │ ├── __init__.py │ │ │ └── internal │ │ │ └── __init__.py │ ├── pyproject.toml │ ├── setup.py │ ├── src │ │ ├── ciphercore_native.cpp │ │ └── lib.rs │ └── test.sh └── pywrapper-macro │ ├── Cargo.toml │ └── src │ └── lib.rs ├── generate_c_header.sh ├── reference ├── images │ ├── arr_mul.svg │ ├── ciphercore_architecture.png │ ├── manual_graph.svg │ ├── matmul.svg │ ├── millionaires.svg │ ├── minimum.svg │ ├── runtime.svg │ ├── set_intersection_graph_rot90.svg │ ├── tutorial_graph_mpc.svg │ └── tutorial_graph_plain.svg ├── main.md └── runtime.md └── runtime ├── example ├── data │ ├── median │ │ ├── graph.json │ │ ├── party0 │ │ │ └── input.txt │ │ ├── party1 │ │ │ └── input.txt │ │ └── party2 │ │ │ └── input.txt │ └── two_millionaires │ │ ├── graph.json │ │ ├── party0 │ │ └── input.txt │ │ ├── party1 │ │ └── input.txt │ │ └── party2 │ │ └── input.txt ├── python │ └── combined_example.py └── scripts │ ├── do_all.sh │ ├── run_compute_nodes.sh │ ├── run_data_nodes.sh │ ├── run_orchestrator.sh │ └── tear_down.sh └── proto ├── data.proto ├── party.proto ├── results.proto └── value.proto /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.'cfg(all())'] 2 | rustflags = [ 3 | "-Aclippy::needless_range_loop", 4 | ] -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | workflow_dispatch: 5 | 6 | # How to use sccache: 7 | # https://www.infinyon.com/blog/2021/04/github-actions-best-practices/ 8 | 9 | jobs: 10 | ci: 11 | name: CI 12 | runs-on: [self-hosted, linux] 13 | env: 14 | RUSTC_WRAPPER: sccache 15 | # See https://matklad.github.io/2021/09/04/fast-rust-builds.html 16 | CARGO_INCREMENTAL: 0 17 | # It assumes it is already installed: 18 | # https://github.com/rui314/mold 19 | RUSTFLAGS: -D warnings -C link-arg=-fuse-ld=mold -Aclippy::needless_range_loop 20 | RUSTDOCFLAGS: -D warnings 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Install libs 24 | run: sudo apt install -y g++ gcc pkg-config libssl-dev cmake libpq-dev clang libclang-dev llvm-dev librocksdb-dev 25 | - name: Install Rust 26 | uses: actions-rs/toolchain@v1 27 | with: 28 | toolchain: 1.69 29 | override: true 30 | components: rustfmt, clippy 31 | - name: Toolchain info 32 | run: | 33 | cargo --version --verbose 34 | rustc --version 35 | cargo clippy --version 36 | - name: Make sccache available 37 | # It assumes it is already installed: 38 | # https://github.com/mozilla/sccache 39 | run: echo "$HOME/.local/bin" >> $GITHUB_PATH 40 | - name: Print sccache stats (before run) 41 | run: sccache --show-stats 42 | - name: Cargo fmt 43 | run: cargo fmt --check 44 | - name: Cargo clippy 45 | run: cargo clippy --workspace --tests --all-targets --all-features 46 | - name: Cargo docs 47 | run: cargo doc --no-deps 48 | - name: Cargo test 49 | run: cargo test --workspace 50 | - name: Print sccache stats (after run) 51 | run: sccache --show-stats 52 | -------------------------------------------------------------------------------- /.github/workflows/python-release.yml: -------------------------------------------------------------------------------- 1 | name: Python Release 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | env: 7 | PYPI_TOKEN: ${{ secrets.PYPI_TOKEN_DIST }} 8 | DIST_DIR: ${{ github.sha }} 9 | 10 | jobs: 11 | create_wheels_manylinux: 12 | runs-on: ubuntu-latest 13 | name: Create wheels for manylinux2014 14 | container: quay.io/pypa/manylinux2014_x86_64 15 | steps: 16 | # v1 was required when using manylinux2010, not sure about manylinux2014 17 | - uses: actions/checkout@v1 18 | 19 | - name: Install dependencies 20 | run: yum install -y openssl-devel 21 | 22 | - name: Build and audit wheels 23 | working-directory: ./ciphercore-wrappers/python/ 24 | run: sh build_wheels.sh 25 | 26 | - name: Upload artifact 27 | uses: actions/upload-artifact@v1 28 | with: 29 | name: manylinux 30 | path: ./ciphercore-wrappers/python/dist/ 31 | 32 | create_wheels_macos: 33 | name: Create wheels for Mac OS 34 | env: 35 | MACOSX_DEPLOYMENT_TARGET: 10.11 36 | runs-on: macos-latest 37 | strategy: 38 | matrix: 39 | python: ["3.7", "3.8", "3.9", "3.10"] 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v2 43 | 44 | - name: Install Rust 45 | uses: actions-rs/toolchain@v1 46 | with: 47 | toolchain: stable 48 | override: true 49 | 50 | - name: Install Python 51 | uses: actions/setup-python@v1 52 | with: 53 | python-version: ${{ matrix.python }} 54 | architecture: x64 55 | 56 | - name: Install dependencies 57 | run: | 58 | # On old versions of python there is an old version of setuptools already installed 59 | pip install setuptools wheel setuptools-rust==0.11.3 --ignore-installed --force-reinstall 60 | 61 | - name: Build wheel 62 | working-directory: ./ciphercore-wrappers/python 63 | run: python setup.py bdist_wheel 64 | 65 | - name: Rename wheels 66 | shell: bash 67 | working-directory: ./ciphercore-wrappers/python/dist 68 | run: for file in *.whl ; do mv $file ${file//macosx_10_1[0-9]/macosx_10_11} ; done 69 | 70 | - name: Upload artifact 71 | uses: actions/upload-artifact@v2 72 | with: 73 | path: ./ciphercore-wrappers/python/dist/*.whl 74 | -------------------------------------------------------------------------------- /.github/workflows/wheels.yml: -------------------------------------------------------------------------------- 1 | name: Build Wheels 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build_wheels: 8 | name: Build wheels on ${{ matrix.os }} 9 | runs-on: ${{ matrix.os }} 10 | strategy: 11 | matrix: 12 | os: [ubuntu-20.04, macOS-10.15] 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | # Used to host cibuildwheel 18 | - uses: actions/setup-python@v2 19 | 20 | - name: Install cibuildwheel 21 | run: python -m pip install cibuildwheel==2.5.0 22 | 23 | - name: Build wheels 24 | run: python -m cibuildwheel --output-dir wheelhouse ciphercore-wrappers/python 25 | # Uncomment for faster testing. 26 | #env: 27 | # CIBW_BUILD: cp310-* 28 | # CIBW_ARCHS: auto64 29 | 30 | - uses: actions/upload-artifact@v2 31 | with: 32 | path: ./wheelhouse/*.whl 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target/ 3 | .vscode/ 4 | *.pem 5 | *_pb2_grpc.py 6 | *_pb2.py 7 | *.pyc 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ciphercore-wrappers/python/external/pybind11"] 2 | path = ciphercore-wrappers/python/external/pybind11 3 | url = ../pybind11 4 | branch = v2.9 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changes in CipherCore 0.3.0 2 | 3 | * **Private set intersection of sparse columns** Implemented as a new operation `JoinWithColumnMasks`. 4 | 5 | # Changes in CipherCore 0.2.0 6 | 7 | * **Added support for 128-bit integers** 8 | * **Postpone resharing** Performance optimization to reduce communication by delaying conversions from 3-out-of-3 secret shares to 2-out-of-3, if 2-out-of-3 shares are not immediately required by proceeding operations (e.g. additions). 9 | 10 | # Changes in CipherCore 0.1.3 11 | 12 | * **Better SMPC protocol for Private set intersection** The new protocol follows the description of the Join protocol from . The operation is renamed from `SetIntersection` to `Join`, and supports four join flavors: Inner, Left, Union and Full. 13 | * **Concatenate** Primitive operation to concatenate arrays. 14 | * **Apply permutation** Primitive operation that permutes private or public data using a private or public permutation. 15 | * **General matrix multiplication** Primitive operation `Gemm` that generalizes `Matmul`. 16 | * **Division** Two algorithms implemented as custom operations: Approximate GoldschmidtDivision and slow but exact LongDivision. 17 | * **Sort** Efficient radix sort with O(n) complexity from . 18 | 19 | # Changes in CipherCore 0.1.2 20 | 21 | * **Runtime documentation + examples** We now provide [detailed documentation](https://github.com/ciphermodelabs/ciphercore/blob/main/reference/runtime.md) for CipherCore runtime, which can be used to execute a secure protocol produced by CipherCore compiler between actual parties over the network. [E-mail us](mailto:ciphercore@ciphermode.tech) to request access to play with runtime. 22 | * **Private set intersection** We added a simple implementation of [private set intersection](https://en.wikipedia.org/wiki/Private_set_intersection) based on sorting (available as a binary `ciphercore_set_intersection`). 23 | * **Exponent, Sigmoid, GeLU** We implemented efficiently several non-linear transformations that are crucial for machine learning (e.g., transformers). 24 | * **Better SMPC protocol for Truncate** The newly implemented protocol for the Truncate operation is more stable and never makes large errors, which makes it more suitable for fixed-point arithmetic necessary for training machine learning models. 25 | * **MixedMultiply + improvements in InverseSqrt and NewtonInversion** We added a new operation and the corresponding secure protocol for multiplying a bit and an integer avoid an expensive conversion. This allows to significantly improve the performance of various operations including ReLU and division. 26 | * **Sorting: custom operation + support for signed integers** Now sorting is implemented as a custom operation, and it supports signed integers inputs. 27 | * **Efficiency improvements: optimizer, log-depth inlining** We added several crucial improvements to the optimizing and inlining passes of the compiler. 28 | * **More complete and documented Python wrapper** Finally, we overhauled and improved our Python wrapper pretty significantly. In particular, almost all the classes and functions now have readable docstrings. -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "ciphercore-utils", 5 | "ciphercore-base", 6 | "ciphercore-wrappers/python", 7 | "ciphercore-wrappers/pywrapper-macro", 8 | ] 9 | 10 | default-members = [ 11 | "ciphercore-utils", 12 | "ciphercore-base", 13 | ] 14 | 15 | [patch.crates-io] 16 | ciphercore-base = { path = "ciphercore-base" } 17 | ciphercore-utils = { path = "ciphercore-utils" } 18 | pywrapper-macro = { path = "ciphercore-wrappers/pywrapper-macro" } 19 | ciphercore-pywrapper = { path = "ciphercore-wrappers/python" } 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CipherCore 2 | 3 | CipherCore is a user-friendly secure computation engine based on [secure multi-party computation](https://en.wikipedia.org/wiki/Secure_multi-party_computation). 4 | 5 | To install CipherCore, run one of the following commands: 6 | * `pip install ciphercore` -- installs the Python wrapper for CipherCore computation graph API 7 | * `cargo install ciphercore-base` -- builds and installs the CipherCore compiler and other CLI tools from source (requires [Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html)) 8 | * `docker pull ciphermodelabs/ciphercore:latest` -- pulls a Docker image with binary distribution of CipherCore 9 | * `docker pull ciphermodelabs/runtime_example:latest` -- pulls a Docker image that contains CipherCore runtime (requires an access token, [e-mail us](mailto:ciphercore@ciphermode.tech) to request access). 10 | 11 | Check out the [complete documentation](https://github.com/ciphermodelabs/ciphercore/blob/main/reference/main.md), which includes a tutorial, several examples and a comprehensive guide to CipherCore. 12 | 13 | If you have any questions, or, more generally, would like to discuss CipherCore, please [join the Slack community](https://communityinviter.com/apps/ciphercore/ciphercore/). 14 | 15 | # Five-minute intro 16 | 17 | Suppose that three parties, Alice, Bob and Charlie, would like to perform the following computation: 18 | 1. Alice and Bob each have one 32-bit integer, `x` and `y`, respectively, which is kept secret. 19 | 2. Charlie wants to know if `x` is greater than `y`, but, crucially, Alice and Bob don't trust Charlie, each other, or any other party with their secrets. 20 | 21 | This is an instance of a general problem of secure multi-party computation (SMPC), where several parties would like to jointly compute some function (comparison, in the above case) of their inputs in a way that no information about the inputs (other than what can be inferred from the output) leaks to any other party. Currently, CipherCore supports [the ABY3 protocol](https://eprint.iacr.org/2018/403.pdf), which works for three parties and is one of the most efficient available protocols. 22 | 23 | First, let us formulate our problem as a CipherCore computation graph: 24 | ```Python 25 | import ciphercore as cc 26 | 27 | c = cc.create_context() 28 | with c: 29 | g = c.create_graph() 30 | with g: 31 | x = g.input(cc.scalar_type(cc.INT32)) # Alice's input 32 | y = g.input(cc.scalar_type(cc.INT32)) # Bob's input 33 | output = x.a2b() > y.a2b() # Charlie's output 34 | # (`a2b` converts integers into bits, which is necessary for comparisons) 35 | output.set_as_output() 36 | g.set_as_main() 37 | print(c) 38 | ``` 39 | 40 | and serialize it by running the above script with Python and redirecting its output to the file `a.json`. 41 | 42 | Next, let's compile the computation graph using CipherCore CLI compiler as follows: 43 | ``` 44 | ciphercore_compile a.json simple 0,1 2 > b.json 45 | ``` 46 | Here `0,1` means that the inputs belong to party `0` (Alice) and party `1` (Bob), respectively. And `2` means that the output should be revealed to party `2` (Charlie). 47 | 48 | The file `b.json` contains a *full description* of the secure protocol for our comparison problem. We can peek into it by running the inspection tool as follows: 49 | ``` 50 | ciphercore_inspect b.json 51 | ``` 52 | which outputs various useful statistics, including the number of network rounds that is necessary to perform the computation securely (45, in our case), and the total amount of traffic that needs to be exchanged (233 bytes). 53 | 54 | To check that the functionality of the secure protocol stayed intact, let's run it locally. Let's first create a file `inputs.json` with Alice's and Bob's inputs: 55 | ```JSON 56 | [ 57 | {"kind": "scalar", "type": "i32", "value": 32}, 58 | {"kind": "scalar", "type": "i32", "value": 12} 59 | ] 60 | ``` 61 | 62 | To evaluate the secure protocol on the inputs 32 and 12, we run 63 | ``` 64 | ciphercore_evaluate b.json inputs.json 65 | ``` 66 | and correctly get: 67 | ```JSON 68 | {"kind": "scalar", "type": "bit", "value": 1} 69 | ``` 70 | since the Alice's number is larger than Bob's. 71 | 72 | However, even though the description of the secure protocol for comparison is *fully contained* within `b.json`, this is just the local simulation of the protocol. To execute the secure protocol between actual parties over the network reliably and with high performance, one needs CipherCore runtime, which we provide [upon request](mailto:ciphercore@ciphermode.tech). 73 | 74 | The above example is a toy one, however, one can use CipherCore to perform machine learning training and inference and analytics at a large scale. Stay tuned! -------------------------------------------------------------------------------- /cbindgen.toml: -------------------------------------------------------------------------------- 1 | language = "C" 2 | documentation = false 3 | include_guard = "CIPHERCORE_ADAPTER_H" 4 | [parse] 5 | # Whether to parse dependent crates and include their types in the output 6 | # default: false 7 | parse_deps = true 8 | include = ["ciphercore-base", "ciphercore-utils"] 9 | -------------------------------------------------------------------------------- /ciphercore-base/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ciphercore-base" 3 | version = "0.3.3" 4 | authors = ["CipherMode Labs, Inc."] 5 | edition = "2021" 6 | description = "The base package of CipherCore: computation graphs API, Secure MPC Compiler, utilities for graph evaluation and inspection" 7 | license = "Apache-2.0" 8 | repository = "https://github.com/ciphermodelabs/ciphercore/" 9 | readme = "../README.md" 10 | keywords = ["data-sharing", "cryptography", "secure-computation", "secure-mpc", "privacy-enhancing"] 11 | categories = ["cryptography"] 12 | homepage = "https://www.ciphermode.com/" 13 | 14 | [dependencies] 15 | ciphercore-utils = "0.3.0" 16 | pywrapper-macro = { version = "0.3.1", optional = true} 17 | serde = { version = "1.0.140", features = ["derive", "rc"] } 18 | serde_json = { version = "1.0.94", features = ["arbitrary_precision"] } 19 | getrandom = { version = "0.2.9", features = ["js"] } 20 | typetag = "0.2.8" 21 | petgraph = "0.6.0" 22 | maplit = "1.0.2" 23 | rand = "0.8" 24 | uuid = { version = "0.8.2", features = ["v4"] } 25 | chrono = "0.4.26" 26 | atomic_refcell = "0.1.8" 27 | env_logger = "0.9.0" 28 | clap = { version = "3.2.23", features = ["derive"] } 29 | ndarray = "0.15.1" 30 | json = "0.12.4" 31 | arbitrary = { version = "1", optional = true, features = ["derive"] } 32 | pyo3 = { version = "0.17.1", optional = true, features = ["extension-module"] } 33 | anyhow = { version = "1.0.70", features = ["backtrace"] } 34 | tonic = { version = "0.9.2", default-features = false, features = [ 35 | "codegen", 36 | "prost", 37 | ], optional = true } 38 | aes = "0.8.2" 39 | cipher = { version = "0.4.4", features = ["block-padding"] } 40 | csv = "1.1" 41 | 42 | [target.'cfg(target_arch = "wasm32")'.dependencies] 43 | wasm-bindgen = { version = "0.2.86", features = ["serde-serialize"] } 44 | 45 | [dev-dependencies] 46 | serde_test = "1.0.130" 47 | bincode = "1.3.3" 48 | tokio = {version = "1.26.0", features = ["macros", "rt", "rt-multi-thread"]} 49 | futures = "0.3.21" 50 | futures-core = "0.3.21" 51 | futures-util = "0.3.21" 52 | 53 | [features] 54 | default = [] 55 | fuzzing = [] 56 | py-binding = ["dep:pyo3", "dep:pywrapper-macro"] 57 | tonic-errors = ["dep:tonic"] 58 | stderr-to-log = [] 59 | 60 | [[bin]] 61 | name = "ciphercore_compile" 62 | path = "src/bin/ciphercore_compile.rs" 63 | 64 | [[bin]] 65 | name = "ciphercore_inspect" 66 | path = "src/bin/ciphercore_inspect.rs" 67 | 68 | [[bin]] 69 | name = "ciphercore_matrix_multiplication" 70 | path = "src/bin/ciphercore_matrix_multiplication.rs" 71 | 72 | [[bin]] 73 | name = "ciphercore_millionaires" 74 | path = "src/bin/ciphercore_millionaires.rs" 75 | 76 | [[bin]] 77 | name = "ciphercore_minimum" 78 | path = "src/bin/ciphercore_minimum.rs" 79 | 80 | [[bin]] 81 | name = "ciphercore_sort" 82 | path = "src/bin/ciphercore_sort.rs" 83 | 84 | [[bin]] 85 | name = "ciphercore_visualize_context" 86 | path = "src/bin/ciphercore_visualize_context.rs" 87 | 88 | [[bin]] 89 | name = "ciphercore_evaluate" 90 | path = "src/bin/ciphercore_evaluate.rs" 91 | 92 | [[bin]] 93 | name = "ciphercore_split_parties" 94 | path = "src/bin/ciphercore_split_parties.rs" 95 | 96 | [[bin]] 97 | name = "ciphercore_gen_zero_input" 98 | path = "src/bin/ciphercore_gen_zero_input.rs" 99 | 100 | [[bin]] 101 | name = "ciphercore_a2b" 102 | path = "src/bin/ciphercore_a2b.rs" 103 | -------------------------------------------------------------------------------- /ciphercore-base/example_scripts/matrix_multiplication/build_graph.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -xe 4 | ciphercore_matrix_multiplication -s i32 2 3 4 > plain_graph.json && ciphercore_compile plain_graph.json simple 1,2 0 > mpc_graph.json 5 | -------------------------------------------------------------------------------- /ciphercore-base/example_scripts/matrix_multiplication/inputs.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "kind": "array", 4 | "type": "i32", 5 | "value": [[1,2,3],[4,5,6]] 6 | }, 7 | { 8 | "kind": "array", 9 | "type": "i32", 10 | "value": [[1,2,3,4],[5,6,7,8],[9,10,11,12]] 11 | } 12 | ] -------------------------------------------------------------------------------- /ciphercore-base/example_scripts/matrix_multiplication/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -xe 4 | ciphercore_evaluate mpc_graph.json inputs.json 5 | -------------------------------------------------------------------------------- /ciphercore-base/example_scripts/millionaires_problem/build_graph.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -xe 4 | ciphercore_millionaires > plain_graph.json && ciphercore_compile plain_graph.json simple 0,1 0,1 > mpc_graph.json 5 | -------------------------------------------------------------------------------- /ciphercore-base/example_scripts/millionaires_problem/inputs.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "kind": "scalar", 4 | "type": "u32", 5 | "value": 1000 6 | }, 7 | { 8 | "kind": "scalar", 9 | "type": "u32", 10 | "value": 2000 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /ciphercore-base/example_scripts/millionaires_problem/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -xe 4 | ciphercore_evaluate mpc_graph.json inputs.json 5 | -------------------------------------------------------------------------------- /ciphercore-base/example_scripts/minimum/build_graph.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -xe 4 | ciphercore_minimum -s u32 4 > plain_graph.json && ciphercore_compile plain_graph.json depth-optimized-default 0 1 > mpc_graph.json 5 | -------------------------------------------------------------------------------- /ciphercore-base/example_scripts/minimum/inputs.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "kind": "array", 4 | "type": "i32", 5 | "value": [26, 93, 31, 10, 31, 2, 92, 89, 62, 93, 10, 7, 18, 78, 83, 74] 6 | } 7 | ] -------------------------------------------------------------------------------- /ciphercore-base/example_scripts/minimum/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -xe 4 | ciphercore_evaluate mpc_graph.json inputs.json 5 | -------------------------------------------------------------------------------- /ciphercore-base/example_scripts/sort/build_graph.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -xe 4 | ciphercore_sort -s i32 16 > plain_graph.json && ciphercore_compile plain_graph.json depth-optimized-default 0 1 > mpc_graph.json 5 | -------------------------------------------------------------------------------- /ciphercore-base/example_scripts/sort/inputs.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "kind": "array", 4 | "type": "i32", 5 | "value": [26, 93, 31, 10, 31, 2, 92, 89, 62, 93, 10, 7, 18, 78, 83, 74] 6 | } 7 | ] 8 | -------------------------------------------------------------------------------- /ciphercore-base/example_scripts/sort/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -xe 4 | ciphercore_evaluate mpc_graph.json inputs.json 5 | -------------------------------------------------------------------------------- /ciphercore-base/src/applications.rs: -------------------------------------------------------------------------------- 1 | //! Examples of computation graphs for several non-trivial tasks 2 | pub mod matrix_multiplication; 3 | pub mod millionaires; 4 | pub mod minimum; 5 | pub mod sort; 6 | -------------------------------------------------------------------------------- /ciphercore-base/src/applications/matrix_multiplication.py: -------------------------------------------------------------------------------- 1 | import ciphercore as cc 2 | 3 | # Number of rows of the first matrix 4 | n = 2 5 | # Number of columns of the first matrix (and number of rows of the second matrix) 6 | m = 3 7 | # Number of columns of the second matrix 8 | k = 4 9 | 10 | # Scalar type of matrix elements 11 | st = cc.INT32 12 | 13 | # Create a context 14 | c = cc.create_context() 15 | with c: 16 | # Create a graph in a given context that will be used for matrix multiplication 17 | g = c.create_graph() 18 | with g: 19 | # Create types of input matrices. 20 | # Matrices can be represented as arrays with two 2-dimensional shapes. 21 | # First, create the array type of a first matrix with shape `[n, m]`, which corresponds to a (n x m)-matrix. 22 | first_matrix_type = cc.array_type([n, m], st) 23 | # Second, create the array type of a second matrix with shape `[m, k]`, which corresponds to a (m x k)-matrix. 24 | second_matrix_type = cc.array_type([m, k], st) 25 | 26 | # For each input matrix, add input nodes to the empty graph g created above. 27 | # Input nodes require the types of input matrices generated in previous lines. 28 | first_matrix_input = g.input(first_matrix_type) 29 | second_matrix_input = g.input(second_matrix_type) 30 | 31 | # Matrix multiplication is a built-in function of CipherCore, so it can be computed by a single computational node. 32 | output = first_matrix_input @ second_matrix_input 33 | 34 | # Before computation, every graph should be finalized, which means that it should have a designated output node. 35 | # This can be done by calling `g.set_output_node(output)?` or as below. 36 | output.set_as_output() 37 | # Set this graph as main to be able to finalize the context 38 | g.set_as_main() 39 | # Serialize the context and print it to stdout 40 | print(c) 41 | -------------------------------------------------------------------------------- /ciphercore-base/src/applications/matrix_multiplication.rs: -------------------------------------------------------------------------------- 1 | //! Multiplication of two matrices 2 | use crate::data_types::{array_type, ScalarType}; 3 | use crate::errors::Result; 4 | use crate::graphs::{Context, Graph}; 5 | 6 | /// Creates a graph that multiplies a matrix with integer entries of shape n x m by a matrix of shape m x k. 7 | /// 8 | /// # Arguments 9 | /// 10 | /// * `context` - context where a matrix multiplication graph should be created 11 | /// * `n` - number of rows of the first matrix, 12 | /// * `m` - number of columns of the first matrix (and number of rows of the second matrix) 13 | /// * `k` - number of columns of the first matrix 14 | /// * `st` - scalar type of matrix elements 15 | /// 16 | /// # Returns 17 | /// 18 | /// Graph that multiplies two matrices 19 | pub fn create_matrix_multiplication_graph( 20 | context: Context, 21 | n: u64, 22 | m: u64, 23 | k: u64, 24 | st: ScalarType, 25 | ) -> Result { 26 | // Create a graph in a given context that will be used for matrix multiplication 27 | let g = context.create_graph()?; 28 | 29 | // Create types of input matrices. 30 | // Matrices can be represented as arrays with two 2-dimensional shapes. 31 | // First, create the array type of a first matrix with shape `[n, m]`, which corresponds to a (n x m)-matrix. 32 | let first_matrix_type = array_type(vec![n, m], st); 33 | // Second, create the array type of a second matrix with shape `[m, k]`, which corresponds to a (m x k)-matrix. 34 | let second_matrix_type = array_type(vec![m, k], st); 35 | 36 | // For each input matrix, add input nodes to the empty graph g created above. 37 | // Input nodes require the types of input matrices generated in previous lines. 38 | let first_matrix_input = g.input(first_matrix_type)?; 39 | let second_matrix_input = g.input(second_matrix_type)?; 40 | 41 | // Matrix multiplication is a built-in function of CipherCore, so it can be computed by a single computational node. 42 | let output = first_matrix_input.matmul(second_matrix_input)?; 43 | 44 | // Before computation, every graph should be finalized, which means that it should have a designated output node. 45 | // This can be done by calling `g.set_output_node(output)?` or as below. 46 | output.set_as_output()?; 47 | // Finalization checks that the output node of the graph g is set. After finalization the graph can't be changed. 48 | g.finalize()?; 49 | 50 | Ok(g) 51 | } 52 | 53 | #[cfg(test)] 54 | mod tests { 55 | use crate::data_types::{INT32, INT64}; 56 | use crate::data_values::Value; 57 | use crate::evaluators::random_evaluate; 58 | use crate::graphs::create_context; 59 | use std::ops::Not; 60 | 61 | use super::*; 62 | 63 | fn test_matmul_helper< 64 | T1: TryInto + Not + TryInto + Copy, 65 | T2: TryInto + Not + TryInto + Copy, 66 | >( 67 | input1_value: &[T1], 68 | input2_value: &[T2], 69 | n: u64, 70 | m: u64, 71 | k: u64, 72 | st: ScalarType, 73 | ) -> Value { 74 | || -> Result { 75 | let c = create_context()?; 76 | let g = create_matrix_multiplication_graph(c.clone(), n, m, k, st)?; 77 | g.set_as_main()?; 78 | c.finalize()?; 79 | 80 | let left_matrix_type = array_type(vec![n, m], st); 81 | let right_matrix_type = array_type(vec![m, k], st); 82 | let val1 = 83 | Value::from_flattened_array(input1_value, left_matrix_type.get_scalar_type())?; 84 | let val2 = 85 | Value::from_flattened_array(input2_value, right_matrix_type.get_scalar_type())?; 86 | random_evaluate(g, vec![val1, val2]) 87 | }() 88 | .unwrap() 89 | } 90 | 91 | #[test] 92 | fn test_matmul() { 93 | || -> Result<()> { 94 | assert!( 95 | test_matmul_helper( 96 | &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 97 | &[1, 2, 3, 4, 5, 6], 98 | 4, 99 | 3, 100 | 2, 101 | INT32 102 | ) == Value::from_flattened_array(&[22, 28, 49, 64, 76, 100, 103, 136], INT32)? 103 | ); 104 | assert!( 105 | test_matmul_helper( 106 | &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 107 | &[6, 5, 4, 3, 2, 1], 108 | 4, 109 | 3, 110 | 2, 111 | INT32 112 | ) == Value::from_flattened_array(&[20, 14, 56, 41, 92, 68, 128, 95], INT32)? 113 | ); 114 | assert!( 115 | test_matmul_helper( 116 | &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 117 | &[12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1], 118 | 1, 119 | 12, 120 | 1, 121 | INT32 122 | ) == Value::from_flattened_array(&[364], INT32)? 123 | ); 124 | assert!( 125 | test_matmul_helper( 126 | &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 127 | &[12, 45, 56], 128 | 4, 129 | 3, 130 | 1, 131 | INT32 132 | ) == Value::from_flattened_array(&[270, 609, 948, 1287], INT32)? 133 | ); 134 | assert!( 135 | test_matmul_helper( 136 | &[12, 45, 56], 137 | &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 138 | 1, 139 | 3, 140 | 4, 141 | INT32 142 | ) == Value::from_flattened_array(&[741, 854, 967, 1080], INT32)? 143 | ); 144 | assert!( 145 | test_matmul_helper( 146 | &[-1, 4, -3, -5, -6, 2], 147 | &[-1, -2, 3, -4, -5, -6], 148 | 2, 149 | 3, 150 | 2, 151 | INT32 152 | ) == Value::from_flattened_array(&[28, 4, -23, 22], INT32)? 153 | ); 154 | assert!( 155 | test_matmul_helper( 156 | &[-1, 4, -3, -5, -6, 2], 157 | &[-1, -2, 3, -4, -5, -6], 158 | 2, 159 | 3, 160 | 2, 161 | INT64 162 | ) == Value::from_flattened_array(&[28, 4, -23, 22], INT64)? 163 | ); 164 | Ok(()) 165 | }() 166 | .unwrap(); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /ciphercore-base/src/applications/millionaires.py: -------------------------------------------------------------------------------- 1 | import ciphercore as cc 2 | 3 | # Create a context 4 | c = cc.create_context() 5 | with c: 6 | # Create a graph in a given context that will be used for matrix multiplication 7 | g = c.create_graph() 8 | with g: 9 | # For each millionaire, add input nodes to the empty graph g created above. 10 | # Input nodes are instantiated with the single unsigned 32-bit integer. 11 | # This should be enough to represent the wealth of each millionaire. 12 | first_millionaire = g.input(cc.scalar_type(cc.UINT32)) 13 | second_millionaire = g.input(cc.scalar_type(cc.UINT32)) 14 | 15 | # For each millionaire, convert integer value to binary array in order to perform comparison. 16 | # Add custom operation to the graph specifying the custom operation and its arguments: `first_millionaire` and `second_millionaire`. 17 | # This operation will compute the bit `(first_millionaire > second_millionaire)`. 18 | output = first_millionaire.a2b() > second_millionaire.a2b() 19 | 20 | # Before computation, every graph should be finalized, which means that it should have a designated output node. 21 | # This can be done by calling `g.set_output_node(output)?` or as below. 22 | output.set_as_output() 23 | # Set this graph as main to be able to finalize the context 24 | g.set_as_main() 25 | # Serialize the context and print it to stdout 26 | print(c) 27 | -------------------------------------------------------------------------------- /ciphercore-base/src/applications/millionaires.rs: -------------------------------------------------------------------------------- 1 | //! [Millionaires' problem](https://en.wikipedia.org/wiki/Yao%27s_Millionaires%27_problem). 2 | //! Two millionaires want to find out who is richer without revealing their wealth. 3 | use crate::custom_ops::CustomOperation; 4 | use crate::data_types::{scalar_type, UINT32}; 5 | use crate::errors::Result; 6 | use crate::graphs::{Context, Graph}; 7 | use crate::ops::comparisons::GreaterThan; 8 | 9 | /// Creates a graph that solves Millionaires' problem. 10 | /// 11 | /// It computes a bit that is equal to 1 if the first millionaire is richer than the second one and 0 otherwise. 12 | /// 13 | /// # Arguments 14 | /// 15 | /// * `context` - context where a Millionaires' problem graph should be created 16 | /// 17 | /// # Returns 18 | /// 19 | /// Graph that solves Millionaires' problem 20 | pub fn create_millionaires_graph(context: Context) -> Result { 21 | // Create a graph in a given context that will be used for Millionaires' problem 22 | let g = context.create_graph()?; 23 | 24 | // For each millionaire, add input nodes to the empty graph g created above. 25 | // Input nodes are instantiated with binary arrays of 32 bits. 26 | // This should be enough to represent the wealth of each millionaire. 27 | let first_millionaire = g.input(scalar_type(UINT32))?; 28 | let second_millionaire = g.input(scalar_type(UINT32))?; 29 | 30 | // Millionaires' problem boils down to computing the greater-than (>) function. 31 | // In CipherCore, comparison functions are realized via custom operations, 32 | // which are a special kind of operations that accept varying number of inputs and input types. 33 | // To add a custom operation node to the graph, create it first. 34 | // Note that the GreaterThan custom operation has a Boolean parameter that indicates whether input binary arrays represent signed integers 35 | let op = CustomOperation::new(GreaterThan { 36 | signed_comparison: false, 37 | }); 38 | // For each millionaire, convert integer value to binary array in order to perform comparison. 39 | // Add custom operation to the graph specifying the custom operation and its arguments: `first_millionaire` and `second_millionaire`. 40 | // This operation will compute the bit `(first_millionaire > second_millionaire)`. 41 | let output = g.custom_op( 42 | op, 43 | vec![first_millionaire.a2b()?, second_millionaire.a2b()?], 44 | )?; 45 | 46 | // Before computation, every graph should be finalized, which means that it should have a designated output node. 47 | // This can be done by calling `g.set_output_node(output)?` or as below. 48 | output.set_as_output()?; 49 | // Finalization checks that the output node of the graph g is set. After finalization the graph can't be changed. 50 | g.finalize()?; 51 | 52 | Ok(g) 53 | } 54 | 55 | #[cfg(test)] 56 | mod tests { 57 | use crate::custom_ops::run_instantiation_pass; 58 | use crate::data_types::BIT; 59 | use crate::data_values::Value; 60 | use crate::evaluators::random_evaluate; 61 | use crate::graphs::create_context; 62 | use std::ops::Not; 63 | 64 | use super::*; 65 | 66 | fn test_millionaires_helper< 67 | T1: TryInto + Not + TryInto + Copy, 68 | T2: TryInto + Not + TryInto + Copy, 69 | >( 70 | input1_value: T1, 71 | input2_value: T2, 72 | ) -> Value { 73 | || -> Result { 74 | let c = create_context()?; 75 | let g = create_millionaires_graph(c.clone())?; 76 | g.set_as_main()?; 77 | c.finalize()?; 78 | let mapped_c = run_instantiation_pass(c)?.get_context(); 79 | let mapped_g = mapped_c.get_main_graph()?; 80 | 81 | let val1 = Value::from_scalar(input1_value, UINT32)?; 82 | let val2 = Value::from_scalar(input2_value, UINT32)?; 83 | random_evaluate(mapped_g, vec![val1, val2]) 84 | }() 85 | .unwrap() 86 | } 87 | 88 | #[test] 89 | fn test_matmul() { 90 | || -> Result<()> { 91 | assert!(test_millionaires_helper(2000, 1000) == Value::from_scalar(1, BIT)?); 92 | assert!(test_millionaires_helper(1000, 2000) == Value::from_scalar(0, BIT)?); 93 | assert!(test_millionaires_helper(1000, 1000) == Value::from_scalar(0, BIT)?); 94 | Ok(()) 95 | }() 96 | .unwrap(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /ciphercore-base/src/applications/minimum.py: -------------------------------------------------------------------------------- 1 | import ciphercore as cc 2 | 3 | # Number of elements of an array (i.e., 2^n) 4 | n = 4 5 | # Scalar type of array elements; it should be unsigned, i.e. BIT, UINT8, UINT16, UINT32 or UINT64 6 | st = cc.UINT32 7 | 8 | # Create a context 9 | c = cc.create_context() 10 | with c: 11 | # Create a graph in a given context that will be used for matrix multiplication 12 | g = c.create_graph() 13 | with g: 14 | # Create the type of the input array with `n` elements. 15 | # To find the minimum of an array, we resort to the custom operation Min (see ops.rs) that accepts only binary input. 16 | input_type = cc.array_type([2 ** n], st) 17 | 18 | # Add an input node to the empty graph g created above. 19 | # This input node requires the input array type generated previously. 20 | input_array = g.input(input_type) 21 | 22 | # We convert the input integer array into the binary representation to perform comparisons between its elements 23 | binary_array = input_array.a2b() 24 | 25 | # We find the minimum using the tournament method. This allows to reduce the graph size to O(n) from O(2^n) nodes. 26 | # Namely, we split the input array into pairs, find a minimum within each pair and create a new array from these minima. 27 | # Then, we repeat this procedure for the new array. 28 | # For example, let [2,7,0,3,11,5,0,4] be an input array. 29 | # The 1st iteration yields [min(2,11), min(7,5), min(0,0), min(3,4)] = [2,5,0,3] 30 | # The 2nd iteration results in [min(2,0), min(5,3)] = [0,3] 31 | # The 3rd iteration returns [min(0,3)] = [0] 32 | for level in reversed(range(n)): 33 | # Extract the first half of the array using the [Graph::get_slice] operation. 34 | # Our slicing conventions follow [the NumPy rules](https://numpy.org/doc/stable/user/basics.indexing.html). 35 | half1 = binary_array[:(2 ** level)] 36 | # Extract the first half of the array using the [Graph::get_slice] operation. 37 | half2 = binary_array[(2 ** level):] 38 | # Compare the first half with the second half elementwise to find minimums. 39 | # This is done via the custom operation Min (see ops.rs). 40 | binary_array = half1.min(half2) 41 | # Convert output from the binary form to the arithmetic form 42 | output = binary_array 43 | if st != cc.BIT: 44 | output = binary_array.b2a(st) 45 | output.set_as_output() 46 | # Set this graph as main to be able to finalize the context 47 | g.set_as_main() 48 | # Serialize the context and print it to stdout 49 | print(c) 50 | -------------------------------------------------------------------------------- /ciphercore-base/src/applications/minimum.rs: -------------------------------------------------------------------------------- 1 | //! Minimum of an integer array 2 | use crate::custom_ops::CustomOperation; 3 | use crate::data_types::{array_type, ScalarType, BIT}; 4 | use crate::errors::Result; 5 | use crate::graphs::{Context, Graph, SliceElement}; 6 | use crate::ops::min_max::Min; 7 | 8 | /// Creates a graph that finds the minimum of an array. 9 | /// 10 | /// # Arguments 11 | /// 12 | /// * `context` - context where a minimum graph should be created 13 | /// * `n` - number of elements of an array (i.e., 2n) 14 | /// * `st` - scalar type of array elements 15 | /// 16 | /// # Returns 17 | /// 18 | /// Graph that finds the minimum of an array 19 | pub fn create_minimum_graph(context: Context, n: u64, st: ScalarType) -> Result { 20 | // Get sign of the input scalar type that indicates whether signed comparisons should be computed 21 | let signed_comparison = st.is_signed(); 22 | 23 | // Create a graph in a given context that will be used for finding the minimum 24 | let g = context.create_graph()?; 25 | 26 | // Create the type of the input array with `2^n` elements. 27 | let input_type = array_type(vec![1 << n], st); 28 | 29 | // Add an input node to the empty graph g created above. 30 | // This input node requires the input array type generated previously. 31 | let input_array = g.input(input_type)?; 32 | 33 | // To find the minimum of an array, we resort to the custom operation Min (see ops.rs) that accepts only binary input. 34 | // Thus, we need to convert the input in the binary form (if necessary). 35 | let mut binary_array = if st != BIT { 36 | input_array.a2b()? 37 | } else { 38 | input_array 39 | }; 40 | 41 | // We find the minimum using the tournament method. This allows to reduce the graph size to O(n) from O(2n) nodes. 42 | // Namely, we split the input array into pairs, find a minimum within each pair and create a new array from these minima. 43 | // Then, we repeat this procedure for the new array. 44 | // For example, let [2,7,0,3,11,5,0,4] be an input array. 45 | // The 1st iteration yields [min(2,11), min(7,5), min(0,0), min(3,4)] = [2,5,0,3] 46 | // The 2nd iteration results in [min(2,0), min(5,3)] = [0,3] 47 | // The 3rd iteration returns [min(0,3)] = [0] 48 | for level in (0..n).rev() { 49 | // Extract the first half of the array using the [Graph::get_slice] operation. 50 | // Our slicing conventions follow [the NumPy rules](https://numpy.org/doc/stable/user/basics.indexing.html). 51 | let half1 = 52 | binary_array.get_slice(vec![SliceElement::SubArray(None, Some(1 << level), None)])?; 53 | // Extract the first half of the array using the [Graph::get_slice] operation. 54 | let half2 = 55 | binary_array.get_slice(vec![SliceElement::SubArray(Some(1 << level), None, None)])?; 56 | // Compare the first half with the second half elementwise to find minimums. 57 | // This is done via the custom operation Min (see ops.rs). 58 | binary_array = g.custom_op( 59 | CustomOperation::new(Min { signed_comparison }), 60 | vec![half1, half2], 61 | )?; 62 | } 63 | // Convert output from the binary form to the arithmetic form 64 | let output = if st != BIT { 65 | binary_array.b2a(st)? 66 | } else { 67 | binary_array 68 | }; 69 | // Before computation every graph should be finalized, which means that it should have a designated output node. 70 | // This can be done by calling `g.set_output_node(output)?` or as below. 71 | output.set_as_output()?; 72 | // Finalization checks that the output node of the graph g is set. After finalization the graph can't be changed. 73 | g.finalize()?; 74 | 75 | Ok(g) 76 | } 77 | 78 | #[cfg(test)] 79 | mod tests { 80 | use crate::custom_ops::run_instantiation_pass; 81 | use crate::data_types::{INT32, UINT32}; 82 | use crate::data_values::Value; 83 | use crate::evaluators::random_evaluate; 84 | use crate::graphs::create_context; 85 | use std::ops::Not; 86 | 87 | use super::*; 88 | 89 | fn test_minimum_helper + Not + TryInto + Copy>( 90 | input_value: &[T], 91 | n: u64, 92 | st: ScalarType, 93 | ) -> Value { 94 | || -> Result { 95 | let c = create_context()?; 96 | let g = create_minimum_graph(c.clone(), n, st)?; 97 | g.set_as_main()?; 98 | c.finalize()?; 99 | let mapped_c = run_instantiation_pass(c)?.get_context(); 100 | let mapped_g = mapped_c.get_main_graph()?; 101 | 102 | let input_type = array_type(vec![n], st); 103 | let val = Value::from_flattened_array(input_value, input_type.get_scalar_type())?; 104 | random_evaluate(mapped_g, vec![val]) 105 | }() 106 | .unwrap() 107 | } 108 | 109 | #[test] 110 | fn test_minimum() { 111 | || -> Result<()> { 112 | assert!( 113 | test_minimum_helper( 114 | &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], 115 | 4, 116 | UINT32 117 | ) == Value::from_flattened_array(&[1], UINT32)? 118 | ); 119 | Ok(()) 120 | }() 121 | .unwrap(); 122 | || -> Result<()> { 123 | assert!( 124 | test_minimum_helper( 125 | &[-1, 2, -3, 4, -5, 6, -7, 8, -9, 10, -11, 12, -13, 14, -15, 16], 126 | 4, 127 | INT32 128 | ) == Value::from_flattened_array(&[-15], INT32)? 129 | ); 130 | Ok(()) 131 | }() 132 | .unwrap(); 133 | || -> Result<()> { 134 | assert!( 135 | test_minimum_helper(&[0, 1, 1, 0, 1, 1, 0, 0], 3, BIT) 136 | == Value::from_flattened_array(&[0], BIT)? 137 | ); 138 | Ok(()) 139 | }() 140 | .unwrap(); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /ciphercore-base/src/bin/ciphercore_a2b.rs: -------------------------------------------------------------------------------- 1 | //! Code example of a binary creating the serialization of a a2b context. 2 | extern crate ciphercore_base; 3 | 4 | use std::str::FromStr; 5 | 6 | use ciphercore_base::data_types::{array_type, ScalarType, BIT}; 7 | use ciphercore_base::errors::Result; 8 | use ciphercore_base::graphs::{create_context, Context, Graph}; 9 | 10 | use ciphercore_utils::execute_main::execute_main; 11 | 12 | use clap::Parser; 13 | 14 | #[derive(Parser, Debug)] 15 | #[clap(author, version, about, long_about=None)] 16 | struct Args { 17 | #[clap(value_parser)] 18 | /// number of elements of an array (i.e., 2k) 19 | k: u32, 20 | /// scalar type of array elements 21 | #[clap(short, long, value_parser)] 22 | scalar_type: String, 23 | /// generate b2a graph instead of a2b 24 | #[clap(long, value_parser)] 25 | inverse: bool, 26 | } 27 | 28 | fn gen_a2b_graph(context: Context, k: u32, st: ScalarType) -> Result { 29 | let graph = context.create_graph()?; 30 | let n = 2u64.pow(k); 31 | let i = graph.input(array_type(vec![n], st))?; 32 | graph.set_output_node(i.a2b()?)?; 33 | graph.finalize()?; 34 | Ok(graph) 35 | } 36 | 37 | fn gen_b2a_graph(context: Context, k: u32, st: ScalarType) -> Result { 38 | let graph = context.create_graph()?; 39 | let n = 2u64.pow(k); 40 | let i = graph.input(array_type(vec![n, st.size_in_bits()], BIT))?; 41 | graph.set_output_node(i.b2a(st)?)?; 42 | graph.finalize()?; 43 | Ok(graph) 44 | } 45 | 46 | /// This binary prints the serialized a2b/b2a context on the non-encrypted input in (serde) JSON format. 47 | /// 48 | /// It is needed for measuring performance of a2b/b2a conversions. 49 | /// 50 | /// # Arguments 51 | /// 52 | /// * `k` - number of elements of an array (i.e. 2n) 53 | /// * `st` - scalar type of array elements 54 | /// * `inverse` - generate b2a graph instead of a2b 55 | /// 56 | /// # Usage 57 | /// 58 | /// `< this_binary > -s ` 59 | fn main() { 60 | // Initialize a logger that collects information about errors and panics within CipherCore. 61 | // This information can be accessed via RUST_LOG. 62 | env_logger::init(); 63 | // Execute CipherCore code such that all the internal errors are properly formatted and logged. 64 | execute_main(|| -> Result<()> { 65 | let args = Args::parse(); 66 | let st = ScalarType::from_str(&args.scalar_type)?; 67 | // Create a context 68 | let context = create_context()?; 69 | 70 | let graph = if args.inverse { 71 | gen_a2b_graph(context.clone(), args.k, st) 72 | } else { 73 | gen_b2a_graph(context.clone(), args.k, st) 74 | }?; 75 | 76 | // Set this graph as main to be able to finalize the context 77 | context.set_main_graph(graph)?; 78 | // Finalize the context. This makes sure that all the graphs of the contexts are ready for computation. 79 | // After this action the context can't be changed. 80 | context.finalize()?; 81 | // Serialize the context and print it to stdout 82 | println!("{}", serde_json::to_string(&context)?); 83 | Ok(()) 84 | }); 85 | } 86 | -------------------------------------------------------------------------------- /ciphercore-base/src/bin/ciphercore_evaluate.rs: -------------------------------------------------------------------------------- 1 | //! Code of a binary printing evaluated result of a given serialized context on given context inputs 2 | 3 | use ciphercore_base::errors::Result; 4 | use ciphercore_base::evaluators::get_result_util::get_evaluator_result; 5 | use ciphercore_base::evaluators::simple_evaluator::SimpleEvaluator; 6 | use ciphercore_base::graphs::Context; 7 | use ciphercore_base::typed_value::TypedValue; 8 | use ciphercore_utils::eprintln_or_log; 9 | use ciphercore_utils::execute_main::execute_main; 10 | use std::fs; 11 | 12 | use clap::Parser; 13 | 14 | #[derive(Parser, Debug)] 15 | #[clap(author, version, about, long_about=None)] 16 | struct Args { 17 | #[clap(value_parser)] 18 | /// Path to a file containing serialized context 19 | context_path: String, 20 | #[clap(value_parser)] 21 | /// Path to a file containing serialized inputs 22 | inputs_path: String, 23 | #[clap(long, value_parser)] 24 | /// (optional) Boolean to indicate if the output is to be revealed for secret-shared outputs 25 | reveal_output: bool, 26 | } 27 | 28 | /// This binary evaluates a given context over the provided inputs using a simple evaluator. 29 | /// 30 | /// For a secret-shared output, output is revealed if the `reveal_output` boolean binary argument is set to `true`. 31 | /// 32 | /// # Arguments 33 | /// 34 | /// * `context_path` - path to a serialized context 35 | /// * `inputs_path` - path to serialized input(s) 36 | /// * `reveal_output` - boolean to indicate if output is to be revealed 37 | /// 38 | /// # Usage 39 | /// 40 | /// < this_binary > [--reveal-output] 41 | fn main() { 42 | // Initialize a logger that collects information about errors and panics within CipherCore. 43 | // This information can be accessed via RUST_LOG. 44 | env_logger::init(); 45 | // Execute CipherCore code such that all the internal errors are properly formatted and logged. 46 | execute_main(|| -> Result<()> { 47 | // Parse the input arguments 48 | let args = Args::parse(); 49 | // Read the entire file containing a serialized context as a string 50 | let serialized_context = fs::read_to_string(&args.context_path)?; 51 | // Deserialize into a context object from the serialized context string 52 | let raw_context = serde_json::from_str::(&serialized_context)?; 53 | // Read the entire file containing serialized inputs as a string 54 | let json_inputs = fs::read_to_string(&args.inputs_path)?; 55 | // Parse inputs to obtain a vector of typed values, i.e., pair of type, and its value 56 | let inputs = serde_json::from_str::>(&json_inputs)?; 57 | // Use the simple evaluator to obtain the typed result value 58 | let result = get_evaluator_result( 59 | raw_context, 60 | inputs, 61 | args.reveal_output, 62 | SimpleEvaluator::new(None)?, 63 | )?; 64 | // Depending on the input argument, print whether the output is revealed 65 | if args.reveal_output { 66 | eprintln_or_log!("Revealing the output"); 67 | } 68 | // Print the serialized result to stdout 69 | println!("{}", serde_json::to_string(&result)?); 70 | Ok(()) 71 | }); 72 | } 73 | -------------------------------------------------------------------------------- /ciphercore-base/src/bin/ciphercore_gen_zero_input.rs: -------------------------------------------------------------------------------- 1 | //! Code of a binary that generates JSON of zero inputs for a given context 2 | 3 | use ciphercore_utils::execute_main::execute_main; 4 | 5 | use ciphercore_base::data_values::Value; 6 | use ciphercore_base::errors::Result; 7 | use ciphercore_base::graphs::{Context, Operation}; 8 | use ciphercore_base::typed_value::TypedValue; 9 | 10 | use clap::Parser; 11 | use std::fs; 12 | 13 | #[derive(Parser, Debug)] 14 | #[clap(author, version, about, long_about=None)] 15 | struct Args { 16 | #[clap(value_parser)] 17 | /// Path to file that contains the input context 18 | input_context: String, 19 | } 20 | 21 | /// This binary reads the context from a given file and prints JSON for zero inputs of appropriate types. 22 | /// 23 | /// # Arguments 24 | /// 25 | /// * `inputs_path` - path to file that contains the context 26 | /// 27 | /// # Usage 28 | /// 29 | /// < this_binary > 30 | fn main() { 31 | env_logger::init(); 32 | execute_main(|| -> Result<()> { 33 | let args = Args::parse(); 34 | let context = serde_json::from_str::(&fs::read_to_string(args.input_context)?)?; 35 | let mut inputs = vec![]; 36 | for node in context.get_main_graph()?.get_nodes() { 37 | if let Operation::Input(ref input_type) = node.get_operation() { 38 | inputs.push(TypedValue::new( 39 | input_type.clone(), 40 | Value::zero_of_type(input_type.clone()), 41 | )?); 42 | } 43 | } 44 | println!("{}", serde_json::to_string(&inputs)?); 45 | Ok(()) 46 | }); 47 | } 48 | -------------------------------------------------------------------------------- /ciphercore-base/src/bin/ciphercore_matrix_multiplication.rs: -------------------------------------------------------------------------------- 1 | //! Code example of a binary creating the serialization of a matrix multiplication context. 2 | extern crate ciphercore_base; 3 | 4 | use std::str::FromStr; 5 | 6 | use ciphercore_base::applications::matrix_multiplication::create_matrix_multiplication_graph; 7 | use ciphercore_base::data_types::ScalarType; 8 | use ciphercore_base::errors::Result; 9 | use ciphercore_base::graphs::{create_context, Graph}; 10 | 11 | use ciphercore_utils::execute_main::execute_main; 12 | 13 | use clap::Parser; 14 | 15 | #[derive(Parser, Debug)] 16 | #[clap(author, version, about, long_about=None)] 17 | struct Args { 18 | #[clap(value_parser)] 19 | /// number of rows of the left matrix 20 | n: u64, 21 | #[clap(value_parser)] 22 | /// number of columns of the left matrix (or number of rows of the right matrix) 23 | m: u64, 24 | #[clap(value_parser)] 25 | /// number of columns of the right matrix 26 | k: u64, 27 | /// scalar type of matrix elements 28 | #[clap(short, long, value_parser)] 29 | scalar_type: String, 30 | } 31 | 32 | /// This binary prints the serialized matrix-multiplication-graph context on the non-encrypted input in (serde) JSON format. 33 | /// 34 | /// # Arguments 35 | /// 36 | /// * `n` - number of rows of the first matrix, 37 | /// * `m` - number of columns of the first matrix (and number of rows of the second matrix) 38 | /// * `k` - number of columns of the second matrix 39 | /// * `st` - scalar type of matrix elements (choose `b` for binary entries or one of the Rust built-in integer types: `i8`, `u8`, ..., `i64`, `u64`) 40 | /// 41 | /// # Usage 42 | /// 43 | /// `< this_binary > -s ` 44 | fn main() { 45 | // Initialize a logger that collects information about errors and panics within CipherCore. 46 | // This information can be accessed via RUST_LOG. 47 | env_logger::init(); 48 | // Execute CipherCore code such that all the internal errors are properly formatted and logged. 49 | execute_main(|| -> Result<()> { 50 | let args = Args::parse(); 51 | let st = ScalarType::from_str(&args.scalar_type)?; 52 | // Create a context 53 | let context = create_context()?; 54 | // Create a matrix multiplication graph in the context 55 | let graph: Graph = 56 | create_matrix_multiplication_graph(context.clone(), args.n, args.m, args.k, st)?; 57 | // Set this graph as main to be able to finalize the context 58 | context.set_main_graph(graph)?; 59 | // Finalize the context. This makes sure that all the graphs of the contexts are ready for computation. 60 | // After this action the context can't be changed. 61 | context.finalize()?; 62 | // Serialize the context and print it to stdout 63 | println!("{}", serde_json::to_string(&context)?); 64 | Ok(()) 65 | }); 66 | } 67 | -------------------------------------------------------------------------------- /ciphercore-base/src/bin/ciphercore_millionaires.rs: -------------------------------------------------------------------------------- 1 | //! Code example of a binary creating the serialization of a minimum context. 2 | extern crate ciphercore_base; 3 | 4 | use ciphercore_base::applications::millionaires::create_millionaires_graph; 5 | use ciphercore_base::errors::Result; 6 | use ciphercore_base::graphs::{create_context, Graph}; 7 | 8 | use ciphercore_utils::execute_main::execute_main; 9 | 10 | /// This binary prints the serialized millionaires-problem-graph context on the non-encrypted input in (serde) JSON format. 11 | /// 12 | /// # Usage 13 | /// 14 | /// < this_binary > 15 | fn main() { 16 | // Initialize a logger that collects information about errors and panics within CipherCore. 17 | // This information can be accessed via RUST_LOG. 18 | env_logger::init(); 19 | // Execute CipherCore code such that all the internal errors are properly formatted and logged. 20 | execute_main(|| -> Result<()> { 21 | // Create a context 22 | let context = create_context()?; 23 | // Create a Millionaires' problem graph in the context 24 | let graph: Graph = create_millionaires_graph(context.clone())?; 25 | // Set this graph as main to be able to finalize the context 26 | context.set_main_graph(graph)?; 27 | // Finalize the context. This makes sure that all the graphs of the contexts are ready for computation. 28 | // After this action the context can't be changed. 29 | context.finalize()?; 30 | // Serialize the context and print it to stdout 31 | println!("{}", serde_json::to_string(&context)?); 32 | Ok(()) 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /ciphercore-base/src/bin/ciphercore_minimum.rs: -------------------------------------------------------------------------------- 1 | //! Code example of a binary creating the serialization of a minimum context. 2 | extern crate ciphercore_base; 3 | 4 | use std::str::FromStr; 5 | 6 | use ciphercore_base::applications::minimum::create_minimum_graph; 7 | use ciphercore_base::data_types::ScalarType; 8 | use ciphercore_base::errors::Result; 9 | use ciphercore_base::graphs::{create_context, Graph}; 10 | 11 | use ciphercore_utils::execute_main::execute_main; 12 | 13 | use clap::Parser; 14 | 15 | #[derive(Parser, Debug)] 16 | #[clap(author, version, about, long_about=None)] 17 | struct Args { 18 | #[clap(value_parser)] 19 | /// parameter defining the number of elements of an array (i.e. 2n) 20 | n: u64, 21 | /// scalar type of array elements 22 | #[clap(short, long, value_parser)] 23 | scalar_type: String, 24 | } 25 | 26 | /// This binary prints the serialized minimum-graph context on the non-encrypted input in (serde) JSON format. 27 | /// 28 | /// # Arguments 29 | /// 30 | /// * `n` - number of elements of an array (i.e. 2n) 31 | /// * `st` - scalar type of matrix elements (choose `b` for binary entries or one of Rust integer types: `i8`, `u8`, ..., `i64`, `u64`) 32 | /// 33 | /// # Usage 34 | /// 35 | /// `< this_binary > -s ` 36 | fn main() { 37 | // Initialize a logger that collects information about errors and panics within CipherCore. 38 | // This information can be accessed via RUST_LOG. 39 | env_logger::init(); 40 | // Execute CipherCore code such that all the internal errors are properly formatted and logged. 41 | execute_main(|| -> Result<()> { 42 | let args = Args::parse(); 43 | let st = ScalarType::from_str(&args.scalar_type)?; 44 | // Create a context 45 | let context = create_context()?; 46 | // Create a minimum graph in the context 47 | let graph: Graph = create_minimum_graph(context.clone(), args.n, st)?; 48 | // Set this graph as main to be able to finalize the context 49 | context.set_main_graph(graph)?; 50 | // Finalize the context. This makes sure that all the graphs of the contexts are ready for computation. 51 | // After this action the context can't be changed. 52 | context.finalize()?; 53 | // Serialize the context and print it to stdout 54 | println!("{}", serde_json::to_string(&context)?); 55 | Ok(()) 56 | }); 57 | } 58 | -------------------------------------------------------------------------------- /ciphercore-base/src/bin/ciphercore_sort.rs: -------------------------------------------------------------------------------- 1 | //! Code example of a binary generates the following graph corresponding to [the Radix Sort MPC protocol](https://eprint.iacr.org/2019/695.pdf). 2 | extern crate ciphercore_base; 3 | 4 | use std::str::FromStr; 5 | 6 | use ciphercore_base::applications::sort::create_sort_graph; 7 | use ciphercore_base::data_types::ScalarType; 8 | use ciphercore_base::errors::Result; 9 | use ciphercore_base::graphs::{create_context, Graph}; 10 | 11 | use ciphercore_utils::execute_main::execute_main; 12 | 13 | use clap::Parser; 14 | 15 | #[derive(Parser, Debug)] 16 | #[clap(author, version, about, long_about=None)] 17 | struct Args { 18 | #[clap(value_parser)] 19 | /// number of elements of an array 20 | n: u64, 21 | /// scalar type of array elements 22 | #[clap(short, long, value_parser)] 23 | scalar_type: String, 24 | } 25 | 26 | /// This binary prints the serialized sorting context on the non-encrypted input in (serde) JSON format. 27 | /// Main context graph is based on [the Radix Sort MPC protocol](https://eprint.iacr.org/2019/695.pdf) implemented [here](../ciphercore_base/applications/sort/fn.create_sort_graph.html). 28 | /// 29 | /// # Arguments 30 | /// 31 | /// * `n` - number of elements of an array 32 | /// * `st` - scalar type of array elements 33 | /// 34 | /// # Usage 35 | /// 36 | /// `< this_binary > -s ` 37 | fn main() { 38 | // Initialize a logger that collects information about errors and panics within CipherCore. 39 | // This information can be accessed via RUST_LOG. 40 | env_logger::init(); 41 | // Execute CipherCore code such that all the internal errors are properly formatted and logged. 42 | execute_main(|| -> Result<()> { 43 | let args = Args::parse(); 44 | let st = ScalarType::from_str(&args.scalar_type)?; 45 | // Create a context 46 | let context = create_context()?; 47 | // Create a sort graph in the context 48 | let graph: Graph = create_sort_graph(context.clone(), args.n, st)?; 49 | // Set this graph as main to be able to finalize the context 50 | context.set_main_graph(graph)?; 51 | // Finalize the context. This makes sure that all the graphs of the contexts are ready for computation. 52 | // After this action the context can't be changed. 53 | context.finalize()?; 54 | // Serialize the context and print it to stdout 55 | println!("{}", serde_json::to_string(&context)?); 56 | Ok(()) 57 | }); 58 | } 59 | -------------------------------------------------------------------------------- /ciphercore-base/src/bin/ciphercore_split_parties.rs: -------------------------------------------------------------------------------- 1 | //! Code of a binary that takes typed inputs and splits them between the parties 2 | #[macro_use] 3 | extern crate ciphercore_base; 4 | 5 | use ciphercore_base::data_values::Value; 6 | use ciphercore_base::errors::Result; 7 | use ciphercore_base::mpc::mpc_compiler::IOStatus; 8 | use ciphercore_base::random::PRNG; 9 | use ciphercore_base::typed_value::TypedValue; 10 | use ciphercore_utils::execute_main::execute_main; 11 | 12 | use clap::Parser; 13 | use std::fs; 14 | 15 | /// Returns tokens from given stringed input consisting of (1) comma separated party IDs, OR (2) "public", OR (3) "secret-shared". 16 | /// 17 | /// # Arguments 18 | /// 19 | /// `s` - string that encodes the information concerning participating parties 20 | /// 21 | /// # Returns 22 | /// 23 | /// Vector of party identifiers 24 | fn get_tokens(s: String) -> Result> { 25 | let tokens: Vec = s.split(',').map(|x| x.to_owned()).collect(); 26 | if tokens.is_empty() { 27 | return Err(runtime_error!("Empty tokens")); 28 | } 29 | let mut result = Vec::new(); 30 | for token in tokens { 31 | let tmp: &str = &token; 32 | match tmp { 33 | "0" => { 34 | result.push(IOStatus::Party(0)); 35 | } 36 | "1" => { 37 | result.push(IOStatus::Party(1)); 38 | } 39 | "2" => { 40 | result.push(IOStatus::Party(2)); 41 | } 42 | "public" => { 43 | result.push(IOStatus::Public); 44 | } 45 | "secret-shared" => { 46 | result.push(IOStatus::Shared); 47 | } 48 | _ => { 49 | return Err(runtime_error!("Invalid token: {}", token)); 50 | } 51 | } 52 | } 53 | Ok(result) 54 | } 55 | 56 | #[derive(Parser, Debug)] 57 | #[clap(author, version, about, long_about=None)] 58 | struct Args { 59 | #[clap(value_parser)] 60 | /// Path to file that contains the inputs 61 | inputs_path: String, 62 | #[clap(value_parser)] 63 | /// String comprising comma separated list of input parties' IDs, valid ID values include `0`, `1`, `2` OR `public` OR `secret-shared`. 64 | input_parties: String, 65 | #[clap(value_parser)] 66 | /// Path to the output file that contains the inputs prepared for party `0` 67 | parties_inputs_0: String, 68 | #[clap(value_parser)] 69 | /// Path to the output file that contains the inputs prepared for party `1` 70 | parties_inputs_1: String, 71 | #[clap(value_parser)] 72 | /// Path to the output file that contains the inputs prepared for party `2` 73 | parties_inputs_2: String, 74 | } 75 | 76 | /// This binary reads the inputs from the file and splits them (+ possibly secret-shares) between the parties according to a command line parameter. 77 | /// 78 | /// # Arguments 79 | /// 80 | /// * `inputs_path` - path to file that contains the inputs 81 | /// * `input_parties` - string comprising of either a comma separated list of input parties' IDs, valid ID values include `0`, `1`, `2` OR `public` OR `secret-shared` 82 | /// * `inputs_path_0` - path to the output file that contains the inputs prepared for party `0` 83 | /// * `inputs_path_1` - path to the output file that contains the inputs prepared for party `1` 84 | /// * `inputs_path_2` - path to the output file that contains the inputs prepared for party `2` 85 | /// 86 | /// # Usage 87 | /// 88 | /// < this_binary > 89 | fn main() { 90 | env_logger::init(); 91 | execute_main(|| -> Result<()> { 92 | let args = Args::parse(); 93 | let json_inputs = fs::read_to_string(&args.inputs_path)?; 94 | let inputs = serde_json::from_str::>(&json_inputs)?; 95 | let input_parties = get_tokens(args.input_parties)?; 96 | let mut split_inputs = vec![vec![], vec![], vec![]]; 97 | if inputs.len() != input_parties.len() { 98 | return Err(runtime_error!( 99 | "Invalid number of inputs parties: {} expected, but {} found", 100 | inputs.len(), 101 | input_parties.len() 102 | )); 103 | } 104 | let mut prng = PRNG::new(None)?; 105 | for i in 0..inputs.len() { 106 | match input_parties[i] { 107 | IOStatus::Party(p) => { 108 | for (j, result_item) in split_inputs.iter_mut().enumerate() { 109 | if j as u64 == p { 110 | result_item.push(inputs[i].clone()); 111 | } else { 112 | result_item.push(TypedValue::new( 113 | inputs[i].t.clone(), 114 | Value::zero_of_type(inputs[i].t.clone()), 115 | )?); 116 | } 117 | } 118 | } 119 | IOStatus::Public => { 120 | for result_item in split_inputs.iter_mut() { 121 | result_item.push(inputs[i].clone()); 122 | } 123 | } 124 | IOStatus::Shared => { 125 | let parties_shares = inputs[i].get_local_shares_for_each_party(&mut prng)?; 126 | for j in 0..3 { 127 | split_inputs[j].push(parties_shares[j].clone()); 128 | } 129 | } 130 | } 131 | } 132 | fs::write( 133 | args.parties_inputs_0, 134 | serde_json::to_string(&split_inputs[0])?, 135 | )?; 136 | fs::write( 137 | args.parties_inputs_1, 138 | serde_json::to_string(&split_inputs[1])?, 139 | )?; 140 | fs::write( 141 | args.parties_inputs_2, 142 | serde_json::to_string(&split_inputs[2])?, 143 | )?; 144 | Ok(()) 145 | }); 146 | } 147 | -------------------------------------------------------------------------------- /ciphercore-base/src/broadcast.rs: -------------------------------------------------------------------------------- 1 | use crate::data_types::{array_type, is_valid_shape, ArrayShape, Type}; 2 | use crate::errors::Result; 3 | use std::cmp::max; 4 | 5 | pub(super) fn broadcast_shapes(s1: ArrayShape, s2: ArrayShape) -> Result { 6 | let result_length = max(s1.len(), s2.len()); 7 | let offset1 = result_length - s1.len(); 8 | let offset2 = result_length - s2.len(); 9 | let mut result = vec![]; 10 | for i in 0..result_length { 11 | let mut value1 = 1; 12 | if i >= offset1 { 13 | value1 = s1[i - offset1]; 14 | } 15 | let mut value2 = 1; 16 | if i >= offset2 { 17 | value2 = s2[i - offset2]; 18 | } 19 | if value1 > 1 && value2 > 1 && value1 != value2 { 20 | return Err(runtime_error!( 21 | "Invalid broadcast: shapes {s1:?} and {s2:?}" 22 | )); 23 | } 24 | result.push(max(value1, value2)); 25 | } 26 | Ok(result) 27 | } 28 | 29 | fn broadcast_pair(t1: Type, t2: Type) -> Result { 30 | if t1.get_scalar_type() != t2.get_scalar_type() { 31 | return Err(runtime_error!("Scalar types mismatch: {t1:?} and {t2:?}")); 32 | } 33 | if t1.is_scalar() { 34 | return Ok(t2); 35 | } 36 | if t2.is_scalar() { 37 | return Ok(t1); 38 | } 39 | let scalar_type = t1.get_scalar_type(); 40 | Ok(array_type( 41 | broadcast_shapes(t1.get_shape(), t2.get_shape())?, 42 | scalar_type, 43 | )) 44 | } 45 | 46 | pub(super) fn broadcast_arrays(element_types: Vec) -> Result { 47 | if element_types.is_empty() { 48 | return Err(runtime_error!("Can't broadcast the empty sequence")); 49 | } 50 | for x in &element_types { 51 | if !x.is_scalar() && !x.is_array() { 52 | return Err(runtime_error!( 53 | "Can broadcast only scalars and arrays, got {x:?}" 54 | )); 55 | } 56 | if x.is_array() && !is_valid_shape(x.get_shape()) { 57 | return Err(runtime_error!("Invalid shape: {:?}", x.get_shape())); 58 | } 59 | } 60 | let mut result = element_types[0].clone(); 61 | for item in element_types.iter().skip(1) { 62 | result = broadcast_pair(result, item.clone())?; 63 | } 64 | Ok(result) 65 | } 66 | 67 | pub fn index_to_number(index: &[u64], shape: &[u64]) -> u64 { 68 | let mut num = 0; 69 | for (i, d) in shape.iter().enumerate() { 70 | // mod d makes sure that input indices do not exceed dimensions given by the shape 71 | num = num * d + (index[i] % d); 72 | } 73 | num 74 | } 75 | 76 | pub fn number_to_index(num: u64, shape: &[u64]) -> Vec { 77 | let mut num_left = num; 78 | let mut index = vec![]; 79 | let mut radix: u64 = shape.iter().product(); 80 | for d in shape { 81 | radix /= d; 82 | let digit = num_left / radix; 83 | index.push(digit); 84 | num_left %= radix; 85 | } 86 | index 87 | } 88 | 89 | #[cfg(tests)] 90 | mod tests { 91 | use super::*; 92 | use crate::data_types::{scalar_type, BIT, UINT8}; 93 | 94 | #[test] 95 | fn test_malformed() { 96 | let e1 = broadcast_arrays(vec![]); 97 | assert!(e1.is_err()); 98 | let e2 = broadcast_arrays(vec![tuple_type(vec![])]); 99 | assert!(e2.is_err()); 100 | let e3 = broadcast_arrays(vec![scalar_type(BIT), tuple_type(vec![])]); 101 | assert!(e3.is_err()); 102 | let e4 = broadcast_arrays(vec![scalar_type(BIT), array_type(vec![10, 10], UINT8)]); 103 | assert!(e4.is_err()); 104 | let e5 = broadcast_arrays(vec![ 105 | array_type(vec![10, 10], BIT), 106 | array_type(vec![10, 10], UINT8), 107 | ]); 108 | assert!(e5.is_err()); 109 | let e6 = broadcast_arrays(vec![array_type(vec![], BIT)]); 110 | assert!(e6.is_err()); 111 | let e7 = broadcast_arrays(vec![array_type(vec![7, 0, 3], BIT)]); 112 | assert!(e7.is_err()); 113 | let e8 = broadcast_arrays(vec![scalar_type(BIT), array_type(vec![], BIT)]); 114 | assert!(e8.is_err()); 115 | let e9 = broadcast_arrays(vec![scalar_type(BIT), array_type(vec![7, 0, 3], BIT)]); 116 | assert!(e9.is_err()); 117 | let e10 = broadcast_arrays(vec![array_type(vec![3], BIT), array_type(vec![7], BIT)]); 118 | assert!(e10.is_err()); 119 | let e11 = broadcast_arrays(vec![array_type(vec![7, 3], BIT), array_type(vec![7], BIT)]); 120 | assert!(e11.is_err()); 121 | } 122 | 123 | #[test] 124 | fn test_valid() { 125 | assert_eq!(broadcast_arrays(vec![scalar_type(BIT)]), scalar_type(BIT)); 126 | assert_eq!( 127 | broadcast_arrays(vec![scalar_type(UINT8)]), 128 | scalar_type(UINT8) 129 | ); 130 | assert_eq!( 131 | broadcast_arrays(vec![scalar_type(BIT), scalar_type(BIT)]), 132 | scalar_type(BIT) 133 | ); 134 | assert_eq!( 135 | broadcast_arrays(vec![ 136 | scalar_type(UINT8), 137 | scalar_type(UINT8), 138 | scalar_type(UINT8) 139 | ]), 140 | scalar_type(UINT8) 141 | ); 142 | assert_eq!( 143 | broadcast_arrays(vec![array_type(vec![10, 10], BIT)]), 144 | array_type(vec![10, 10], BIT) 145 | ); 146 | assert_eq!( 147 | broadcast_arrays(vec![array_type(vec![10, 10], BIT), scalar_type(BIT)]), 148 | array_type(vec![10, 10], BIT) 149 | ); 150 | assert_eq!( 151 | broadcast_arrays(vec![scalar_type(BIT), array_type(vec![10, 10], BIT)]), 152 | array_type(vec![10, 10], BIT) 153 | ); 154 | assert_eq!( 155 | broadcast_arrays(vec![ 156 | array_type(vec![10, 10], BIT), 157 | array_type(vec![10, 10], BIT) 158 | ]), 159 | array_type(vec![10, 10], BIT) 160 | ); 161 | assert_eq!( 162 | broadcast_arrays(vec![ 163 | array_type(vec![10, 10], BIT), 164 | array_type(vec![10, 1], BIT) 165 | ]), 166 | array_type(vec![10, 10], BIT) 167 | ); 168 | assert_eq!( 169 | broadcast_arrays(vec![ 170 | array_type(vec![1, 10], BIT), 171 | array_type(vec![10, 1], BIT) 172 | ]), 173 | array_type(vec![10, 10], BIT) 174 | ); 175 | assert_eq!( 176 | broadcast_arrays(vec![ 177 | array_type(vec![10], BIT), 178 | array_type(vec![10, 1], BIT) 179 | ]), 180 | array_type(vec![10, 10], BIT) 181 | ); 182 | assert_eq!( 183 | broadcast_arrays(vec![array_type(vec![9], BIT), array_type(vec![3, 1], BIT)]), 184 | array_type(vec![3, 9], BIT) 185 | ); 186 | assert_eq!( 187 | broadcast_arrays(vec![array_type(vec![9], BIT), array_type(vec![3, 9], BIT)]), 188 | array_type(vec![3, 9], BIT) 189 | ); 190 | assert_eq!( 191 | broadcast_arrays(vec![array_type(vec![3, 9], BIT), array_type(vec![9], BIT)]), 192 | array_type(vec![3, 9], BIT) 193 | ); 194 | assert_eq!( 195 | broadcast_arrays(vec![array_type(vec![3, 1], BIT), array_type(vec![9], BIT)]), 196 | array_type(vec![3, 9], BIT) 197 | ); 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /ciphercore-base/src/constants.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "fuzzing")] 2 | pub mod type_size_limit_constants { 3 | pub const MAX_TOTAL_SIZE_NODES: u64 = 10000; 4 | pub const MAX_INDIVIDUAL_NODE_SIZE: u64 = 1000; 5 | pub const TYPES_VECTOR_LENGTH_LIMIT: usize = 1000; 6 | pub const TYPE_MEMORY_OVERHEAD: u64 = 1; 7 | } 8 | #[cfg(not(feature = "fuzzing"))] 9 | pub mod type_size_limit_constants { 10 | pub const MAX_TOTAL_SIZE_NODES: u64 = u64::MAX - 1; 11 | pub const MAX_INDIVIDUAL_NODE_SIZE: u64 = u64::MAX - 1; 12 | pub const TYPES_VECTOR_LENGTH_LIMIT: usize = usize::MAX - 1; 13 | pub const TYPE_MEMORY_OVERHEAD: u64 = 1; 14 | } 15 | -------------------------------------------------------------------------------- /ciphercore-base/src/csv/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod test_utils; 2 | pub mod utils; 3 | -------------------------------------------------------------------------------- /ciphercore-base/src/csv/test_utils.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::Result; 2 | 3 | fn assert_rows_eq(rows: Vec, expected_rows: Vec>) { 4 | assert_eq!(rows.len(), expected_rows.len()); 5 | for (i, (csv_row, expected_row)) in rows.into_iter().zip(expected_rows.into_iter()).enumerate() 6 | { 7 | assert_eq!(csv_row, expected_row, "row {i}"); 8 | } 9 | } 10 | 11 | pub fn assert_table_eq( 12 | csv_bytes: Vec, 13 | expected_headers: Vec<&str>, 14 | expected_records: Vec>, 15 | ) -> Result<()> { 16 | let mut csv = csv::Reader::from_reader(csv_bytes.as_slice()); 17 | assert_eq!(csv.headers()?, expected_headers); 18 | let csv_rows = csv 19 | .records() 20 | .collect::, csv::Error>>()?; 21 | assert_rows_eq(csv_rows, expected_records); 22 | Ok(()) 23 | } 24 | 25 | pub fn assert_sorted_table_eq( 26 | csv_bytes: Vec, 27 | expected_headers: Vec<&str>, 28 | expected_records: Vec>, 29 | ) -> Result<()> { 30 | let mut csv = csv::Reader::from_reader(csv_bytes.as_slice()); 31 | assert_eq!(csv.headers()?, expected_headers); 32 | let mut csv_rows = csv 33 | .records() 34 | .collect::, csv::Error>>()?; 35 | csv_rows.sort_by(|r1, r2: &csv::StringRecord| r1[0].cmp(&r2[0])); 36 | assert_rows_eq(csv_rows, expected_records); 37 | Ok(()) 38 | } 39 | 40 | pub fn assert_table_unordered_eq( 41 | csv_bytes: Vec, 42 | expected_headers: Vec<&str>, 43 | expected_records: Vec>, 44 | ) -> Result<()> { 45 | let mut csv = csv::Reader::from_reader(csv_bytes.as_slice()); 46 | assert_eq!(csv.headers()?, expected_headers); 47 | let mut csv_rows = csv 48 | .records() 49 | .collect::, csv::Error>>()?; 50 | csv_rows.sort_by_key(|row| { 51 | row.clone() 52 | .into_iter() 53 | .map(|s| s.to_string()) 54 | .collect::>() 55 | }); 56 | let mut expected_records = expected_records; 57 | expected_records.sort(); 58 | assert_rows_eq(csv_rows, expected_records); 59 | Ok(()) 60 | } 61 | -------------------------------------------------------------------------------- /ciphercore-base/src/errors.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | #[doc(hidden)] 4 | #[macro_export] 5 | macro_rules! runtime_error { 6 | ($($x:tt)*) => { 7 | $crate::errors::Error::new(anyhow::anyhow!($($x)*), true) 8 | }; 9 | } 10 | 11 | #[derive(Clone)] 12 | pub struct Error { 13 | // Note: we use Arc to make it clonable 14 | inner: Arc, 15 | pub can_retry: bool, 16 | } 17 | 18 | impl Error { 19 | pub fn new(err: anyhow::Error, can_retry: bool) -> Self { 20 | Self { 21 | inner: Arc::new(err), 22 | can_retry, 23 | } 24 | } 25 | } 26 | 27 | pub type Result = std::result::Result; 28 | 29 | impl std::fmt::Debug for Error { 30 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 31 | std::fmt::Debug::fmt(&self.inner, f) 32 | } 33 | } 34 | 35 | impl std::fmt::Display for Error { 36 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 37 | std::fmt::Display::fmt(&self.inner, f) 38 | } 39 | } 40 | 41 | #[cfg(feature = "tonic-errors")] 42 | impl From for tonic::Status { 43 | fn from(err: Error) -> Self { 44 | // TODO: add stacktrace to details? 45 | tonic::Status::new(tonic::Code::Internal, err.inner.to_string()) 46 | } 47 | } 48 | 49 | #[cfg(feature = "py-binding")] 50 | impl std::convert::From for pyo3::PyErr { 51 | fn from(err: Error) -> pyo3::PyErr { 52 | pyo3::exceptions::PyRuntimeError::new_err(err.to_string()) 53 | } 54 | } 55 | 56 | impl From for Error 57 | where 58 | E: Into, 59 | { 60 | fn from(error: E) -> Self { 61 | // TODO: downcast and check some error types to set can_retry to false 62 | // (e.g. for authorization errors) 63 | Self::new(error.into(), true) 64 | } 65 | } 66 | 67 | // You can't convert Lock errors with '?' because they are not 'static, so you can `.map_err(poisoned_mutex)` instead 68 | pub fn poisoned_mutex(_: E) -> Error { 69 | runtime_error!("Poisoned mutex") 70 | } 71 | -------------------------------------------------------------------------------- /ciphercore-base/src/evaluators.rs: -------------------------------------------------------------------------------- 1 | pub mod get_result_util; 2 | pub(crate) mod join; 3 | #[cfg(test)] 4 | pub(crate) mod join_test_utils; 5 | pub mod simple_evaluator; 6 | 7 | use crate::data_values::Value; 8 | use crate::errors::Result; 9 | use crate::graphs::{Context, Operation}; 10 | use crate::graphs::{Graph, Node}; 11 | use crate::random::SEED_SIZE; 12 | 13 | pub trait Evaluator { 14 | fn preprocess(&mut self, context: &Context) -> Result<()> { 15 | context.check_finalized()?; 16 | for graph in context.get_graphs() { 17 | for node in graph.get_nodes() { 18 | node.get_type()?; 19 | } 20 | } 21 | Ok(()) 22 | } 23 | 24 | fn evaluate_node(&mut self, node: Node, dependencies_values: Vec) -> Result; 25 | 26 | fn evaluate_call_iterate( 27 | &mut self, 28 | node: Node, 29 | dependencies_values: Vec, 30 | ) -> Result { 31 | match node.get_operation() { 32 | Operation::Call => { 33 | let graphs = node.get_graph_dependencies(); 34 | self.evaluate_graph(graphs[0].clone(), dependencies_values) 35 | } 36 | Operation::Iterate => { 37 | let graphs = node.get_graph_dependencies(); 38 | let initial_state_value = dependencies_values[0].clone(); 39 | let inputs_value = dependencies_values[1].clone(); 40 | let mut current_state_value = initial_state_value; 41 | let mut output_values = vec![]; 42 | for input_value in inputs_value.to_vector()? { 43 | let result = self.evaluate_graph( 44 | graphs[0].clone(), 45 | vec![current_state_value.clone(), input_value], 46 | )?; 47 | let result = result.to_vector()?; 48 | current_state_value = result[0].clone(); 49 | output_values.push(result[1].clone()); 50 | } 51 | Ok(Value::from_vector(vec![ 52 | current_state_value, 53 | Value::from_vector(output_values), 54 | ])) 55 | } 56 | _ => { 57 | panic!("Should not be here!"); 58 | } 59 | } 60 | } 61 | 62 | fn evaluate_graph(&mut self, graph: Graph, inputs_values: Vec) -> Result { 63 | graph.get_context().check_finalized()?; 64 | let mut num_input_nodes = 0; 65 | let nodes = graph.get_nodes(); 66 | 67 | for node in nodes.iter() { 68 | if node.get_operation().is_input() { 69 | num_input_nodes += 1; 70 | } 71 | } 72 | if num_input_nodes != inputs_values.len() { 73 | return Err(runtime_error!( 74 | "Incorrect number of inputs for evaluation: {} expected, but {} provided", 75 | num_input_nodes, 76 | inputs_values.len() 77 | )); 78 | } 79 | 80 | let mut node_option_values: Vec> = vec![]; 81 | 82 | let num_nodes = nodes.len(); 83 | let mut to_consume_option = vec![0; num_nodes]; 84 | for node in nodes.iter() { 85 | for dep in node.get_node_dependencies() { 86 | to_consume_option[dep.get_id() as usize] += 1; 87 | } 88 | } 89 | 90 | let output_node = graph.get_output_node()?; 91 | let output_id = output_node.get_id() as usize; 92 | let mut update_consumed_option_nodes = |node: Node, values: &mut [Option]| { 93 | for dep in node.get_node_dependencies() { 94 | let dep_id = dep.get_id() as usize; 95 | to_consume_option[dep_id] -= 1; 96 | if to_consume_option[dep_id] == 0 && dep_id != output_id { 97 | values[dep_id] = None; 98 | } 99 | } 100 | }; 101 | let mut input_id: u64 = 0; 102 | for node in nodes.iter() { 103 | let mut dependencies_values = vec![]; 104 | for dependency in node.get_node_dependencies() { 105 | let node_value = node_option_values[dependency.get_id() as usize].clone(); 106 | match node_value { 107 | Some(value) => dependencies_values.push(value.clone()), 108 | None => { 109 | panic!("Dependency is already removed. Shouldn't be here."); 110 | } 111 | } 112 | } 113 | match node.get_operation() { 114 | Operation::Input(t) => { 115 | if !inputs_values[input_id as usize].check_type(t.clone())? { 116 | return Err(runtime_error!( 117 | "Invalid input type. Type is {:?}, value is {:?}.", 118 | t, 119 | inputs_values[input_id as usize] 120 | )); 121 | } 122 | node_option_values.push(Some(inputs_values[input_id as usize].clone())); 123 | input_id += 1; 124 | } 125 | Operation::Call | Operation::Iterate => { 126 | let res = self.evaluate_call_iterate(node.clone(), dependencies_values)?; 127 | node_option_values.push(Some(res)); 128 | update_consumed_option_nodes((*node).clone(), &mut node_option_values); 129 | } 130 | _ => { 131 | let res = self.evaluate_node(node.clone(), dependencies_values)?; 132 | node_option_values.push(Some(res.clone())); 133 | update_consumed_option_nodes((*node).clone(), &mut node_option_values); 134 | } 135 | } 136 | } 137 | Ok(node_option_values[output_node.get_id() as usize] 138 | .clone() 139 | .unwrap()) 140 | } 141 | 142 | fn evaluate_context(&mut self, context: Context, inputs_values: Vec) -> Result { 143 | context.check_finalized()?; 144 | self.evaluate_graph(context.get_main_graph()?, inputs_values) 145 | } 146 | } 147 | 148 | pub fn evaluate_simple_evaluator( 149 | graph: Graph, 150 | inputs: Vec, 151 | prng_seed: Option<[u8; SEED_SIZE]>, 152 | ) -> Result { 153 | let mut evaluator = simple_evaluator::SimpleEvaluator::new(prng_seed)?; 154 | evaluator.preprocess(&graph.get_context())?; 155 | evaluator.evaluate_graph(graph, inputs) 156 | } 157 | 158 | /// Evaluate a given graph on a given set of inputs with a random PRNG seed. 159 | pub fn random_evaluate(graph: Graph, inputs: Vec) -> Result { 160 | evaluate_simple_evaluator(graph, inputs, None) 161 | } 162 | -------------------------------------------------------------------------------- /ciphercore-base/src/evaluators/get_result_util.rs: -------------------------------------------------------------------------------- 1 | use ciphercore_utils::{eprint_or_log, eprintln_or_log}; 2 | 3 | use crate::custom_ops::run_instantiation_pass; 4 | use crate::data_types::Type; 5 | use crate::evaluators::Evaluator; 6 | use crate::graphs::{Context, Operation}; 7 | use crate::random::PRNG; 8 | 9 | use crate::errors::Result; 10 | use crate::typed_value::TypedValue; 11 | 12 | #[doc(hidden)] 13 | /// This function evaluates a given context on given inputs using a given evaluator. 14 | /// 15 | /// A flag may be passed if the output has to be revealed for secret-shared outputs. 16 | /// 17 | /// Returns a runtime error if the number and type of inputs do not match with 18 | /// those of the inputs of the context. 19 | /// 20 | /// This function is for internal use. 21 | /// 22 | /// # Arguments 23 | /// 24 | /// * `context` - context to compute the result for 25 | /// 26 | /// * `inputs` - vector containing type-value data 27 | /// 28 | /// * `reveal_output` - boolean to indicate if output is to be revealed 29 | /// 30 | /// * `evaluator` - evaluator to be used for processing the context 31 | /// 32 | /// # Returns 33 | /// 34 | /// Result corresponding to the inputs for the given context 35 | /// 36 | /// ## Example 37 | /// 38 | /// ``` 39 | /// # use ciphercore_base::graphs::{Context, create_context}; 40 | /// # use ciphercore_base::typed_value::TypedValue; 41 | /// # use ciphercore_base::data_types::{UINT64, scalar_type}; 42 | /// # use ciphercore_base::data_values::Value; 43 | /// # use ciphercore_base::evaluators::get_result_util::get_evaluator_result; 44 | /// # use ciphercore_base::evaluators::simple_evaluator::SimpleEvaluator; 45 | /// # use ciphercore_base::errors::Result; 46 | /// let c = || -> Result { 47 | /// let c = create_context()?; 48 | /// let g = c.create_graph()?; 49 | /// let t = scalar_type(UINT64); 50 | /// let ip0 = g.input(t.clone())?; 51 | /// let ip1 = g.input(t)?; 52 | /// let r = g.add(ip0, ip1)?; 53 | /// g.set_output_node(r)?; 54 | /// g.finalize()?; 55 | /// c.set_main_graph(g)?; 56 | /// c.finalize(); 57 | /// Ok(c) 58 | /// }().unwrap(); 59 | /// let inputs = || -> Result> { 60 | /// Ok(vec![ 61 | /// TypedValue::new(scalar_type(UINT64), Value::from_scalar(2, UINT64)?)?, 62 | /// TypedValue::new(scalar_type(UINT64), Value::from_scalar(2, UINT64)?)? 63 | /// ]) 64 | /// }().unwrap(); 65 | /// let result = get_evaluator_result( 66 | /// c, 67 | /// inputs, 68 | /// false, 69 | /// SimpleEvaluator::new(None).unwrap() 70 | /// ).unwrap(); 71 | /// let tv_sc_u64 = TypedValue::new( 72 | /// scalar_type(UINT64), 73 | /// Value::from_scalar(4, UINT64).unwrap()).unwrap(); 74 | /// assert_eq!(result, tv_sc_u64); 75 | /// ``` 76 | pub fn get_evaluator_result( 77 | context: Context, 78 | inputs: Vec, 79 | reveal_output: bool, 80 | mut evaluator: T, 81 | ) -> Result { 82 | let context = run_instantiation_pass(context)?.get_context(); 83 | let mut input_types = vec![]; 84 | for node in context.get_main_graph()?.get_nodes() { 85 | if let Operation::Input(t) = node.get_operation() { 86 | input_types.push(t); 87 | } 88 | } 89 | if inputs.len() != input_types.len() { 90 | return Err(runtime_error!( 91 | "Invalid number of inputs: {} expected, {} received", 92 | input_types.len(), 93 | inputs.len() 94 | )); 95 | } 96 | let mut input_values = vec![]; 97 | let mut prng = PRNG::new(None)?; 98 | for i in 0..inputs.len() { 99 | eprint_or_log!("Input {i}: "); 100 | if inputs[i].value.check_type(input_types[i].clone())? { 101 | eprintln_or_log!("Using as is"); 102 | input_values.push(inputs[i].value.clone()); 103 | } else { 104 | let e = Err(runtime_error!("Invalid input value")); 105 | let v = if let Type::Tuple(v) = input_types[i].clone() { 106 | v 107 | } else { 108 | return e; 109 | }; 110 | if v.len() == 3 111 | && v[0] == v[1] 112 | && v[0] == v[2] 113 | && inputs[i].value.check_type((*v[0]).clone())? 114 | { 115 | eprintln_or_log!("Secret-sharing"); 116 | // This is a nasty hack. 117 | // It allows to pass an array of i32's to a graph 118 | // that accepts bit arrays etc. 119 | input_values.push( 120 | TypedValue::new((*v[0]).clone(), inputs[i].value.clone())? 121 | .secret_share(&mut prng)? 122 | .value, 123 | ); 124 | } else { 125 | return e; 126 | } 127 | } 128 | } 129 | 130 | evaluator.preprocess(&context)?; 131 | let mut result = TypedValue::new( 132 | context.get_main_graph()?.get_output_node()?.get_type()?, 133 | evaluator.evaluate_graph(context.get_main_graph()?, input_values)?, 134 | )?; 135 | if reveal_output { 136 | result = result.secret_share_reveal()?; 137 | } 138 | Ok(result) 139 | } 140 | -------------------------------------------------------------------------------- /ciphercore-base/src/evaluators/join_test_utils.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::{ 4 | data_types::{array_type, named_tuple_type, tuple_type, ScalarType, Type, BIT}, 5 | data_values::Value, 6 | errors::Result, 7 | graphs::JoinType, 8 | join_utils::ColumnType, 9 | type_inference::NULL_HEADER, 10 | }; 11 | 12 | // Helper structures and functions for testing 13 | #[derive(Clone)] 14 | pub(crate) struct ColumnInfo { 15 | pub header: String, 16 | t: ColumnType, 17 | mask: Option>, 18 | data: Vec, 19 | } 20 | impl ColumnInfo { 21 | pub(crate) fn get_value(&self) -> Result { 22 | let data_value = Value::from_flattened_array(&self.data, self.t.get_scalar_type())?; 23 | if let Some(mask_arr) = self.mask.clone() { 24 | let mask_value = Value::from_flattened_array(&mask_arr, BIT)?; 25 | return Ok(Value::from_vector(vec![mask_value, data_value])); 26 | } 27 | Ok(data_value) 28 | } 29 | 30 | fn has_mask(&self) -> bool { 31 | self.mask.is_some() 32 | } 33 | } 34 | 35 | pub(crate) fn column_info( 36 | header: &str, 37 | shape: &[u64], 38 | st: ScalarType, 39 | data: &[u64], 40 | ) -> Result { 41 | let t = ColumnType::new(array_type(shape.to_vec(), st), false, header)?; 42 | Ok(ColumnInfo { 43 | header: header.to_owned(), 44 | t, 45 | mask: None, 46 | data: data.to_vec(), 47 | }) 48 | } 49 | 50 | pub(crate) fn column_info_with_mask( 51 | header: &str, 52 | shape: &[u64], 53 | st: ScalarType, 54 | mask: Option<&[u64]>, 55 | data: &[u64], 56 | ) -> Result { 57 | if header == NULL_HEADER && mask.is_some() { 58 | panic!("Null column shouldn't have a mask"); 59 | } 60 | if header != NULL_HEADER && mask.is_none() { 61 | panic!("Column should have a mask"); 62 | } 63 | let resolved_mask = mask.map(|mask_arr| mask_arr.to_vec()); 64 | let binary_t = array_type(vec![shape[0]], BIT); 65 | let column_t = if header == NULL_HEADER { 66 | binary_t 67 | } else { 68 | tuple_type(vec![binary_t, array_type(shape.to_vec(), st)]) 69 | }; 70 | let t = ColumnType::new(column_t, true, header)?; 71 | Ok(ColumnInfo { 72 | header: header.to_owned(), 73 | t, 74 | mask: resolved_mask, 75 | data: data.to_vec(), 76 | }) 77 | } 78 | 79 | pub(crate) type SetInfo = Vec; 80 | 81 | pub(crate) trait SetHelpers { 82 | fn get_type(&self) -> Type; 83 | fn get_value(&self) -> Result; 84 | } 85 | 86 | impl SetHelpers for SetInfo { 87 | fn get_type(&self) -> Type { 88 | let mut v = vec![]; 89 | for col in self.iter() { 90 | v.push((col.header.clone(), col.t.clone().into())); 91 | } 92 | named_tuple_type(v) 93 | } 94 | fn get_value(&self) -> Result { 95 | let mut v = vec![]; 96 | for col in self.iter() { 97 | v.push(col.get_value()?); 98 | } 99 | Ok(Value::from_vector(v)) 100 | } 101 | } 102 | 103 | pub(crate) type ExpectedSetInfo = Vec<(String, Option>, Vec)>; 104 | 105 | pub(crate) fn expected_set_info(expected_columns: Vec<(&str, &[u64])>) -> ExpectedSetInfo { 106 | let mut v = vec![]; 107 | for (header, data) in expected_columns { 108 | v.push((header.to_owned(), None, data.to_vec())); 109 | } 110 | v 111 | } 112 | 113 | type ExpectedColumn<'a> = (&'a str, Option<&'a [u64]>, &'a [u64]); 114 | 115 | pub(crate) fn expected_set_info_with_mask( 116 | expected_columns: Vec, 117 | ) -> ExpectedSetInfo { 118 | let mut v = vec![]; 119 | for (header, mask, data) in expected_columns { 120 | if header == NULL_HEADER && mask.is_some() { 121 | panic!("Null column shouldn't have a column mask"); 122 | } 123 | let resolved_mask = mask.map(|mask_arr| mask_arr.to_vec()); 124 | v.push((header.to_owned(), resolved_mask, data.to_vec())); 125 | } 126 | v 127 | } 128 | 129 | #[derive(Clone)] 130 | pub(crate) struct JoinTestInfo { 131 | pub set0: SetInfo, 132 | pub set1: SetInfo, 133 | pub headers: HashMap, 134 | pub expected: HashMap, 135 | pub has_column_masks: bool, 136 | } 137 | 138 | pub(crate) fn join_info( 139 | set0: SetInfo, 140 | set1: SetInfo, 141 | headers: Vec<(&str, &str)>, 142 | expected: HashMap, 143 | ) -> JoinTestInfo { 144 | let mut hmap = HashMap::new(); 145 | for (h0, h1) in headers { 146 | hmap.insert(h0.to_owned(), h1.to_owned()); 147 | } 148 | let mut has_column_masks = false; 149 | for col in &set0 { 150 | if col.has_mask() { 151 | has_column_masks = true; 152 | break; 153 | } 154 | } 155 | JoinTestInfo { 156 | set0, 157 | set1, 158 | headers: hmap, 159 | expected, 160 | has_column_masks, 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /ciphercore-base/src/inline.rs: -------------------------------------------------------------------------------- 1 | mod associative_iterate_inliner; 2 | mod data_structures; 3 | mod empty_state_iterate_inliner; 4 | mod exponential_inliner; 5 | pub mod inline_common; 6 | pub mod inline_ops; 7 | #[cfg(test)] 8 | mod inline_test_utils; 9 | mod simple_iterate_inliner; 10 | -------------------------------------------------------------------------------- /ciphercore-base/src/inline/empty_state_iterate_inliner.rs: -------------------------------------------------------------------------------- 1 | use crate::data_types::{Type, UINT64}; 2 | use crate::errors::Result; 3 | use crate::graphs::{Graph, Node}; 4 | use crate::inline::inline_common::InlineState; 5 | use crate::ops::utils::constant_scalar; 6 | 7 | pub(super) fn inline_iterate_empty_state( 8 | graph: Graph, 9 | initial_state: Node, 10 | inputs_node: Node, 11 | inliner: &mut dyn InlineState, 12 | ) -> Result<(Node, Vec)> { 13 | let inputs_len = match inputs_node.get_type()? { 14 | Type::Vector(len, _) => len, 15 | _ => { 16 | panic!("Inconsistency with type checker"); 17 | } 18 | }; 19 | let mut outputs = vec![]; 20 | for i in 0..inputs_len { 21 | let current_input = 22 | inputs_node.vector_get(constant_scalar(&inliner.output_graph(), i, UINT64)?)?; 23 | inliner.assign_input_nodes( 24 | graph.clone(), 25 | vec![initial_state.clone(), current_input.clone()], 26 | )?; 27 | let result = inliner.recursively_inline_graph(graph.clone())?; 28 | inliner.unassign_nodes(graph.clone())?; 29 | outputs.push(result.tuple_get(1)?.clone()); 30 | } 31 | Ok((initial_state, outputs)) 32 | } 33 | 34 | #[cfg(test)] 35 | mod tests { 36 | use super::*; 37 | use crate::graphs::create_context; 38 | use crate::inline::inline_test_utils::{build_test_data, resolve_tuple_get, MockInlineState}; 39 | 40 | #[test] 41 | fn test_empty_state_iterate() { 42 | || -> Result<()> { 43 | let c = create_context()?; 44 | let (g, initial_state, inputs_node, input_vals) = build_test_data(&c, UINT64)?; 45 | let mut inliner = MockInlineState { 46 | fake_graph: g.clone(), 47 | inputs: vec![], 48 | inline_graph_calls: vec![], 49 | returned_nodes: vec![], 50 | }; 51 | let res = 52 | inline_iterate_empty_state(g, initial_state.clone(), inputs_node, &mut inliner)?; 53 | assert_eq!(inliner.inputs.len(), 5); 54 | assert_eq!(inliner.inline_graph_calls.len(), 5); 55 | assert_eq!(inliner.returned_nodes.len(), 5); 56 | assert!(initial_state == inliner.inputs[0][0]); 57 | for i in 0..input_vals.len() { 58 | assert!(resolve_tuple_get(res.1[i].clone()) == inliner.returned_nodes[i][1]); 59 | } 60 | for i in 1..input_vals.len() { 61 | // Inlinings should be independent. 62 | assert!( 63 | inliner.returned_nodes[i - 1][0] 64 | != resolve_tuple_get(inliner.inputs[i][0].clone()) 65 | ); 66 | assert!( 67 | resolve_tuple_get(inliner.inputs[0][0].clone()) 68 | == resolve_tuple_get(inliner.inputs[i][0].clone()) 69 | ); 70 | } 71 | Ok(()) 72 | }() 73 | .unwrap(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /ciphercore-base/src/inline/inline_common.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::Result; 2 | use crate::graphs::{Graph, Node}; 3 | use crate::inline::data_structures::{ 4 | prefix_sums_binary_ascent, prefix_sums_segment_tree, prefix_sums_sqrt_trick, CombineOp, 5 | }; 6 | use serde::{Deserialize, Serialize}; 7 | 8 | // This trait is needed only for calling back to the inlining processor from the 9 | // individual inliners, and mocking it out in the tests/ 10 | pub(super) trait InlineState { 11 | fn assign_input_nodes(&mut self, graph: Graph, nodes: Vec) -> Result<()>; 12 | fn unassign_nodes(&mut self, graph: Graph) -> Result<()>; 13 | fn recursively_inline_graph(&mut self, graph: Graph) -> Result; 14 | fn output_graph(&self) -> Graph; 15 | } 16 | 17 | #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] 18 | pub enum DepthOptimizationLevel { 19 | Default, 20 | // The "Extreme" level will aggressively trade performance for lower depth. 21 | Extreme, 22 | } 23 | 24 | pub(super) type PrefixSumAlgorithm = fn(&[T], &mut dyn CombineOp) -> Result>; 25 | 26 | pub(super) fn pick_prefix_sum_algorithm( 27 | inputs_len: u64, 28 | optimization_level: DepthOptimizationLevel, 29 | ) -> PrefixSumAlgorithm { 30 | if matches!(optimization_level, DepthOptimizationLevel::Extreme) { 31 | // Get best depth possible. 32 | prefix_sums_binary_ascent 33 | } else { 34 | // Performance matters, use O(n)-complexity algorithms. 35 | // Why 16? This is the point where the segment tree inlining (2 * log(n) depth) 36 | // becomes better than sqrt-inlining (2 * sqrt(n) depth). 37 | if inputs_len < 16 { 38 | prefix_sums_sqrt_trick 39 | } else { 40 | prefix_sums_segment_tree 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ciphercore-base/src/inline/inline_test_utils.rs: -------------------------------------------------------------------------------- 1 | use crate::data_types::{scalar_type, ScalarType, BIT}; 2 | use crate::errors::Result; 3 | use crate::graphs::{Context, Graph, Node, Operation}; 4 | use crate::inline::inline_common::InlineState; 5 | use crate::ops::utils::constant_scalar; 6 | 7 | pub(super) struct MockInlineState { 8 | pub fake_graph: Graph, 9 | pub inputs: Vec>, 10 | pub inline_graph_calls: Vec, 11 | pub returned_nodes: Vec>, 12 | } 13 | 14 | impl InlineState for MockInlineState { 15 | fn assign_input_nodes(&mut self, _graph: Graph, nodes: Vec) -> Result<()> { 16 | self.inputs.push(nodes); 17 | Ok(()) 18 | } 19 | 20 | fn unassign_nodes(&mut self, _graph: Graph) -> Result<()> { 21 | Ok(()) 22 | } 23 | 24 | fn recursively_inline_graph(&mut self, graph: Graph) -> Result { 25 | self.inline_graph_calls.push(graph); 26 | let nodes = vec![ 27 | constant_scalar(&self.fake_graph, 0, BIT)?, 28 | self.fake_graph.create_tuple(vec![])?, 29 | ]; 30 | self.returned_nodes.push(nodes.clone()); 31 | self.fake_graph.create_tuple(nodes) 32 | } 33 | 34 | fn output_graph(&self) -> Graph { 35 | self.fake_graph.clone() 36 | } 37 | } 38 | 39 | pub(super) fn build_test_data( 40 | c: &Context, 41 | st: ScalarType, 42 | ) -> Result<(Graph, Node, Node, Vec)> { 43 | let g = c.create_graph()?; 44 | let initial_state = constant_scalar(&g, if st == BIT { 1 } else { 42 }, st)?; 45 | let input_vals = if st == BIT { 46 | vec![1, 0, 0, 1, 1] 47 | } else { 48 | vec![1, 2, 3, 42, 57] 49 | }; 50 | let mut inputs = vec![]; 51 | for i in input_vals { 52 | let val = constant_scalar(&g, i, st)?; 53 | inputs.push(val.clone()); 54 | } 55 | let inputs_node = g.create_vector(scalar_type(st), inputs.clone())?; 56 | Ok((g, initial_state, inputs_node, inputs)) 57 | } 58 | 59 | pub(super) fn resolve_tuple_get(node: Node) -> Node { 60 | if let Operation::TupleGet(index) = node.get_operation() { 61 | let tuple = node.get_node_dependencies()[0].clone(); 62 | // We assume that this is an output of CreateTuple, which can be wrong in general. 63 | let elements = tuple.get_node_dependencies(); 64 | return elements[index as usize].clone(); 65 | } 66 | node 67 | } 68 | -------------------------------------------------------------------------------- /ciphercore-base/src/inline/simple_iterate_inliner.rs: -------------------------------------------------------------------------------- 1 | use crate::data_types::{Type, UINT64}; 2 | use crate::errors::Result; 3 | use crate::graphs::{Graph, Node}; 4 | use crate::inline::inline_common::InlineState; 5 | use crate::ops::utils::constant_scalar; 6 | 7 | pub(super) fn inline_iterate_simple( 8 | graph: Graph, 9 | initial_state: Node, 10 | inputs_node: Node, 11 | inliner: &mut dyn InlineState, 12 | ) -> Result<(Node, Vec)> { 13 | let mut state = initial_state; 14 | let mut outputs = vec![]; 15 | let inputs_len = match inputs_node.get_type()? { 16 | Type::Vector(len, _) => len, 17 | _ => { 18 | panic!("Inconsistency with type checker"); 19 | } 20 | }; 21 | for i in 0..inputs_len { 22 | let current_input = 23 | inputs_node.vector_get(constant_scalar(&inliner.output_graph(), i, UINT64)?)?; 24 | inliner.assign_input_nodes(graph.clone(), vec![state.clone(), current_input.clone()])?; 25 | let result = inliner.recursively_inline_graph(graph.clone())?; 26 | inliner.unassign_nodes(graph.clone())?; 27 | state = result.tuple_get(0)?.clone(); 28 | outputs.push(result.tuple_get(1)?.clone()); 29 | } 30 | Ok((state, outputs)) 31 | } 32 | 33 | #[cfg(test)] 34 | mod tests { 35 | use super::*; 36 | use crate::graphs::create_context; 37 | use crate::inline::inline_test_utils::{build_test_data, resolve_tuple_get, MockInlineState}; 38 | 39 | #[test] 40 | fn test_simple_iterate() { 41 | || -> Result<()> { 42 | let c = create_context()?; 43 | let (g, initial_state, inputs_node, input_vals) = build_test_data(&c, UINT64)?; 44 | let mut inliner = MockInlineState { 45 | fake_graph: g.clone(), 46 | inputs: vec![], 47 | inline_graph_calls: vec![], 48 | returned_nodes: vec![], 49 | }; 50 | let res = inline_iterate_simple(g, initial_state.clone(), inputs_node, &mut inliner)?; 51 | assert_eq!(inliner.inputs.len(), 5); 52 | assert_eq!(inliner.inline_graph_calls.len(), 5); 53 | assert_eq!(inliner.returned_nodes.len(), 5); 54 | assert!(initial_state == inliner.inputs[0][0]); 55 | assert!(resolve_tuple_get(res.0) == inliner.returned_nodes[4][0]); 56 | for i in 0..input_vals.len() { 57 | assert!(resolve_tuple_get(res.1[i].clone()) == inliner.returned_nodes[i][1]); 58 | } 59 | for i in 1..input_vals.len() { 60 | assert!( 61 | inliner.returned_nodes[i - 1][0] 62 | == resolve_tuple_get(inliner.inputs[i][0].clone()) 63 | ); 64 | } 65 | Ok(()) 66 | }() 67 | .unwrap(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /ciphercore-base/src/join_utils.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | data_types::{ 3 | array_type, get_named_types, get_types_vector, tuple_type, ArrayShape, ScalarType, Type, 4 | BIT, 5 | }, 6 | errors::Result, 7 | type_inference::NULL_HEADER, 8 | }; 9 | use std::collections::HashMap; 10 | 11 | #[derive(Clone)] 12 | pub(crate) struct ColumnType { 13 | mask_t: Option, 14 | data_t: Type, 15 | } 16 | 17 | impl ColumnType { 18 | pub(crate) fn new(column_t: Type, has_column_mask: bool, header: &str) -> Result { 19 | let (mask_t, data_t) = if has_column_mask && header != NULL_HEADER { 20 | if !column_t.is_tuple() { 21 | return Err(runtime_error!( 22 | "Column should contain a tuple, got: {column_t:?}" 23 | )); 24 | } 25 | match get_types_vector(column_t.clone())?.as_slice() { 26 | [mask_t, data_t] => (Some((**mask_t).clone()), (**data_t).clone()), 27 | _ => { 28 | return Err(runtime_error!( 29 | "Column should contain a tuple with two arrays, given {}", 30 | column_t 31 | )); 32 | } 33 | } 34 | } else { 35 | (None, column_t.clone()) 36 | }; 37 | if !data_t.is_array() { 38 | if header == NULL_HEADER { 39 | return Err(runtime_error!( 40 | "Null column should be a binary array, got {data_t}" 41 | )); 42 | } 43 | return Err(runtime_error!( 44 | "Column should have an array with data, got: {column_t:?}" 45 | )); 46 | } 47 | if let Some(mask_t_resolved) = mask_t.clone() { 48 | let num_col_entries = data_t.get_shape()[0]; 49 | ColumnType::check_column_mask_type(mask_t_resolved, num_col_entries, header)?; 50 | } 51 | if header == NULL_HEADER && data_t.get_scalar_type() != BIT { 52 | return Err(runtime_error!( 53 | "Null column should be binary, got {data_t:?}" 54 | )); 55 | } 56 | Ok(ColumnType { mask_t, data_t }) 57 | } 58 | 59 | fn check_column_mask_type( 60 | binary_mask_t: Type, 61 | expected_num_entries: u64, 62 | header: &str, 63 | ) -> Result<()> { 64 | if binary_mask_t.get_scalar_type() != BIT { 65 | return Err(runtime_error!( 66 | "{header} column mask should be binary, got {binary_mask_t:?}" 67 | )); 68 | } 69 | if binary_mask_t.get_shape() != vec![expected_num_entries] { 70 | return Err(runtime_error!( 71 | "{header} column mask should have shape {:?}", 72 | vec![expected_num_entries] 73 | )); 74 | } 75 | Ok(()) 76 | } 77 | 78 | pub(crate) fn get_num_entries(&self) -> u64 { 79 | self.get_data_shape()[0] 80 | } 81 | 82 | pub(crate) fn clone_with_number_of_entries(&self, new_num_entries: u64) -> ColumnType { 83 | let mut shape = self.get_data_shape(); 84 | shape[0] = new_num_entries; 85 | let st = self.get_scalar_type(); 86 | let data_t = array_type(shape, st); 87 | let mut mask_t = None; 88 | if self.mask_t.is_some() { 89 | mask_t = Some(array_type(vec![new_num_entries], BIT)); 90 | } 91 | ColumnType { mask_t, data_t } 92 | } 93 | 94 | pub(crate) fn get_data_shape(&self) -> ArrayShape { 95 | self.data_t.get_shape() 96 | } 97 | 98 | pub(crate) fn get_scalar_type(&self) -> ScalarType { 99 | self.data_t.get_scalar_type() 100 | } 101 | 102 | pub(crate) fn get_data_type(&self) -> Type { 103 | self.data_t.clone() 104 | } 105 | 106 | pub(crate) fn get_mask_type(&self) -> Result { 107 | if let Some(mask_t) = self.mask_t.clone() { 108 | return Ok(mask_t); 109 | } 110 | Err(runtime_error!("Column has no mask")) 111 | } 112 | 113 | pub(crate) fn get_row_size_in_elements(&self) -> usize { 114 | self.get_data_shape().iter().skip(1).product::() as usize 115 | } 116 | 117 | pub(crate) fn get_row_size_in_bits(&self) -> u64 { 118 | self.get_row_size_in_elements() as u64 * self.get_scalar_type().size_in_bits() 119 | } 120 | 121 | #[cfg(test)] 122 | pub(crate) fn clone_with_mask(&self) -> ColumnType { 123 | let mask_t = Some(array_type(vec![self.get_num_entries()], BIT)); 124 | ColumnType { 125 | mask_t, 126 | data_t: self.data_t.clone(), 127 | } 128 | } 129 | 130 | pub(crate) fn has_mask(&self) -> bool { 131 | self.mask_t.is_some() 132 | } 133 | } 134 | 135 | impl From for Type { 136 | fn from(column_t: ColumnType) -> Self { 137 | if let Some(mask_t) = column_t.mask_t { 138 | return tuple_type(vec![mask_t, column_t.data_t]); 139 | } 140 | column_t.data_t 141 | } 142 | } 143 | 144 | pub(crate) fn check_table_and_extract_column_types( 145 | t: Type, 146 | has_null_column: bool, 147 | has_column_masks: bool, 148 | ) -> Result<(HashMap, u64)> { 149 | let v = get_named_types(&t)?; 150 | 151 | if has_null_column && v.len() < 2 { 152 | return Err(runtime_error!("Named tuple should contain at least two columns, one of which must be the null column. Got: {v:?}")); 153 | } 154 | if !has_null_column && v.is_empty() { 155 | return Err(runtime_error!( 156 | "Named tuple should contain at least one column." 157 | )); 158 | } 159 | let mut num_rows = 0; 160 | let mut contains_null = false; 161 | let mut all_headers: HashMap = HashMap::new(); 162 | for (h, sub_t) in v { 163 | let column_type = ColumnType::new((**sub_t).clone(), has_column_masks, h)?; 164 | let num_entries = column_type.get_num_entries(); 165 | if num_rows == 0 { 166 | num_rows = num_entries 167 | } 168 | if num_rows != num_entries { 169 | return Err(runtime_error!( 170 | "Number of entries should be the same in each column: {num_rows} vs {num_entries}" 171 | )); 172 | } 173 | if h == NULL_HEADER && has_null_column { 174 | let null_shape = column_type.get_data_shape(); 175 | let expected_shape = vec![num_rows]; 176 | if null_shape != expected_shape { 177 | return Err(runtime_error!( 178 | "Null column has shape {null_shape:?}, should be {expected_shape:?}" 179 | )); 180 | } 181 | contains_null = true; 182 | } 183 | all_headers.insert(h.clone(), column_type); 184 | } 185 | if !contains_null && has_null_column { 186 | return Err(runtime_error!("Named tuple should contain the null column")); 187 | } 188 | Ok((all_headers, num_rows)) 189 | } 190 | -------------------------------------------------------------------------------- /ciphercore-base/src/mpc.rs: -------------------------------------------------------------------------------- 1 | pub mod low_mc; 2 | mod mpc_apply_permutation; 3 | mod mpc_arithmetic; 4 | pub mod mpc_compiler; 5 | mod mpc_conversion; 6 | mod mpc_equivalence_class; 7 | mod mpc_psi; 8 | mod mpc_radix_sort; 9 | mod mpc_truncate; 10 | mod resharing; 11 | pub mod utils; 12 | -------------------------------------------------------------------------------- /ciphercore-base/src/mpc/low_mc_constants/generate_matrices.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import numpy as np 3 | import itertools 4 | 5 | def matrix_to_bytes(mat): 6 | ''' Convert a given matrix to the list of bytes 7 | ''' 8 | bytes_list = [] 9 | bit_counter = 0 10 | byte = 0 11 | for bit in np.nditer(mat): 12 | byte += bit << bit_counter 13 | if bit_counter == 7: 14 | bytes_list.append(byte) 15 | byte = 0 16 | bit_counter = (bit_counter + 1) % 8 17 | if bit_counter > 0: 18 | bytes_list.append(byte) 19 | 20 | return bytes(bytes_list) 21 | 22 | def main(): 23 | ''' Use the global parameters `blocksize`, `keysize` and `rounds` 24 | to create the set of matrices and constants for the corresponding 25 | LowMC instance. 26 | ''' 27 | 28 | program_description = 'Create a set of matrices and constants for the corresponding LowMC instance. Save those in files named `linear_layer_matrices.dat`, `key_matrices.dat`, `round_constant.dat`.' 29 | parser = argparse.ArgumentParser(description=program_description) 30 | parser.add_argument('blocksize', type=int, help='specifies block size in bits') 31 | parser.add_argument('keysize', type=int, help='specifies key size in bits') 32 | parser.add_argument('rounds', type=int, help='specifies the number of rounds') 33 | 34 | arguments = parser.parse_args() 35 | blocksize = arguments.blocksize 36 | keysize = arguments.keysize 37 | rounds = arguments.rounds 38 | 39 | gen = grain_ssg() 40 | linlayers = np.array([instantiate_matrix(blocksize, blocksize, gen) for _ in range(rounds)]) 41 | round_constants = np.array([[next(gen) for _ in range(blocksize)] for _ in range(rounds)]) 42 | roundkey_matrices = np.array([instantiate_matrix(blocksize, keysize, gen) for _ in range(rounds + 1)]) 43 | 44 | with open('linear_layer_matrices.dat', 'wb') as linfile: 45 | linfile.write(matrix_to_bytes(linlayers)) 46 | 47 | with open('key_matrices.dat', 'wb') as keyfile: 48 | keyfile.write(matrix_to_bytes(roundkey_matrices)) 49 | 50 | with open('round_constants.dat', 'wb') as constfile: 51 | constfile.write(matrix_to_bytes(round_constants)) 52 | 53 | with open('matrices_and_constants.dat', 'w') as matfile: 54 | s = 'LowMC matrices and constants\n'\ 55 | '============================\n'\ 56 | 'Block size: ' + str(blocksize) + '\n'\ 57 | 'Key size: ' + str(keysize) + '\n'\ 58 | 'Rounds: ' + str(rounds) + '\n\n' 59 | matfile.write(s) 60 | s = 'Linear layer matrices\n'\ 61 | '---------------------' 62 | matfile.write(s) 63 | for r in range(rounds): 64 | s = '\nLinear layer ' + str(r + 1) + ':\n' 65 | for row in linlayers[r]: 66 | s += str(row) + '\n' 67 | matfile.write(s) 68 | 69 | s = '\nRound constants\n'\ 70 | '---------------------' 71 | matfile.write(s) 72 | for r in range(rounds): 73 | s = '\nRound constant ' + str(r + 1) + ':\n' 74 | s += str(round_constants[r]) + '\n' 75 | matfile.write(s) 76 | 77 | s = '\nRound key matrices\n'\ 78 | '---------------------' 79 | matfile.write(s) 80 | for r in range(rounds + 1): 81 | s = '\nRound key matrix ' + str(r) + ':\n' 82 | for row in roundkey_matrices[r]: 83 | s += str(row) + '\n' 84 | matfile.write(s) 85 | 86 | def instantiate_matrix(n, m, gen): 87 | ''' Instantiate a matrix of maximal rank using bits from the 88 | generatator `gen`. 89 | ''' 90 | while True: 91 | mat = np.fromiter(itertools.islice(gen, n*m), dtype=int).reshape((n,m)) 92 | if np.linalg.matrix_rank(mat) >= min(n, m): 93 | return mat 94 | 95 | 96 | def grain_ssg(): 97 | ''' A generator for using the Grain LSFR in a self-shrinking generator. ''' 98 | state = [1 for _ in range(80)] 99 | index = 0 100 | # Discard first 160 bits 101 | for _ in range(160): 102 | state[index] ^= state[(index + 13) % 80] ^ state[(index + 23) % 80]\ 103 | ^ state[(index + 38) % 80] ^ state[(index + 51) % 80]\ 104 | ^ state[(index + 62) % 80] 105 | index += 1 106 | index %= 80 107 | choice = False 108 | while True: 109 | state[index] ^= state[(index + 13) % 80] ^ state[(index + 23) % 80]\ 110 | ^ state[(index + 38) % 80] ^ state[(index + 51) % 80]\ 111 | ^ state[(index + 62) % 80] 112 | choice = state[index] 113 | index += 1 114 | index %= 80 115 | state[index] ^= state[(index + 13) % 80] ^ state[(index + 23) % 80]\ 116 | ^ state[(index + 38) % 80] ^ state[(index + 51) % 80]\ 117 | ^ state[(index + 62) % 80] 118 | if choice == 1: 119 | yield state[index] 120 | index += 1 121 | index %= 80 122 | 123 | 124 | if __name__ == '__main__': 125 | main() 126 | -------------------------------------------------------------------------------- /ciphercore-base/src/mpc/low_mc_constants/key_matrices128.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciphermodelabs/ciphercore/cd4a2cd8e4e24392a6390ba2364a6387089c8e18/ciphercore-base/src/mpc/low_mc_constants/key_matrices128.dat -------------------------------------------------------------------------------- /ciphercore-base/src/mpc/low_mc_constants/key_matrices80.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciphermodelabs/ciphercore/cd4a2cd8e4e24392a6390ba2364a6387089c8e18/ciphercore-base/src/mpc/low_mc_constants/key_matrices80.dat -------------------------------------------------------------------------------- /ciphercore-base/src/mpc/low_mc_constants/linear_layer_matrices128.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciphermodelabs/ciphercore/cd4a2cd8e4e24392a6390ba2364a6387089c8e18/ciphercore-base/src/mpc/low_mc_constants/linear_layer_matrices128.dat -------------------------------------------------------------------------------- /ciphercore-base/src/mpc/low_mc_constants/linear_layer_matrices80.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciphermodelabs/ciphercore/cd4a2cd8e4e24392a6390ba2364a6387089c8e18/ciphercore-base/src/mpc/low_mc_constants/linear_layer_matrices80.dat -------------------------------------------------------------------------------- /ciphercore-base/src/mpc/low_mc_constants/round_constants128.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciphermodelabs/ciphercore/cd4a2cd8e4e24392a6390ba2364a6387089c8e18/ciphercore-base/src/mpc/low_mc_constants/round_constants128.dat -------------------------------------------------------------------------------- /ciphercore-base/src/mpc/low_mc_constants/round_constants80.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciphermodelabs/ciphercore/cd4a2cd8e4e24392a6390ba2364a6387089c8e18/ciphercore-base/src/mpc/low_mc_constants/round_constants80.dat -------------------------------------------------------------------------------- /ciphercore-base/src/ops.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of several custom operations. 2 | //! A custom operation can be thought of as a polymorphic function, i.e., where the number of inputs and their types can vary. 3 | 4 | pub mod adder; 5 | pub mod auc; 6 | mod broadcast_test; 7 | pub mod clip; 8 | pub mod comparisons; 9 | pub mod fixed_precision; 10 | pub mod goldschmidt_division; 11 | pub mod integer_key_sort; 12 | pub mod inverse_sqrt; 13 | pub mod long_division; 14 | pub mod min_max; 15 | pub mod multiplexer; 16 | pub mod newton_inversion; 17 | pub mod pwl; 18 | pub mod taylor_exponent; 19 | #[doc(hidden)] 20 | pub mod utils; 21 | -------------------------------------------------------------------------------- /ciphercore-base/src/ops/broadcast_test.rs: -------------------------------------------------------------------------------- 1 | //! Regression tests for broadcasting in custom operations. 2 | //! We don't test correctness, just that it doesn't crash. 3 | 4 | #[cfg(test)] 5 | mod tests { 6 | use ndarray::array; 7 | 8 | use crate::custom_ops::run_instantiation_pass; 9 | use crate::custom_ops::CustomOperation; 10 | use crate::custom_ops::CustomOperationBody; 11 | use crate::data_types::INT64; 12 | use crate::errors::Result; 13 | use crate::evaluators::random_evaluate; 14 | use crate::graphs::util::simple_context; 15 | use crate::ops::adder::BinaryAdd; 16 | use crate::ops::comparisons::{ 17 | Equal, GreaterThan, GreaterThanEqualTo, LessThan, LessThanEqualTo, NotEqual, 18 | }; 19 | use crate::ops::fixed_precision::fixed_multiply::FixedMultiply; 20 | use crate::ops::fixed_precision::fixed_precision_config::FixedPrecisionConfig; 21 | use crate::ops::goldschmidt_division::GoldschmidtDivision; 22 | use crate::ops::min_max::Max; 23 | use crate::ops::min_max::Min; 24 | use crate::typed_value::TypedValue; 25 | use crate::typed_value_operations::TypedValueArrayOperations; 26 | 27 | #[test] 28 | fn test_binary_custom_op_broadcast() -> Result<()> { 29 | // Broadcasting in custom operations that operate on two bitstrings. 30 | let values = vec![ 31 | TypedValue::from_scalar(1, INT64)?, 32 | TypedValue::from_ndarray(array![1].into_dyn(), INT64)?, 33 | TypedValue::from_ndarray(array![1, 2, 3].into_dyn(), INT64)?, 34 | TypedValue::from_ndarray(array![[1, 2, 3], [4, 5, 6]].into_dyn(), INT64)?, 35 | ]; 36 | for v1 in values.iter() { 37 | for v2 in values.iter() { 38 | binary_helper( 39 | BinaryAdd { 40 | overflow_bit: false, 41 | }, 42 | v1.clone(), 43 | v2.clone(), 44 | )?; 45 | binary_helper(Equal {}, v1.clone(), v2.clone())?; 46 | binary_helper(NotEqual {}, v1.clone(), v2.clone())?; 47 | macro_rules! comparison_op { 48 | ($custom_op:ident) => { 49 | binary_helper( 50 | $custom_op { 51 | signed_comparison: true, 52 | }, 53 | v1.clone(), 54 | v2.clone(), 55 | )?; 56 | }; 57 | } 58 | comparison_op!(LessThan); 59 | comparison_op!(LessThanEqualTo); 60 | comparison_op!(GreaterThan); 61 | comparison_op!(GreaterThanEqualTo); 62 | comparison_op!(Min); 63 | comparison_op!(Max); 64 | } 65 | } 66 | Ok(()) 67 | } 68 | 69 | fn binary_helper( 70 | operation: impl CustomOperationBody, 71 | arg1: TypedValue, 72 | arg2: TypedValue, 73 | ) -> Result<()> { 74 | let c = simple_context(|g| { 75 | let i1 = g.input(arg1.t)?; 76 | let i2 = g.input(arg2.t)?; 77 | let b1 = i1.a2b()?; 78 | let b2 = i2.a2b()?; 79 | g.custom_op(CustomOperation::new(operation), vec![b1, b2]) 80 | })?; 81 | let c = run_instantiation_pass(c)?.context; 82 | random_evaluate(c.get_main_graph()?, vec![arg1.value, arg2.value])?; 83 | Ok(()) 84 | } 85 | 86 | #[test] 87 | fn test_arithemtic_custom_op_broadcast() -> Result<()> { 88 | // Broadcasting in custom operations that operate on two numbers. 89 | let values = vec![ 90 | TypedValue::from_scalar(1, INT64)?, 91 | TypedValue::from_ndarray(array![1].into_dyn(), INT64)?, 92 | TypedValue::from_ndarray(array![1, 2, 3].into_dyn(), INT64)?, 93 | TypedValue::from_ndarray(array![[1, 2, 3], [4, 5, 6]].into_dyn(), INT64)?, 94 | ]; 95 | for v1 in values.iter() { 96 | for v2 in values.iter() { 97 | arithmetic_helper( 98 | GoldschmidtDivision { 99 | iterations: 5, 100 | denominator_cap_2k: 15, 101 | }, 102 | v1.clone(), 103 | v2.clone(), 104 | )?; 105 | arithmetic_helper( 106 | FixedMultiply { 107 | config: FixedPrecisionConfig { 108 | fractional_bits: 10, 109 | debug: false, 110 | }, 111 | }, 112 | v1.clone(), 113 | v2.clone(), 114 | )?; 115 | } 116 | } 117 | Ok(()) 118 | } 119 | 120 | fn arithmetic_helper( 121 | operation: impl CustomOperationBody, 122 | arg1: TypedValue, 123 | arg2: TypedValue, 124 | ) -> Result<()> { 125 | let c = simple_context(|g| { 126 | let i1 = g.input(arg1.t)?; 127 | let i2 = g.input(arg2.t)?; 128 | g.custom_op(CustomOperation::new(operation), vec![i1, i2]) 129 | })?; 130 | let c = run_instantiation_pass(c)?.context; 131 | random_evaluate(c.get_main_graph()?, vec![arg1.value, arg2.value])?; 132 | Ok(()) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /ciphercore-base/src/ops/fixed_precision.rs: -------------------------------------------------------------------------------- 1 | pub mod fixed_multiply; 2 | pub mod fixed_precision_config; 3 | -------------------------------------------------------------------------------- /ciphercore-base/src/ops/fixed_precision/fixed_precision_config.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// Configuration for fixed precision arithmetic. 4 | #[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, Copy)] 5 | pub struct FixedPrecisionConfig { 6 | /// Number of bits for fixed precision. 7 | /// Integer `x` represents `x / 2^fractional_bits`. 8 | pub fractional_bits: u64, 9 | 10 | /// Whether to perform expensive debug-only checks for fixed precision arithmetic. 11 | /// E.g. for multiplication, we can check for overflow. 12 | pub debug: bool, 13 | } 14 | 15 | impl Default for FixedPrecisionConfig { 16 | fn default() -> Self { 17 | Self { 18 | fractional_bits: 15, 19 | debug: false, 20 | } 21 | } 22 | } 23 | 24 | impl FixedPrecisionConfig { 25 | pub fn denominator(&self) -> u128 { 26 | 1u128 << self.fractional_bits 27 | } 28 | 29 | pub fn denominator_f64(&self) -> f64 { 30 | self.denominator() as f64 31 | } 32 | 33 | pub fn new(fractional_bits: u64) -> Self { 34 | Self { 35 | fractional_bits, 36 | debug: false, 37 | } 38 | } 39 | 40 | pub fn set_debug(mut self, debug: bool) -> Self { 41 | self.debug = debug; 42 | self 43 | } 44 | 45 | pub fn convert_from_float(&self, x: f64) -> i64 { 46 | (x * self.denominator_f64()).round() as i64 47 | } 48 | 49 | pub fn convert_to_float(&self, x: i64) -> f64 { 50 | x as f64 / self.denominator_f64() 51 | } 52 | 53 | pub fn convert_from_floats(&self, a: &[f64]) -> Vec { 54 | a.iter().map(|&x| self.convert_from_float(x)).collect() 55 | } 56 | 57 | pub fn convert_to_floats(&self, a: &[i64]) -> Vec { 58 | a.iter().map(|&x| self.convert_to_float(x)).collect() 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ciphercore-base/src/ops/integer_key_sort.rs: -------------------------------------------------------------------------------- 1 | //! Sort by integer key. 2 | use crate::custom_ops::CustomOperationBody; 3 | use crate::data_types::{ScalarType, Type, BIT}; 4 | use crate::errors::Result; 5 | use crate::graphs::*; 6 | 7 | use serde::{Deserialize, Serialize}; 8 | 9 | use super::comparisons::flip_msb; 10 | use super::utils::unsqueeze; 11 | 12 | #[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Hash)] 13 | pub struct SortByIntegerKey { 14 | pub key: String, 15 | } 16 | 17 | #[typetag::serde] 18 | impl CustomOperationBody for SortByIntegerKey { 19 | fn instantiate(&self, context: Context, arguments_types: Vec) -> Result { 20 | if arguments_types.len() != 1 { 21 | return Err(runtime_error!("SortByIntegerKey accepts only 1 argument")); 22 | } 23 | if !arguments_types[0].is_named_tuple() { 24 | return Err(runtime_error!( 25 | "SortByIntegerKey accepts only named tuple as input" 26 | )); 27 | } 28 | let g = context.create_graph()?; 29 | let input = g.input(arguments_types[0].clone())?; 30 | let mut elements = vec![]; 31 | for (name, _) in arguments_types[0].get_named_types()? { 32 | let node = input.named_tuple_get(name.clone())?; 33 | if name == self.key { 34 | elements.push((name, integer_to_bits(node)?)); 35 | } else { 36 | elements.push((name, node)); 37 | } 38 | } 39 | let sorted = g.create_named_tuple(elements)?.sort(self.key.clone())?; 40 | let mut elements = vec![]; 41 | for (name, t) in arguments_types[0].get_named_types()? { 42 | let node = sorted.named_tuple_get(name.clone())?; 43 | if name == self.key { 44 | elements.push((name, integer_from_bits(node, t.get_scalar_type())?)); 45 | } else { 46 | elements.push((name, node)); 47 | } 48 | } 49 | let output = g.create_named_tuple(elements)?; 50 | g.set_output_node(output)?; 51 | g.finalize()?; 52 | Ok(g) 53 | } 54 | fn get_name(&self) -> String { 55 | "SortIntegers".to_string() 56 | } 57 | } 58 | 59 | fn integer_to_bits(node: Node) -> Result { 60 | let st = node.get_type()?.get_scalar_type(); 61 | if st == BIT { 62 | return unsqueeze(node, -1); 63 | } 64 | // Convert to bit representation. 65 | let node = node.a2b()?; 66 | // We need to preocess sign bit correctly. Reuse method from `comparisons`. 67 | let node = if st.is_signed() { 68 | flip_msb(node)? 69 | } else { 70 | node 71 | }; 72 | // Reverse order of bits. 73 | node.get_slice(vec![ 74 | SliceElement::Ellipsis, 75 | SliceElement::SubArray(None, None, Some(-1)), 76 | ]) 77 | } 78 | 79 | fn integer_from_bits(node: Node, st: ScalarType) -> Result { 80 | if st == BIT { 81 | return node.get_slice(vec![SliceElement::Ellipsis, SliceElement::SingleIndex(0)]); 82 | } 83 | // Order of bits was reversed. 84 | let node = node.get_slice(vec![ 85 | SliceElement::Ellipsis, 86 | SliceElement::SubArray(None, None, Some(-1)), 87 | ])?; 88 | // We need to preocess sign bit correctly. Reuse method from `comparisons`. 89 | let node = if st.is_signed() { 90 | flip_msb(node)? 91 | } else { 92 | node 93 | }; 94 | // Convert to integer representation. 95 | node.b2a(st) 96 | } 97 | 98 | #[cfg(test)] 99 | mod tests { 100 | use crate::{ 101 | custom_ops::{run_instantiation_pass, CustomOperation}, 102 | data_types::{INT64, UINT64}, 103 | evaluators::random_evaluate, 104 | graphs::util::simple_context, 105 | typed_value::TypedValue, 106 | typed_value_operations::{TypedValueArrayOperations, TypedValueOperations}, 107 | }; 108 | 109 | use super::*; 110 | 111 | fn test_helper(mut x: Vec, st: ScalarType) -> Result<()> { 112 | let values = TypedValue::from_ndarray(ndarray::Array::from(x.clone()).into_dyn(), st)?; 113 | let c = simple_context(|g| { 114 | let input = g.input(values.get_type())?; 115 | let key = "key".to_string(); 116 | let node = g.create_named_tuple(vec![(key.clone(), input)])?; 117 | let sorted = g.custom_op( 118 | CustomOperation::new(SortByIntegerKey { key: key.clone() }), 119 | vec![node], 120 | )?; 121 | sorted.named_tuple_get(key) 122 | })?; 123 | let output_type = c.get_main_graph()?.get_output_node()?.get_type()?; 124 | let c = run_instantiation_pass(c)?.context; 125 | 126 | let result = random_evaluate(c.get_main_graph()?, vec![values.value])? 127 | .to_flattened_array_i64(output_type)?; 128 | x.sort(); 129 | assert_eq!(result, x); 130 | Ok(()) 131 | } 132 | 133 | #[test] 134 | fn test_correctnes() -> Result<()> { 135 | let x = vec![50, 1, 300, 13, 74532, 100]; 136 | test_helper(x, UINT64)?; 137 | let x = vec![12, 45, -1, 0, 2, -100, 50, 1, 300]; 138 | test_helper(x, INT64)?; 139 | let x = vec![0, 0, 1, 1, 0, 1, 0, 1, 1, 0]; 140 | test_helper(x, BIT) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /ciphercore-base/src/ops/pwl.rs: -------------------------------------------------------------------------------- 1 | pub mod approx_exponent; 2 | pub mod approx_gelu; 3 | pub mod approx_gelu_derivative; 4 | pub mod approx_pointwise; 5 | pub mod approx_sigmoid; 6 | -------------------------------------------------------------------------------- /ciphercore-base/src/ops/pwl/approx_exponent.rs: -------------------------------------------------------------------------------- 1 | //! Exp(x) piecewise-linear approximation. 2 | use crate::custom_ops::CustomOperationBody; 3 | use crate::data_types::{Type, INT64}; 4 | use crate::errors::Result; 5 | use crate::graphs::{Context, Graph}; 6 | 7 | use serde::{Deserialize, Serialize}; 8 | 9 | use super::approx_pointwise::{create_approximation, PWLConfig}; 10 | 11 | /// A structure that defines the custom operation ApproxExponent that computes an approximate exp(x / (2 ** precision)) * (2 ** precision) using piecewise-linear approximation. 12 | /// 13 | /// So far this operation supports only INT64 scalar type. 14 | /// 15 | /// # Custom operation arguments 16 | /// 17 | /// - Node containing a signed 64-bit array or scalar to compute the exponent 18 | /// 19 | /// # Custom operation returns 20 | /// 21 | /// New ApproxExponent node 22 | /// 23 | /// # Example 24 | /// 25 | /// ``` 26 | /// # use ciphercore_base::graphs::create_context; 27 | /// # use ciphercore_base::data_types::{scalar_type, array_type, INT64}; 28 | /// # use ciphercore_base::custom_ops::{CustomOperation}; 29 | /// # use ciphercore_base::ops::pwl::approx_exponent::ApproxExponent; 30 | /// let c = create_context().unwrap(); 31 | /// let g = c.create_graph().unwrap(); 32 | /// let t = array_type(vec![3], INT64); 33 | /// let x = g.input(t.clone()).unwrap(); 34 | /// let n = g.custom_op(CustomOperation::new(ApproxExponent {precision: 4}), vec![x]).unwrap(); 35 | /// 36 | // TODO: generalize to other types. 37 | #[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Hash)] 38 | pub struct ApproxExponent { 39 | /// Assume that we're operating in fixed precision arithmetic with denominator 2 ** precision. 40 | pub precision: u64, 41 | } 42 | 43 | #[typetag::serde] 44 | impl CustomOperationBody for ApproxExponent { 45 | fn instantiate(&self, context: Context, arguments_types: Vec) -> Result { 46 | if arguments_types.len() != 1 { 47 | return Err(runtime_error!( 48 | "Invalid number of arguments for ApproxExponent" 49 | )); 50 | } 51 | let t = arguments_types[0].clone(); 52 | if !t.is_scalar() && !t.is_array() { 53 | return Err(runtime_error!( 54 | "Argument in ApproxExponent must be a scalar or an array" 55 | )); 56 | } 57 | let sc = t.get_scalar_type(); 58 | if sc != INT64 { 59 | return Err(runtime_error!( 60 | "Argument in ApproxExponent must consist of INT64's" 61 | )); 62 | } 63 | if self.precision > 30 || self.precision == 0 { 64 | return Err(runtime_error!("`precision` should be in range [1, 30].")); 65 | } 66 | 67 | let g = context.create_graph()?; 68 | let arg = g.input(t)?; 69 | // Choice of parameters: 70 | // -- left/right: our typical use-case is precision=15, leading to minimum value around 3e-5. Exp(-10) is 4e-5, so right=-left=10 is a reasonable choice with our precision; 71 | // -- log_buckets: we look at max relative difference to the real exponent. It looks as follows (note that this usually happens around -10, for higher values, it is more accurate): 72 | // log_buckets=4 => 36%, 73 | // log_buckets=5 => 21%, 74 | // log_buckets=6 => 22%, 75 | // log_buckets=7 => 22%. 76 | // Note that it is not monotonic due to numerical issues for very low values. From this table, the best value is 5. 77 | // -- flatten_left/flatten_right: exponent is flat on the left, so we replicate this in our approximation (we don't want to go to 0 and below). 78 | // However, due to the issues with Truncate on non-power-of-2 denominators, we want (right - left) to be a power of 2, 79 | // so we round the boundaries to +/- 16, and increase the log_buckets to 6. 80 | let result = create_approximation( 81 | arg, 82 | |x| x.exp(), 83 | // The boundaries are chosen in a way that (left - right) * precision is a power of 2. 84 | // It is important because otherwise we get non-power-of-2 truncations during the approximation. 85 | -16.0, 86 | 16.0, 87 | self.precision, 88 | PWLConfig { 89 | log_buckets: 6, 90 | flatten_left: true, 91 | flatten_right: false, 92 | }, 93 | )?; 94 | result.set_as_output()?; 95 | g.finalize()?; 96 | Ok(g) 97 | } 98 | 99 | fn get_name(&self) -> String { 100 | format!("ApproxExponent(scaling_factor=2**{})", self.precision) 101 | } 102 | } 103 | 104 | #[cfg(test)] 105 | mod tests { 106 | use super::*; 107 | 108 | use crate::custom_ops::run_instantiation_pass; 109 | use crate::custom_ops::CustomOperation; 110 | use crate::data_types::array_type; 111 | use crate::data_types::scalar_type; 112 | use crate::data_values::Value; 113 | use crate::evaluators::random_evaluate; 114 | use crate::graphs::util::simple_context; 115 | 116 | fn scalar_helper(arg: i64, precision: u64) -> Result { 117 | let c = simple_context(|g| { 118 | let i = g.input(scalar_type(INT64))?; 119 | g.custom_op(CustomOperation::new(ApproxExponent { precision }), vec![i]) 120 | })?; 121 | let mapped_c = run_instantiation_pass(c)?; 122 | let result = random_evaluate( 123 | mapped_c.get_context().get_main_graph()?, 124 | vec![Value::from_scalar(arg, INT64)?], 125 | )?; 126 | let res = result.to_i64(INT64)?; 127 | Ok(res) 128 | } 129 | 130 | fn array_helper(arg: Vec) -> Result> { 131 | let array_t = array_type(vec![arg.len() as u64], INT64); 132 | let c = simple_context(|g| { 133 | let i = g.input(array_t.clone())?; 134 | g.custom_op( 135 | CustomOperation::new(ApproxExponent { precision: 10 }), 136 | vec![i], 137 | ) 138 | })?; 139 | let mapped_c = run_instantiation_pass(c)?; 140 | let result = random_evaluate( 141 | mapped_c.get_context().get_main_graph()?, 142 | vec![Value::from_flattened_array(&arg, INT64)?], 143 | )?; 144 | result.to_flattened_array_i64(array_t) 145 | } 146 | 147 | #[test] 148 | fn test_approx_exp_scalar() { 149 | for i in [-10000, -1000, -100, -1, 0, 1, 100, 1000, 10000] { 150 | let expected = (((i as f64) / 1024.0).exp() * 1024.0) as i64; 151 | let actual = scalar_helper(i, 10).unwrap(); 152 | let relative_error = ((expected - actual).abs() as f64) 153 | / (1.0 + f64::max(expected as f64, actual as f64)); 154 | assert!(relative_error <= 0.05); 155 | } 156 | } 157 | 158 | #[test] 159 | fn test_approx_exp_array() { 160 | let arr = vec![23, 32, 57, 1271, 183, 555, -23, -32, -57, -1271, -183, -555]; 161 | let res = array_helper(arr.clone()).unwrap(); 162 | for i in 0..arr.len() { 163 | let expected = (((arr[i] as f64) / 1024.0).exp() * 1024.0) as i64; 164 | let actual = res[i]; 165 | let relative_error = ((expected - actual).abs() as f64) 166 | / (1.0 + f64::max(expected as f64, actual as f64)); 167 | assert!(relative_error <= 0.05); 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /ciphercore-base/src/ops/pwl/approx_sigmoid.rs: -------------------------------------------------------------------------------- 1 | //! Sigmoid(x) piecewise-linear approximation. 2 | use crate::custom_ops::CustomOperationBody; 3 | use crate::data_types::{Type, INT64}; 4 | use crate::errors::Result; 5 | use crate::graphs::{Context, Graph}; 6 | 7 | use serde::{Deserialize, Serialize}; 8 | 9 | use super::approx_pointwise::{create_approximation, PWLConfig}; 10 | 11 | /// A structure that defines the custom operation ApproxSigmoid that computes an approximate Sigmoid(x / (2 ** precision)) * (2 ** precision) using piecewise-linear approximation. 12 | /// 13 | /// Sigmoid is a very commonly used function in ML: Sigmoid(x) = 1 / (1 + exp(-x)). 14 | /// So far this operation supports only INT64 scalar type. 15 | /// 16 | /// # Custom operation arguments 17 | /// 18 | /// - Node containing a signed 64-bit array or scalar to compute the sigmoid 19 | /// 20 | /// # Custom operation returns 21 | /// 22 | /// New ApproxSigmoid node 23 | /// 24 | /// # Example 25 | /// 26 | /// ``` 27 | /// # use ciphercore_base::graphs::create_context; 28 | /// # use ciphercore_base::data_types::{scalar_type, array_type, INT64}; 29 | /// # use ciphercore_base::custom_ops::{CustomOperation}; 30 | /// # use ciphercore_base::ops::pwl::approx_sigmoid::ApproxSigmoid; 31 | /// let c = create_context().unwrap(); 32 | /// let g = c.create_graph().unwrap(); 33 | /// let t = array_type(vec![3], INT64); 34 | /// let x = g.input(t.clone()).unwrap(); 35 | /// let n = g.custom_op(CustomOperation::new(ApproxSigmoid {precision: 4, ..Default::default()}), vec![x]).unwrap(); 36 | /// 37 | // TODO: generalize to other types. 38 | #[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Hash)] 39 | pub struct ApproxSigmoid { 40 | /// Assume that we're operating in fixed precision arithmetic with denominator 2 ** precision. 41 | pub precision: u64, 42 | pub approximation_log_buckets: u64, 43 | } 44 | 45 | impl Default for ApproxSigmoid { 46 | fn default() -> Self { 47 | ApproxSigmoid { 48 | precision: 15, 49 | approximation_log_buckets: 5, 50 | } 51 | } 52 | } 53 | 54 | #[typetag::serde] 55 | impl CustomOperationBody for ApproxSigmoid { 56 | fn instantiate(&self, context: Context, arguments_types: Vec) -> Result { 57 | if arguments_types.len() != 1 { 58 | return Err(runtime_error!( 59 | "Invalid number of arguments for ApproxSigmoid" 60 | )); 61 | } 62 | let t = arguments_types[0].clone(); 63 | if !t.is_scalar() && !t.is_array() { 64 | return Err(runtime_error!( 65 | "Argument in ApproxSigmoid must be a scalar or an array" 66 | )); 67 | } 68 | let sc = t.get_scalar_type(); 69 | if sc != INT64 { 70 | return Err(runtime_error!( 71 | "Argument in ApproxSigmoid must consist of INT64's" 72 | )); 73 | } 74 | if self.precision > 30 || self.precision == 0 { 75 | return Err(runtime_error!("`precision` should be in range [1, 30].")); 76 | } 77 | 78 | let g = context.create_graph()?; 79 | let arg = g.input(t)?; 80 | // Choice of parameters: 81 | // -- left/right: our typical use-case is precision=15, leading to minimum value around 3e-5. Sigmoid(-10) is 3.e-4, so right=-left=10 is a reasonable choice with our precision; 82 | // -- log_buckets: we look at max absolute difference to the real sigmoid. It looks as follows: 83 | // log_buckets=4 => 0.0163, 84 | // log_buckets=5 => 0.0045, 85 | // log_buckets=6 => 0.0012. 86 | // After 5 segments, we're getting diminishing returns, so it doesn't make sense to go higher (for the sake of performance). 87 | // -- flatten_left/flatten_right: sigmoid is flat on both sides. 88 | let result = create_approximation( 89 | arg, 90 | |x| 1.0 / (1.0 + (-x).exp()), 91 | // The boundaries are chosen in a way that (left - right) * precision is a power of 2. 92 | // It is important because otherwise we get non-power-of-2 truncations during the approximation. 93 | -8.0, 94 | 8.0, 95 | self.precision, 96 | PWLConfig { 97 | log_buckets: self.approximation_log_buckets, 98 | flatten_left: true, 99 | flatten_right: true, 100 | }, 101 | )?; 102 | result.set_as_output()?; 103 | g.finalize()?; 104 | Ok(g) 105 | } 106 | 107 | fn get_name(&self) -> String { 108 | format!("ApproxSigmoid(scaling_factor=2**{})", self.precision) 109 | } 110 | } 111 | 112 | #[cfg(test)] 113 | mod tests { 114 | use super::*; 115 | 116 | use crate::custom_ops::run_instantiation_pass; 117 | use crate::custom_ops::CustomOperation; 118 | use crate::data_types::array_type; 119 | use crate::data_types::scalar_type; 120 | use crate::data_values::Value; 121 | use crate::evaluators::random_evaluate; 122 | use crate::graphs::util::simple_context; 123 | 124 | fn scalar_helper(arg: i64, precision: u64) -> Result { 125 | let c = simple_context(|g| { 126 | let i = g.input(scalar_type(INT64))?; 127 | g.custom_op( 128 | CustomOperation::new(ApproxSigmoid { 129 | precision, 130 | ..Default::default() 131 | }), 132 | vec![i], 133 | ) 134 | })?; 135 | let mapped_c = run_instantiation_pass(c)?; 136 | let result = random_evaluate( 137 | mapped_c.get_context().get_main_graph()?, 138 | vec![Value::from_scalar(arg, INT64)?], 139 | )?; 140 | let res = result.to_i64(INT64)?; 141 | Ok(res) 142 | } 143 | 144 | fn array_helper(arg: Vec) -> Result> { 145 | let array_t = array_type(vec![arg.len() as u64], INT64); 146 | let c = simple_context(|g| { 147 | let i = g.input(array_t.clone())?; 148 | g.custom_op( 149 | CustomOperation::new(ApproxSigmoid { 150 | precision: 10, 151 | ..Default::default() 152 | }), 153 | vec![i], 154 | ) 155 | })?; 156 | let mapped_c = run_instantiation_pass(c)?; 157 | let result = random_evaluate( 158 | mapped_c.get_context().get_main_graph()?, 159 | vec![Value::from_flattened_array(&arg, INT64)?], 160 | )?; 161 | result.to_flattened_array_i64(array_t) 162 | } 163 | 164 | fn sigmoid(x: f32) -> f32 { 165 | 1.0 / (1.0 + (-x).exp()) 166 | } 167 | 168 | #[test] 169 | fn test_approx_sigmoid_scalar() { 170 | for i in (-5000..5000).step_by(1000) { 171 | let expected = (sigmoid((i as f32) / 1024.0) * 1024.0) as i64; 172 | let actual = scalar_helper(i, 10).unwrap(); 173 | let absolute_error = ((expected - actual).abs() as f64) / 1024.0; 174 | assert!(absolute_error <= 0.01); 175 | } 176 | } 177 | 178 | #[test] 179 | fn test_approx_sigmoid_array() { 180 | let arr: Vec = (-5000..5000).step_by(100).collect(); 181 | let res = array_helper(arr.clone()).unwrap(); 182 | for i in 0..arr.len() { 183 | let expected = (sigmoid((arr[i] as f32) / 1024.0) * 1024.0) as i64; 184 | let actual = res[i]; 185 | let absolute_error = ((expected - actual).abs() as f64) / 1024.0; 186 | assert!(absolute_error <= 0.01); 187 | } 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /ciphercore-base/src/optimizer.rs: -------------------------------------------------------------------------------- 1 | mod constant_optimizer; 2 | mod dangling_nodes_optimizer; 3 | mod duplicates_optimizer; 4 | mod meta_operation_optimizer; 5 | pub mod optimize; 6 | -------------------------------------------------------------------------------- /ciphercore-base/src/optimizer/dangling_nodes_optimizer.rs: -------------------------------------------------------------------------------- 1 | use crate::custom_ops::ContextMappings; 2 | use crate::errors::Result; 3 | use crate::graphs::{copy_node_name, Graph, Node}; 4 | use std::collections::HashSet; 5 | 6 | /// This optimization removes the nodes from which the output node is not reachable. 7 | /// This can happen if the graph is constructed inefficiently, or (more common) as a result 8 | /// of other graph optimizations. 9 | /// This function preserves annotations and names of remaining nodes. 10 | pub(super) fn optimize_graph_dangling_nodes( 11 | graph: Graph, 12 | out_graph: Graph, 13 | ) -> Result { 14 | graph.check_finalized()?; 15 | let mut useful_nodes = HashSet::::new(); 16 | useful_nodes.insert(graph.get_output_node()?); 17 | for node in graph.get_nodes().iter().rev() { 18 | if useful_nodes.contains(node) { 19 | for dep in node.get_node_dependencies() { 20 | useful_nodes.insert(dep.clone()); 21 | } 22 | } 23 | } 24 | let mut mapping = ContextMappings::default(); 25 | for node in graph.get_nodes() { 26 | if !node.get_operation().is_input() && !useful_nodes.contains(&node) { 27 | continue; 28 | } 29 | let mut deps = vec![]; 30 | for dep in node.get_node_dependencies() { 31 | deps.push(mapping.get_node(&dep)); 32 | } 33 | if !node.get_graph_dependencies().is_empty() { 34 | return Err(runtime_error!( 35 | "Graph must be fully inlined to use the optimizer" 36 | )); 37 | } 38 | let new_node = 39 | out_graph.add_node_with_type(deps, vec![], node.get_operation(), node.get_type()?)?; 40 | for annotation in node.get_annotations()? { 41 | new_node.add_annotation(annotation)?; 42 | } 43 | copy_node_name(node.clone(), new_node.clone())?; 44 | if node == graph.get_output_node()? { 45 | new_node.set_as_output()?; 46 | } 47 | mapping.insert_node(node, new_node); 48 | } 49 | Ok(mapping) 50 | } 51 | 52 | #[cfg(test)] 53 | mod tests { 54 | use super::*; 55 | use crate::data_types::{scalar_type, UINT64}; 56 | use crate::data_values::Value; 57 | use crate::graphs::create_context; 58 | use crate::graphs::util::simple_context; 59 | use crate::graphs::{contexts_deep_equal, Operation}; 60 | 61 | #[test] 62 | fn test_no_dangling_nodes() { 63 | || -> Result<()> { 64 | let c = simple_context(|g| { 65 | let i1 = g.input(scalar_type(UINT64))?; 66 | let i2 = g.input(scalar_type(UINT64))?; 67 | let n = i1.add(i2)?; 68 | n.add(g.constant(scalar_type(UINT64), Value::from_scalar(1, UINT64)?)?) 69 | })?; 70 | 71 | let new_c = create_context()?; 72 | let new_g = new_c.create_graph()?; 73 | optimize_graph_dangling_nodes(c.get_main_graph()?, new_g.clone())?; 74 | new_g.finalize()?; 75 | new_g.set_as_main()?; 76 | new_c.finalize()?; 77 | assert!(contexts_deep_equal(&new_c, &c)); 78 | Ok(()) 79 | }() 80 | .unwrap(); 81 | } 82 | 83 | #[test] 84 | fn test_dangling_nodes() { 85 | || -> Result<()> { 86 | let c = simple_context(|g| { 87 | let i1 = g.input(scalar_type(UINT64))?; 88 | let i2 = g.input(scalar_type(UINT64))?; 89 | let _dangling1 = i1.multiply(i2.clone()); 90 | _dangling1?.set_name("Removed")?; 91 | let n = i1.add(i2)?; 92 | n.set_name("Left")?; 93 | let _dangling2 = 94 | n.multiply(g.constant(scalar_type(UINT64), Value::from_scalar(1, UINT64)?)?)?; 95 | n.add(g.constant(scalar_type(UINT64), Value::from_scalar(1, UINT64)?)?) 96 | })?; 97 | 98 | let new_c = create_context()?; 99 | let new_g = new_c.create_graph()?; 100 | optimize_graph_dangling_nodes(c.get_main_graph()?, new_g.clone())?; 101 | new_g.finalize()?; 102 | new_g.set_as_main()?; 103 | new_c.finalize()?; 104 | 105 | assert!(!contexts_deep_equal(&new_c, &c)); 106 | assert_eq!(c.get_main_graph()?.get_nodes().len(), 8); 107 | assert_eq!(new_c.get_main_graph()?.get_nodes().len(), 5); 108 | // Check names 109 | let new_dangling1 = new_c.retrieve_node(new_g.clone(), "Removed"); 110 | assert!(new_dangling1.is_err()); 111 | let new_n = new_c.retrieve_node(new_g, "Left"); 112 | assert!(new_n.is_ok()); 113 | assert_eq!(new_n?.get_operation(), Operation::Add); 114 | Ok(()) 115 | }() 116 | .unwrap(); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /ciphercore-base/src/optimizer/optimize.rs: -------------------------------------------------------------------------------- 1 | use crate::custom_ops::{ContextMappings, MappedContext}; 2 | use crate::errors::Result; 3 | use crate::evaluators::Evaluator; 4 | use crate::graphs::{create_context, Context, Graph, Operation}; 5 | use crate::optimizer::constant_optimizer::optimize_graph_constants; 6 | use crate::optimizer::dangling_nodes_optimizer::optimize_graph_dangling_nodes; 7 | use crate::optimizer::meta_operation_optimizer::optimize_graph_meta_operations; 8 | use crate::random::PRNG; 9 | 10 | use super::duplicates_optimizer::optimize_graph_duplicates; 11 | 12 | /// Applies common optimizations to all graphs in the context. 13 | /// The graphs must be fully inlined. 14 | /// The primary targets of the optimizations here are to remove inefficiencies 15 | /// which happen because of the boilerplate from Iterate inlining. 16 | pub fn optimize_context( 17 | context: &Context, 18 | mut evaluator: T, 19 | ) -> Result { 20 | context.check_finalized()?; 21 | evaluator.preprocess(context)?; 22 | let mut mappings = ContextMappings::default(); 23 | let output_context = create_context()?; 24 | for graph in context.get_graphs() { 25 | let (_const_context, const_graph) = graph_in_new_context(graph.clone())?; 26 | let node_mapping1 = 27 | optimize_graph_constants(graph.clone(), const_graph.clone(), &mut evaluator)?; 28 | const_graph.finalize()?; 29 | 30 | let (_meta_context, meta_graph) = graph_in_new_context(graph.clone())?; 31 | let node_mapping2 = 32 | optimize_graph_meta_operations(const_graph.clone(), meta_graph.clone())?; 33 | meta_graph.finalize()?; 34 | 35 | let (_dup_context, dup_graph) = graph_in_new_context(graph.clone())?; 36 | let node_mapping3 = optimize_graph_duplicates(meta_graph.clone(), dup_graph.clone())?; 37 | dup_graph.finalize()?; 38 | 39 | let final_graph = add_graph_to_context(output_context.clone(), graph.clone())?; 40 | let node_mapping4 = optimize_graph_dangling_nodes(dup_graph.clone(), final_graph.clone())?; 41 | final_graph.finalize()?; 42 | 43 | if graph == context.get_main_graph()? { 44 | final_graph.set_as_main()?; 45 | } 46 | 47 | let mapping = ContextMappings::new_from_chain(&[ 48 | node_mapping1, 49 | node_mapping2, 50 | node_mapping3, 51 | node_mapping4, 52 | ]); 53 | mappings.extend(mapping); 54 | } 55 | output_context.finalize()?; 56 | 57 | Ok(MappedContext::new_with_mappings( 58 | context.clone(), 59 | output_context, 60 | mappings, 61 | )) 62 | } 63 | 64 | fn add_graph_to_context(context: Context, source_graph: Graph) -> Result { 65 | let new_graph = context.create_graph()?; 66 | for annotation in source_graph.get_annotations()? { 67 | new_graph.add_annotation(annotation)?; 68 | } 69 | Ok(new_graph) 70 | } 71 | 72 | fn graph_in_new_context(source_graph: Graph) -> Result<(Context, Graph)> { 73 | let context = create_context()?; 74 | let graph = add_graph_to_context(context.clone(), source_graph)?; 75 | Ok((context, graph)) 76 | } 77 | 78 | #[doc(hidden)] 79 | pub fn stress_test( 80 | c1: Context, 81 | c2: Context, 82 | ip_evaluator1: T, 83 | ip_evaluator2: T, 84 | ) -> Result<()> { 85 | let seed = b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"; 86 | let mut prng = PRNG::new(Some(*seed)).unwrap(); 87 | let mut evaluator1 = ip_evaluator1; 88 | evaluator1.preprocess(&c1).unwrap(); 89 | let mut evaluator2 = ip_evaluator2; 90 | evaluator2.preprocess(&c2).unwrap(); 91 | for _ in 0..10 { 92 | let mut inputs = vec![]; 93 | for node in c1.get_main_graph().unwrap().get_nodes() { 94 | if let Operation::Input(t) = node.get_operation() { 95 | inputs.push(prng.get_random_value(t).unwrap()); 96 | } 97 | } 98 | let result1 = evaluator1 99 | .evaluate_graph(c1.get_main_graph().unwrap(), inputs.clone()) 100 | .unwrap(); 101 | let result2 = evaluator2 102 | .evaluate_graph(c2.get_main_graph().unwrap(), inputs.clone()) 103 | .unwrap(); 104 | assert_eq!(result1, result2); 105 | } 106 | 107 | Ok(()) 108 | } 109 | 110 | #[cfg(test)] 111 | mod tests { 112 | use super::*; 113 | use crate::data_types::{scalar_type, UINT64}; 114 | use crate::data_values::Value; 115 | use crate::evaluators::simple_evaluator::SimpleEvaluator; 116 | use crate::graphs::util::simple_context; 117 | 118 | #[test] 119 | fn test_simple() { 120 | || -> Result<()> { 121 | let c = simple_context(|g| { 122 | let i1 = g.input(scalar_type(UINT64))?; 123 | let i2 = g.input(scalar_type(UINT64))?; 124 | let n = i1.add(i2)?; 125 | n.add(g.constant(scalar_type(UINT64), Value::from_scalar(1, UINT64)?)?) 126 | })?; 127 | 128 | let optimized_c = optimize_context(&c, SimpleEvaluator::new(None)?)?.get_context(); 129 | stress_test( 130 | c, 131 | optimized_c, 132 | SimpleEvaluator::new(None).unwrap(), 133 | SimpleEvaluator::new(None).unwrap(), 134 | )?; 135 | Ok(()) 136 | }() 137 | .unwrap(); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /ciphercore-base/src/test_data/version_testcase.txt: -------------------------------------------------------------------------------- 1 | {"version":2,"data":"{\"finalized\":false,\"graphs\":[],\"main_graph\":null,\"graphs_names\":[],\"nodes_names\":[],\"nodes_annotations\":[],\"graphs_annotations\":[]}"} 2 | {"version":2,"data":"{\"finalized\":false,\"graphs\":[],\"main_graph\":918276318,\"graphs_names\":[],\"nodes_names\":[],\"nodes_annotations\":[],\"graphs_annotations\":[]}"} 3 | {"version":2,"data":"{\"finalized\":false,\"graphs\":[{\"finalized\":true,\"nodes\":[{\"node_dependencies\":[],\"graph_dependencies\":[],\"operation\":{\"Input\":{\"Scalar\":\"bit\"}}}],\"output_node\":0},{\"finalized\":true,\"nodes\":[{\"node_dependencies\":[],\"graph_dependencies\":[],\"operation\":{\"Input\":{\"Scalar\":\"bit\"}}}],\"output_node\":0},{\"finalized\":false,\"nodes\":[{\"node_dependencies\":[],\"graph_dependencies\":[],\"operation\":{\"Input\":{\"Scalar\":\"bit\"}}},{\"node_dependencies\":[0],\"graph_dependencies\":[1],\"operation\":\"Call\"}],\"output_node\":null}],\"main_graph\":null,\"graphs_names\":[],\"nodes_names\":[],\"nodes_annotations\":[],\"graphs_annotations\":[]}"} 4 | {"version":2,"data":"{\"finalized\":false,\"graphs\":[{\"finalized\":true,\"nodes\":[{\"node_dependencies\":[],\"graph_dependencies\":[],\"operation\":{\"Input\":{\"Scalar\":\"bit\"}}}],\"output_node\":0},{\"finalized\":true,\"nodes\":[{\"node_dependencies\":[],\"graph_dependencies\":[],\"operation\":{\"Input\":{\"Scalar\":\"bit\"}}}],\"output_node\":0},{\"finalized\":false,\"nodes\":[{\"node_dependencies\":[],\"graph_dependencies\":[],\"operation\":{\"Input\":{\"Scalar\":\"bit\"}}},{\"node_dependencies\":[918723],\"graph_dependencies\":[1],\"operation\":\"Call\"}],\"output_node\":null}],\"main_graph\":null,\"graphs_names\":[],\"nodes_names\":[],\"nodes_annotations\":[],\"graphs_annotations\":[]}"} 5 | {"version":2,"data":"{\"finalized\":false,\"graphs\":[{\"finalized\":true,\"nodes\":[{\"node_dependencies\":[],\"graph_dependencies\":[],\"operation\":{\"Input\":{\"Scalar\":\"bit\"}}}],\"output_node\":0},{\"finalized\":true,\"nodes\":[{\"node_dependencies\":[],\"graph_dependencies\":[],\"operation\":{\"Input\":{\"Scalar\":\"bit\"}}}],\"output_node\":0},{\"finalized\":false,\"nodes\":[{\"node_dependencies\":[],\"graph_dependencies\":[],\"operation\":{\"Input\":{\"Scalar\":\"bit\"}}},{\"node_dependencies\":[0],\"graph_dependencies\":[918723],\"operation\":\"Call\"}],\"output_node\":null}],\"main_graph\":null,\"graphs_names\":[],\"nodes_names\":[],\"nodes_annotations\":[],\"graphs_annotations\":[]}"} 6 | {"version":2,"data":"{\"finalized\":true,\"graphs\":[{\"finalized\":true,\"nodes\":[{\"node_dependencies\":[],\"graph_dependencies\":[],\"operation\":{\"Input\":{\"Scalar\":\"bit\"}}}],\"output_node\":0}],\"main_graph\":0,\"graphs_names\":[[0,\"main\"]],\"nodes_names\":[[[0,0],\"input\"]],\"nodes_annotations\":[[[0,0],[\"AssociativeOperation\"]]],\"graphs_annotations\":[[0,[\"AssociativeOperation\"]]]}"} 7 | {"version":2,"data":"{\"finalized\":true,\"graphs\":[{\"finalized\":true,\"nodes\":[{\"node_dependencies\":[],\"graph_dependencies\":[],\"operation\":{\"Input\":{\"Scalar\":\"bit\"}}}],\"output_node\":9817273}],\"main_graph\":0,\"graphs_names\":[[0,\"main\"]],\"nodes_names\":[[[0,0],\"input\"]],\"nodes_annotations\":[[[0,0],[\"AssociativeOperation\"]]],\"graphs_annotations\":[[0,[\"AssociativeOperation\"]]]}"} 8 | {"version":2,"data":"{\"finalized\":true,\"graphs\":[{\"finalized\":true,\"nodes\":[{\"node_dependencies\":[],\"graph_dependencies\":[],\"operation\":{\"Input\":{\"Scalar\":\"bit\"}}}],\"output_node\":0}],\"main_graph\":0,\"graphs_names\":[[8079123,\"main\"]],\"nodes_names\":[[[0,0],\"input\"]],\"nodes_annotations\":[[[0,0],[\"AssociativeOperation\"]]],\"graphs_annotations\":[[0,[\"AssociativeOperation\"]]]}"} 9 | {"version":2,"data":"{\"finalized\":true,\"graphs\":[{\"finalized\":true,\"nodes\":[{\"node_dependencies\":[],\"graph_dependencies\":[],\"operation\":{\"Input\":{\"Scalar\":\"bit\"}}}],\"output_node\":0}],\"main_graph\":0,\"graphs_names\":[[0,\"main\"]],\"nodes_names\":[[[8079123,0],\"input\"]],\"nodes_annotations\":[[[0,0],[\"AssociativeOperation\"]]],\"graphs_annotations\":[[0,[\"AssociativeOperation\"]]]}"} 10 | {"version":2,"data":"{\"finalized\":true,\"graphs\":[{\"finalized\":true,\"nodes\":[{\"node_dependencies\":[],\"graph_dependencies\":[],\"operation\":{\"Input\":{\"Scalar\":\"bit\"}}}],\"output_node\":0}],\"main_graph\":0,\"graphs_names\":[[0,\"main\"]],\"nodes_names\":[[[0,8079123],\"input\"]],\"nodes_annotations\":[[[0,0],[\"AssociativeOperation\"]]],\"graphs_annotations\":[[0,[\"AssociativeOperation\"]]]}"} 11 | {"version":1,"data":"{\"finalized\":false,\"graphs\":[],\"main_graph\":null,\"graphs_names\":[],\"nodes_names\":[],\"nodes_annotations\":[],\"graphs_annotations\":[]}"} 12 | {"version":0,"data":"{\"finalized\":false,\"graphs\":[],\"main_graph\":null,\"graphs_names\":[],\"nodes_names\":[],\"nodes_annotations\":[],\"graphs_annotations\":[]}"} 13 | {"version":0,"data":"{\"unsupported_struct\":false,\"unsupported_graphs\":[],\"unsupported_main_graph\":null,\"unsupported_graphs_names\":[],\"unsupported_nodes_names\":[],\"unsupported_nodes_annotations\":[],\"unsupported_graphs_annotations\":[]}"} -------------------------------------------------------------------------------- /ciphercore-base/src/typed_value_operations.rs: -------------------------------------------------------------------------------- 1 | use crate::data_types::{ScalarType, Type}; 2 | use crate::errors::Result; 3 | use std::ops::Not; 4 | use std::option::Option; 5 | 6 | /// Converts `self` to a multi-dimensional array with the corresponding type. 7 | /// 8 | /// # Result 9 | /// 10 | /// Resulting multi-dimensional array with the corresponding type. 11 | /// 12 | /// # Examples 13 | /// 14 | /// ``` 15 | /// # use ciphercore_base::data_types::{INT32, array_type}; 16 | /// # use ciphercore_base::typed_value::TypedValue; 17 | /// # use ndarray::array; 18 | /// # use ciphercore_base::typed_value_operations::{TypedValueArrayOperations,ToNdarray}; 19 | /// let a = array![[-123, 123], [-456, 456]].into_dyn(); 20 | /// let v = TypedValue::from_ndarray(a, INT32).unwrap(); 21 | /// let a = ToNdarray::::to_ndarray(&v).unwrap(); 22 | /// assert_eq!(a, array![[-123i32, 123i32], [-456i32, 456i32]].into_dyn()); 23 | /// ``` 24 | pub trait ToNdarray { 25 | fn to_ndarray(&self) -> Result>; 26 | } 27 | 28 | pub trait TypedValueArrayOperations: 29 | ToNdarray 30 | + ToNdarray 31 | + ToNdarray 32 | + ToNdarray 33 | + ToNdarray 34 | + ToNdarray 35 | + ToNdarray 36 | + ToNdarray 37 | + ToNdarray 38 | + ToNdarray 39 | + ToNdarray 40 | { 41 | fn from_ndarray + Not + TryInto + Copy>( 42 | a: ndarray::ArrayD, 43 | st: ScalarType, 44 | ) -> Result; 45 | } 46 | pub enum FromVectorMode { 47 | Vector, 48 | Tuple, 49 | AutoDetection, 50 | } 51 | pub trait TypedValueOperations: TypedValueArrayOperations { 52 | fn get_type(&self) -> Type; 53 | fn is_equal(&self, other: &T) -> Result; 54 | fn to_vector(&self) -> Result>; 55 | fn from_vector(v: Vec, mode: FromVectorMode) -> Result; 56 | fn get(&self, index: usize) -> Result; 57 | fn get_sub_vector( 58 | &self, 59 | start_index_option: Option, 60 | end_index_option: Option, 61 | step_option: Option, 62 | ) -> Result; 63 | // Please note that call to this function copies all the data, as a result pushing N elements 64 | // has quadratic complexity. 65 | fn push(&mut self, to_push_element: T) -> Result<()>; 66 | fn insert(&mut self, to_insert_element: T, index: usize) -> Result<()>; 67 | fn extend(&mut self, to_extend_collection: T) -> Result<()>; 68 | fn remove(&mut self, index: usize) -> Result<()>; 69 | fn pop(&mut self, index: usize) -> Result; 70 | } 71 | pub(crate) fn get_helper(tv: &T, index: usize) -> Result 72 | where 73 | T: TypedValueOperations + Clone, 74 | { 75 | let v = tv.to_vector()?; 76 | if index >= v.len() { 77 | return Err(runtime_error!("Out of bound get")); 78 | } 79 | Ok(v[index].clone()) 80 | } 81 | 82 | fn from_vector_helper(tv: &T, v: Vec) -> Result 83 | where 84 | T: TypedValueOperations, 85 | { 86 | match tv.get_type() { 87 | Type::Tuple(_) | Type::NamedTuple(_) => T::from_vector(v, FromVectorMode::Tuple), 88 | Type::Vector(_, _) => T::from_vector(v, FromVectorMode::Vector), 89 | _ => Err(runtime_error!("Not a vector!")), 90 | } 91 | } 92 | 93 | pub(crate) fn get_sub_vector_helper( 94 | tv: &T, 95 | start_index_option: Option, 96 | end_index_option: Option, 97 | step_option: Option, 98 | ) -> Result 99 | where 100 | T: TypedValueOperations + Clone, 101 | { 102 | let mut v = tv.to_vector()?; 103 | let start_index = start_index_option.unwrap_or(0); 104 | let end_index = end_index_option.unwrap_or(v.len() - 1); 105 | let step = step_option.unwrap_or(1); 106 | if start_index >= v.len() || end_index >= v.len() { 107 | return Err(runtime_error!("Out of bound get")); 108 | } 109 | v.truncate(end_index); 110 | let new_v: Vec = v.iter().skip(start_index).step_by(step).cloned().collect(); 111 | from_vector_helper(tv, new_v) 112 | } 113 | pub(crate) fn insert_helper(tv: &T, to_insert_element: T, index: usize) -> Result 114 | where 115 | T: TypedValueOperations, 116 | { 117 | let mut v = tv.to_vector()?; 118 | if index > v.len() { 119 | return Err(runtime_error!("Out of bound insert")); 120 | } 121 | v.insert(index, to_insert_element); 122 | from_vector_helper(tv, v) 123 | } 124 | pub(crate) fn push_helper(tv: &T, to_insert_element: T) -> Result 125 | where 126 | T: TypedValueOperations, 127 | { 128 | insert_helper::(tv, to_insert_element, tv.to_vector()?.len()) 129 | } 130 | 131 | pub(crate) fn extend_helper(tv: &T, to_extend_collection: T) -> Result 132 | where 133 | T: TypedValueOperations, 134 | { 135 | let mut v = tv.to_vector()?; 136 | let v_to_extend = to_extend_collection.to_vector()?; 137 | v.extend(v_to_extend); 138 | from_vector_helper(tv, v) 139 | } 140 | 141 | pub(crate) fn remove_helper(tv: &T, index: usize) -> Result 142 | where 143 | T: TypedValueOperations, 144 | { 145 | let mut v = tv.to_vector()?; 146 | if index >= v.len() { 147 | return Err(runtime_error!("Out of bound insert")); 148 | } 149 | v.remove(index); 150 | from_vector_helper(tv, v) 151 | } 152 | 153 | pub(crate) fn pop_helper(tv: &T, index: usize) -> Result<(T, T)> 154 | where 155 | T: TypedValueOperations + Clone, 156 | { 157 | let ret1 = get_helper::(tv, index)?; 158 | let ret2 = remove_helper::(tv, index)?; 159 | Ok((ret1, ret2)) 160 | } 161 | -------------------------------------------------------------------------------- /ciphercore-base/src/typed_value_secret_shared.rs: -------------------------------------------------------------------------------- 1 | pub mod replicated_shares; 2 | use crate::errors::Result; 3 | use crate::random::PRNG; 4 | use crate::typed_value::TypedValue; 5 | use crate::typed_value_operations::TypedValueOperations; 6 | 7 | pub trait TypedValueSecretShared: TypedValueOperations { 8 | // It secret shares a TypedValue and returns one TypeValueSecretShared that can be converted to 9 | // a tuple of TypedValue and be used in local evaluation of MPC compiled graphs outside of runtime. 10 | fn secret_share_for_local_evaluation(tv: TypedValue, prng: &mut PRNG) -> Result; 11 | // It secret shares a TypedValue and returns a vector of TypedValueSecretShared, 12 | // one TypedValueSecretShared for each party. Parties can use these shares for evaluation of 13 | // compiled graphs in the runtime. 14 | fn secret_share_for_parties(tv: TypedValue, prng: &mut PRNG) -> Result>; 15 | fn to_tuple(&self) -> Result; 16 | fn from_tuple(tv: TypedValue) -> Result; 17 | fn reveal(&self) -> Result; 18 | } 19 | -------------------------------------------------------------------------------- /ciphercore-base/src/version.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::Result; 2 | use serde::{Deserialize, Serialize}; 3 | //DATA_VERSION represents the current version of serializable data types, i.e., Value and Context. We only bump this const when we make backwards-incompatible changes of Value and Context. 4 | pub(crate) const DATA_VERSION: u64 = 2; 5 | 6 | #[derive(Serialize, Deserialize)] 7 | pub(crate) struct VersionedData { 8 | version: u64, 9 | data: String, 10 | } 11 | 12 | impl VersionedData { 13 | pub(crate) fn create_versioned_data(ver: u64, serialized_data: String) -> Result { 14 | Ok(VersionedData { 15 | version: ver, 16 | data: serialized_data, 17 | }) 18 | } 19 | 20 | pub(crate) fn check_version(&self, version_req: u64) -> bool { 21 | self.version == version_req 22 | } 23 | 24 | pub(crate) fn get_data_string(&self) -> &str { 25 | &self.data 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ciphercore-utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ciphercore-utils" 3 | version = "0.3.0" 4 | authors = ["CipherMode Labs, Inc."] 5 | edition = "2021" 6 | description = "Convenience functions and wrappers used by CipherCore crates" 7 | license = "Apache-2.0" 8 | repository = "https://github.com/ciphermodelabs/ciphercore/" 9 | readme = "../README.md" 10 | keywords = ["data-sharing", "cryptography", "secure-computation", "secure-mpc", "privacy-enhancing"] 11 | categories = ["cryptography"] 12 | homepage = "https://www.ciphermode.com/" 13 | 14 | [dependencies] 15 | serde = { version = "1.0.140", features = ["derive", "rc"] } 16 | serde_json = "1.0.94" 17 | log = "0.4.18" 18 | chrono = "0.4.26" 19 | num-traits = "0.2.15" 20 | 21 | [features] 22 | default = [] 23 | -------------------------------------------------------------------------------- /ciphercore-utils/src/execute_main.rs: -------------------------------------------------------------------------------- 1 | //! Wrapper for a main function to run CipherCore 2 | use log::error; 3 | use std::fmt::Display; 4 | use std::process; 5 | use std::result::Result; 6 | 7 | /// Executes CipherCore code such that all the internal errors are properly formatted and logged. 8 | /// 9 | /// # Arguments 10 | /// 11 | /// `f` - closure without arguments containing CipherCore code and returning `Result<()>` 12 | pub fn execute_main(f: T) 13 | where 14 | T: FnOnce() -> Result<(), E> + std::panic::UnwindSafe, 15 | E: Display, 16 | { 17 | let result = std::panic::catch_unwind(|| { 18 | let result = f(); 19 | if let Err(e) = result { 20 | error!("CipherCore Error:\n{e}"); 21 | process::exit(1); 22 | } 23 | }); 24 | process_result(result); 25 | } 26 | 27 | #[doc(hidden)] 28 | pub fn extract_panic_message(e: Box) -> Option { 29 | match e.downcast::() { 30 | Ok(panic_msg) => Some(*panic_msg), 31 | Err(e) => match e.downcast::<&str>() { 32 | Ok(panic_msg) => Some((*panic_msg).to_owned()), 33 | Err(_) => None, 34 | }, 35 | } 36 | } 37 | 38 | #[doc(hidden)] 39 | pub fn process_result(result: std::thread::Result) { 40 | if let Err(e) = result { 41 | match extract_panic_message(e) { 42 | Some(panic_msg) => error!("panic: {}", panic_msg), 43 | None => error!("panic of unknown type"), 44 | } 45 | process::exit(1); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /ciphercore-utils/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod execute_main; 2 | pub mod test_utils; 3 | 4 | pub use log::debug as log_debug; 5 | 6 | #[doc(hidden)] 7 | #[macro_export] 8 | macro_rules! eprint_or_log { 9 | ($($x:tt)*) => { 10 | #[cfg(feature = "stderr-to-log")] 11 | $crate::log_debug!($($x)*); 12 | #[cfg(not(feature = "stderr-to-log"))] 13 | eprint!($($x)*); 14 | }; 15 | } 16 | 17 | #[doc(hidden)] 18 | #[macro_export] 19 | macro_rules! eprintln_or_log { 20 | ($($x:tt)*) => { 21 | #[cfg(feature = "stderr-to-log")] 22 | $crate::log_debug!($($x)*); 23 | #[cfg(not(feature = "stderr-to-log"))] 24 | eprintln!($($x)*); 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /ciphercore-utils/src/test_utils.rs: -------------------------------------------------------------------------------- 1 | use num_traits::AsPrimitive; 2 | 3 | pub fn pearson_correlation + std::ops::Mul>( 4 | predicted: &[T], 5 | labels: &[T], 6 | ) -> f64 { 7 | let mut exy = 0.0; 8 | let mut ex = 0.0; 9 | let mut ey = 0.0; 10 | let mut ex2 = 0.0; 11 | let mut ey2 = 0.0; 12 | let mut den = 0.0; 13 | for (x, y) in predicted.iter().cloned().zip(labels.iter().cloned()) { 14 | exy += (x * y).as_(); 15 | ex += x.as_(); 16 | ey += y.as_(); 17 | ex2 += (x * x).as_(); 18 | ey2 += (y * y).as_(); 19 | den += 1.0; 20 | } 21 | exy /= den; 22 | ex /= den; 23 | ey /= den; 24 | ex2 /= den; 25 | ey2 /= den; 26 | 27 | (exy - ex * ey) / ((ex2 - ex * ex) * (ey2 - ey * ey)).sqrt() 28 | } 29 | 30 | #[cfg(test)] 31 | mod tests { 32 | use super::*; 33 | #[test] 34 | fn test_pearson_correlation() { 35 | let predicted = vec![1i64, 2, 3, 4, 5]; 36 | let labels = vec![1, 2, 3, 4, 5]; 37 | let corr = pearson_correlation(&predicted, &labels); 38 | assert_eq!(corr, 1.0); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ciphercore-wrappers/python/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | project(CipherCorePython) 4 | 5 | add_subdirectory(external/pybind11) 6 | 7 | find_package(OpenSSL REQUIRED) 8 | 9 | add_custom_target( 10 | ciphercore 11 | COMMAND cargo build --release 12 | COMMAND mkdir -p ${CMAKE_CURRENT_SOURCE_DIR}/lib && cp target/release/libcadapter.a ${CMAKE_CURRENT_SOURCE_DIR}/lib/ 13 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../../" 14 | ) 15 | 16 | set(HEADER_DEPENDENCIES include/) 17 | set(LIBRARY_DEPENDENCIES ${CMAKE_CURRENT_SOURCE_DIR}/lib/libcadapter.a OpenSSL::Crypto OpenSSL::SSL) 18 | add_library(ciphercore_lib INTERFACE) 19 | add_dependencies(ciphercore_lib ciphercore) 20 | target_include_directories(ciphercore_lib INTERFACE ${HEADER_DEPENDENCIES}) 21 | target_link_libraries(ciphercore_lib INTERFACE ${LIBRARY_DEPENDENCIES}) 22 | target_compile_options(ciphercore_lib INTERFACE -O3 -Wall -std=c++17 -march=native -g -Wno-return-type-c-linkage) 23 | 24 | pybind11_add_module(ciphercore_native src/ciphercore_native.cpp) 25 | target_link_libraries("ciphercore_native" PRIVATE ciphercore_lib) 26 | -------------------------------------------------------------------------------- /ciphercore-wrappers/python/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ciphercore-pywrapper" 3 | version = "0.3.3" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | ciphercore-base = { version = "0.3.1", features=["py-binding"] } 11 | serde = { version = "1.0.140", features = ["derive", "rc"] } 12 | serde_json = "1.0.94" 13 | ndarray = "0.15.1" 14 | numpy = "0.17.2" 15 | pywrapper-macro = { version = "0.3.1" } 16 | 17 | [dependencies.pyo3] 18 | version = "0.17.1" 19 | features = ["extension-module"] 20 | 21 | [features] 22 | default = [] 23 | -------------------------------------------------------------------------------- /ciphercore-wrappers/python/Example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import ciphercore as cc" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 2, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "context = cc.create_context()\n", 19 | "graph = context.create_graph()" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": 3, 25 | "metadata": {}, 26 | "outputs": [ 27 | { 28 | "data": { 29 | "text/plain": [ 30 | "Graph[num_nodes=0]" 31 | ] 32 | }, 33 | "execution_count": 3, 34 | "metadata": {}, 35 | "output_type": "execute_result" 36 | } 37 | ], 38 | "source": [ 39 | "graph" 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": 4, 45 | "metadata": {}, 46 | "outputs": [ 47 | { 48 | "data": { 49 | "text/plain": [ 50 | "{\"version\":1,\"data\":\"{\\\"finalized\\\":false,\\\"graphs\\\":[{\\\"finalized\\\":false,\\\"nodes\\\":[],\\\"output_node\\\":null}],\\\"main_graph\\\":null,\\\"graphs_names\\\":[],\\\"nodes_names\\\":[],\\\"nodes_annotations\\\":[],\\\"graphs_annotations\\\":[]}\"}" 51 | ] 52 | }, 53 | "execution_count": 4, 54 | "metadata": {}, 55 | "output_type": "execute_result" 56 | } 57 | ], 58 | "source": [ 59 | "context" 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": 5, 65 | "metadata": {}, 66 | "outputs": [], 67 | "source": [ 68 | "A = graph.input(cc.array_type([256, 128], cc.INT32))\n", 69 | "B = graph.input(cc.array_type([128, 64], cc.INT32))\n", 70 | "c = graph.input(cc.array_type([64], cc.INT32))" 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": 6, 76 | "metadata": {}, 77 | "outputs": [], 78 | "source": [ 79 | "res = A @ B + c" 80 | ] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "execution_count": 7, 85 | "metadata": {}, 86 | "outputs": [ 87 | { 88 | "data": { 89 | "text/plain": [ 90 | "Node[type=i32[256, 64]]" 91 | ] 92 | }, 93 | "execution_count": 7, 94 | "metadata": {}, 95 | "output_type": "execute_result" 96 | } 97 | ], 98 | "source": [ 99 | "res" 100 | ] 101 | }, 102 | { 103 | "cell_type": "code", 104 | "execution_count": 8, 105 | "metadata": {}, 106 | "outputs": [ 107 | { 108 | "data": { 109 | "text/plain": [ 110 | "{\"version\":1,\"data\":\"{\\\"finalized\\\":true,\\\"graphs\\\":[{\\\"finalized\\\":true,\\\"nodes\\\":[{\\\"node_dependencies\\\":[],\\\"graph_dependencies\\\":[],\\\"operation\\\":{\\\"Input\\\":{\\\"Array\\\":[[256,128],{\\\"signed\\\":true,\\\"modulus\\\":4294967296}]}}},{\\\"node_dependencies\\\":[],\\\"graph_dependencies\\\":[],\\\"operation\\\":{\\\"Input\\\":{\\\"Array\\\":[[128,64],{\\\"signed\\\":true,\\\"modulus\\\":4294967296}]}}},{\\\"node_dependencies\\\":[],\\\"graph_dependencies\\\":[],\\\"operation\\\":{\\\"Input\\\":{\\\"Array\\\":[[64],{\\\"signed\\\":true,\\\"modulus\\\":4294967296}]}}},{\\\"node_dependencies\\\":[0,1],\\\"graph_dependencies\\\":[],\\\"operation\\\":\\\"Matmul\\\"},{\\\"node_dependencies\\\":[3,2],\\\"graph_dependencies\\\":[],\\\"operation\\\":\\\"Add\\\"}],\\\"output_node\\\":4}],\\\"main_graph\\\":0,\\\"graphs_names\\\":[],\\\"nodes_names\\\":[],\\\"nodes_annotations\\\":[],\\\"graphs_annotations\\\":[]}\"}" 111 | ] 112 | }, 113 | "execution_count": 8, 114 | "metadata": {}, 115 | "output_type": "execute_result" 116 | } 117 | ], 118 | "source": [ 119 | "res.set_as_output()\n", 120 | "graph.finalize()\n", 121 | "graph.set_as_main()\n", 122 | "context.finalize()" 123 | ] 124 | }, 125 | { 126 | "cell_type": "code", 127 | "execution_count": null, 128 | "metadata": {}, 129 | "outputs": [], 130 | "source": [] 131 | } 132 | ], 133 | "metadata": { 134 | "kernelspec": { 135 | "display_name": "Python 3", 136 | "language": "python", 137 | "name": "python3" 138 | }, 139 | "language_info": { 140 | "codemirror_mode": { 141 | "name": "ipython", 142 | "version": 3 143 | }, 144 | "file_extension": ".py", 145 | "mimetype": "text/x-python", 146 | "name": "python", 147 | "nbconvert_exporter": "python", 148 | "pygments_lexer": "ipython3", 149 | "version": "3.10.1" 150 | } 151 | }, 152 | "nbformat": 4, 153 | "nbformat_minor": 4 154 | } 155 | -------------------------------------------------------------------------------- /ciphercore-wrappers/python/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include Cargo.toml 2 | recursive-include src * 3 | -------------------------------------------------------------------------------- /ciphercore-wrappers/python/README.md: -------------------------------------------------------------------------------- 1 | This is the Python wrapper for CipherCore base library. 2 | It currently supports building CipherCore computation graphs. 3 | See https://github.com/ciphermodelabs/ciphercore/ for more details. 4 | 5 | Example usage: 6 | 7 | ```python 8 | import ciphercore as cc 9 | 10 | c = cc.create_context() 11 | with c: 12 | g = c.create_graph() 13 | with g: 14 | a = g.input(cc.array_type([10, 20, 30], cc.INT32)) 15 | s = a[..., 2:3] 16 | b = s.sum([0, 2]) 17 | b.set_as_output() 18 | print(g) 19 | g.set_as_main() 20 | print(c) 21 | 22 | ``` 23 | -------------------------------------------------------------------------------- /ciphercore-wrappers/python/a.py: -------------------------------------------------------------------------------- 1 | import ciphercore as cc 2 | 3 | c = cc.create_context() 4 | print(c.get_graphs()) 5 | with c: 6 | g = c.create_graph() 7 | with g: 8 | a = g.input(cc.array_type([10, 20, 30], cc.INT32)) 9 | b = a.sum([0, 2]) 10 | s = a[..., 2:3] 11 | n = g.create_named_tuple([('boda',b)]) 12 | s.set_as_output() 13 | print(a.get_operation()) 14 | print(s) 15 | print(c.get_graphs()) 16 | print(g.get_nodes()) 17 | print(b.get_operation()) 18 | print(b.get_global_id()) 19 | print(n.get_operation()) 20 | print(s.get_operation()) 21 | print(g) 22 | g.set_as_main() 23 | print(c) 24 | -------------------------------------------------------------------------------- /ciphercore-wrappers/python/build_wheels.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | 4 | curl https://sh.rustup.rs -sSf | sh -s -- -y 5 | export PATH="$HOME/.cargo/bin:$PATH" 6 | 7 | for PYBIN in /opt/python/{cp37-cp37m,cp38-cp38,cp39-cp39,cp310-cp310}/bin; do 8 | export PYTHON_SYS_EXECUTABLE="$PYBIN/python" 9 | 10 | "${PYBIN}/pip" install -U setuptools-rust==0.11.3 11 | "${PYBIN}/python" setup.py bdist_wheel 12 | rm -rf build/* 13 | done 14 | 15 | for whl in dist/*.whl; do 16 | auditwheel repair "$whl" -w dist/ 17 | done 18 | -------------------------------------------------------------------------------- /ciphercore-wrappers/python/include/CResults.h: -------------------------------------------------------------------------------- 1 | ../../C-wrapper/CResults.h -------------------------------------------------------------------------------- /ciphercore-wrappers/python/include/CStr.h: -------------------------------------------------------------------------------- 1 | ../../C-wrapper/CStr.h -------------------------------------------------------------------------------- /ciphercore-wrappers/python/include/CVec.h: -------------------------------------------------------------------------------- 1 | ../../C-wrapper/CVec.h -------------------------------------------------------------------------------- /ciphercore-wrappers/python/include/Error.h: -------------------------------------------------------------------------------- 1 | ../../C-wrapper/Error.h -------------------------------------------------------------------------------- /ciphercore-wrappers/python/include/ciphercore_adapters.h: -------------------------------------------------------------------------------- 1 | ../../C-wrapper/ciphercore_adapters.h -------------------------------------------------------------------------------- /ciphercore-wrappers/python/include/ciphercore_auxiliary_structs.h: -------------------------------------------------------------------------------- 1 | ../../C-wrapper/ciphercore_auxiliary_structs.h -------------------------------------------------------------------------------- /ciphercore-wrappers/python/include/ciphercore_context.h: -------------------------------------------------------------------------------- 1 | ../../C-wrapper/ciphercore_context.h -------------------------------------------------------------------------------- /ciphercore-wrappers/python/include/ciphercore_data_types.h: -------------------------------------------------------------------------------- 1 | ../../C-wrapper/ciphercore_data_types.h -------------------------------------------------------------------------------- /ciphercore-wrappers/python/include/ciphercore_graph.h: -------------------------------------------------------------------------------- 1 | ../../C-wrapper/ciphercore_graph.h -------------------------------------------------------------------------------- /ciphercore-wrappers/python/include/ciphercore_node.h: -------------------------------------------------------------------------------- 1 | ../../C-wrapper/ciphercore_node.h -------------------------------------------------------------------------------- /ciphercore-wrappers/python/include/ciphercore_structs.h: -------------------------------------------------------------------------------- 1 | ../../C-wrapper/ciphercore_structs.h -------------------------------------------------------------------------------- /ciphercore-wrappers/python/prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | if [[ $OSTYPE == 'darwin'* ]]; then 5 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 6 | brew install openssl 7 | OPENSSL_DIR=$(brew --prefix openssl) 8 | ln -s /usr/local/opt/openssl@3/include/openssl /usr/local/include 9 | ln -s /usr/local/opt/openssl@3/lib/libcrypto.a /usr/local/lib 10 | ln -s /usr/local/opt/openssl@3/lib/libssl.a /usr/local/lib 11 | else 12 | yum install -y openssl openssl-devel curl 13 | fi 14 | curl https://sh.rustup.rs -sSf > install_cargo.sh 15 | sh install_cargo.sh -y 16 | source $HOME/.cargo/env 17 | cargo build --release 18 | mkdir -p ciphercore-wrappers/python/lib 19 | cp -f target/release/libcadapter.a ciphercore-wrappers/python/lib/libcadapter.a 20 | 21 | -------------------------------------------------------------------------------- /ciphercore-wrappers/python/py/ciphercore/__init__.py: -------------------------------------------------------------------------------- 1 | from .internal import (BIT, 2 | INT8, 3 | INT16, 4 | INT32, 5 | INT64, 6 | UINT8, 7 | UINT16, 8 | UINT32, 9 | UINT64, 10 | Type, 11 | ScalarType, 12 | array_type, 13 | scalar_type, 14 | tuple_type, 15 | vector_type, 16 | named_tuple_type, 17 | create_context, 18 | Context, 19 | Graph, 20 | Node, 21 | CustomOperation, 22 | TypedValue, 23 | Value) 24 | -------------------------------------------------------------------------------- /ciphercore-wrappers/python/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "wheel", "setuptools-rust"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [tool.black] 6 | target-version = ['py37'] 7 | line-length = 100 8 | -------------------------------------------------------------------------------- /ciphercore-wrappers/python/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages, setup 2 | from setuptools_rust import Binding, RustExtension 3 | 4 | __version__ = "0.3.0" 5 | 6 | with open('../../README.md', 'r') as f: 7 | long_description = f.read() 8 | 9 | setup( 10 | name="ciphercore", 11 | version=__version__, 12 | author="CipherMode", 13 | author_email="info@ciphermode.com", 14 | url="https://github.com/ciphermodelabs/ciphercore/", 15 | description="Python wrapper for CipherCore base library (graph building part)", 16 | long_description=long_description, 17 | long_description_content_type='text/markdown', 18 | license="Apache 2.0", 19 | rust_extensions=[RustExtension("ciphercore_internal", binding=Binding.PyO3, debug=False)], 20 | extras_require={"test": ["pytest", "numpy"], "dev": ["numpy"]}, 21 | zip_safe=False, 22 | python_requires=">=3.7", 23 | package_dir={"": "py"}, 24 | packages=find_packages(where="py"), 25 | include_package_data=True, 26 | ) 27 | -------------------------------------------------------------------------------- /ciphercore-wrappers/python/src/lib.rs: -------------------------------------------------------------------------------- 1 | use ciphercore_base::custom_ops::PyBindingCustomOperation; 2 | use ciphercore_base::data_types::{ 3 | py_binding_array_type, py_binding_named_tuple_type, py_binding_scalar_type, 4 | py_binding_tuple_type, py_binding_vector_type, 5 | }; 6 | use ciphercore_base::data_types::{ 7 | PyBindingScalarType, PyBindingType, BIT, INT16, INT32, INT64, INT8, UINT16, UINT32, UINT64, 8 | UINT8, 9 | }; 10 | use ciphercore_base::data_values::PyBindingValue; 11 | use ciphercore_base::graphs::{ 12 | py_binding_create_context, PyBindingContext, PyBindingGraph, PyBindingJoinType, PyBindingNode, 13 | PyBindingShardConfig, PyBindingSliceElement, 14 | }; 15 | use ciphercore_base::typed_value::PyBindingTypedValue; 16 | use numpy::PyReadonlyArrayDyn; 17 | use pyo3::prelude::{pymodule, wrap_pyfunction, PyModule, PyResult, Python}; 18 | 19 | #[pymodule] 20 | fn ciphercore_internal(_py: Python<'_>, m: &PyModule) -> PyResult<()> { 21 | macro_rules! call_serialize_to_str { 22 | ($n:ident, $t:ident, $v:expr) => { 23 | #[pyo3::pyfunction] 24 | fn $n(x: PyReadonlyArrayDyn<$t>) -> PyResult { 25 | Ok(rust::serialize_to_str(x, $v)?) 26 | } 27 | m.add_function(wrap_pyfunction!($n, m)?).unwrap(); 28 | }; 29 | } 30 | 31 | call_serialize_to_str!(serialize_to_str_uint64, u64, UINT64); 32 | call_serialize_to_str!(serialize_to_str_uint32, u32, UINT32); 33 | call_serialize_to_str!(serialize_to_str_uint16, u16, UINT16); 34 | call_serialize_to_str!(serialize_to_str_uint8, u8, UINT8); 35 | call_serialize_to_str!(serialize_to_str_int64, i64, INT64); 36 | call_serialize_to_str!(serialize_to_str_int32, i32, INT32); 37 | call_serialize_to_str!(serialize_to_str_int16, i16, INT16); 38 | call_serialize_to_str!(serialize_to_str_int8, i8, INT8); 39 | call_serialize_to_str!(serialize_to_str_bool, bool, BIT); 40 | 41 | m.add_function(wrap_pyfunction!(py_binding_create_context, m)?)?; 42 | m.add_function(wrap_pyfunction!(py_binding_scalar_type, m)?)?; 43 | m.add_function(wrap_pyfunction!(py_binding_tuple_type, m)?)?; 44 | m.add_function(wrap_pyfunction!(py_binding_array_type, m)?)?; 45 | m.add_function(wrap_pyfunction!(py_binding_vector_type, m)?)?; 46 | m.add_function(wrap_pyfunction!(py_binding_named_tuple_type, m)?)?; 47 | 48 | m.add("BIT", PyBindingScalarType { inner: BIT })?; 49 | m.add("UINT8", PyBindingScalarType { inner: UINT8 })?; 50 | m.add("INT8", PyBindingScalarType { inner: INT8 })?; 51 | m.add("UINT16", PyBindingScalarType { inner: UINT16 })?; 52 | m.add("INT16", PyBindingScalarType { inner: INT16 })?; 53 | m.add("UINT32", PyBindingScalarType { inner: UINT32 })?; 54 | m.add("INT32", PyBindingScalarType { inner: INT32 })?; 55 | m.add("UINT64", PyBindingScalarType { inner: UINT64 })?; 56 | m.add("INT64", PyBindingScalarType { inner: INT64 })?; 57 | m.add_class::()?; 58 | m.add_class::()?; 59 | m.add_class::()?; 60 | m.add_class::()?; 61 | m.add_class::()?; 62 | m.add_class::()?; 63 | m.add_class::()?; 64 | m.add_class::()?; 65 | m.add_class::()?; 66 | m.add_class::()?; 67 | m.add_class::()?; 68 | Ok(()) 69 | } 70 | 71 | mod rust { 72 | use std::ops::Not; 73 | 74 | use ciphercore_base::data_types::ScalarType; 75 | use ciphercore_base::errors::Result; 76 | use ciphercore_base::typed_value::TypedValue; 77 | use ciphercore_base::typed_value_operations::TypedValueArrayOperations; 78 | use numpy::PyReadonlyArrayDyn; 79 | 80 | pub(crate) fn serialize_to_str< 81 | T: numpy::Element + TryInto + Not + TryInto + Copy, 82 | >( 83 | x: PyReadonlyArrayDyn, 84 | st: ScalarType, 85 | ) -> Result { 86 | let array = x.as_array(); 87 | let tv = TypedValue::from_ndarray(array.to_owned(), st)?; 88 | Ok(serde_json::to_string(&tv)?) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /ciphercore-wrappers/python/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | 6 | python3 -m venv /tmp/test-env 7 | source /tmp/test-env/bin/activate 8 | 9 | pip install setuptools_rust 10 | pip uninstall ciphercore 11 | 12 | python setup.py install 13 | python a.py 14 | 15 | deactivate 16 | -------------------------------------------------------------------------------- /ciphercore-wrappers/pywrapper-macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pywrapper-macro" 3 | authors = ["CipherMode Labs, Inc."] 4 | version = "0.3.1" 5 | edition = "2021" 6 | description = "Macros needed to create a Python wrapper for CipherCore" 7 | license = "Apache-2.0" 8 | repository = "https://github.com/ciphermodelabs/ciphercore/" 9 | readme = "../../README.md" 10 | keywords = ["data-sharing", "cryptography", "secure-computation", "secure-mpc", "privacy-enhancing"] 11 | categories = ["cryptography"] 12 | homepage = "https://www.ciphermode.com/" 13 | 14 | [lib] 15 | proc-macro = true 16 | 17 | [dependencies] 18 | syn = { version = "1.0.99", features = ["full"] } 19 | quote = "1.0.28" 20 | proc-macro2 = "1.0.60" 21 | lazy_static = "1.4.0" 22 | -------------------------------------------------------------------------------- /generate_c_header.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cbindgen --config cbindgen.toml --crate cadapter --output ciphercore-wrappers/C-wrapper/raw.h 3 | -------------------------------------------------------------------------------- /reference/images/ciphercore_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ciphermodelabs/ciphercore/cd4a2cd8e4e24392a6390ba2364a6387089c8e18/reference/images/ciphercore_architecture.png -------------------------------------------------------------------------------- /reference/images/manual_graph.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | %3 11 | 12 | 13 | cluster0 14 | 15 | 16 | 17 | 18 | Input0_0 19 | 20 | Input 21 | i32 22 | 23 | 24 | 25 | Add2_0 26 | 27 | Add 28 | i32 29 | 30 | 31 | 32 | Input0_0->Add2_0 33 | 34 | 35 | 36 | 37 | 38 | Input1_0 39 | 40 | Input 41 | i32 42 | 43 | 44 | 45 | Input1_0->Add2_0 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /reference/images/matmul.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | %3 11 | 12 | 13 | cluster0 14 | 15 | 16 | 17 | 18 | Input0_0 19 | 20 | Input 21 | i32[2, 3] 22 | 23 | 24 | 25 | Matmul2_0 26 | 27 | Matmul 28 | i32[2, 4] 29 | 30 | 31 | 32 | Input0_0->Matmul2_0 33 | 34 | 35 | 36 | 37 | 38 | Input1_0 39 | 40 | Input 41 | i32[3, 4] 42 | 43 | 44 | 45 | Input1_0->Matmul2_0 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /reference/images/millionaires.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | %3 11 | 12 | 13 | cluster0 14 | 15 | 16 | 17 | 18 | Input0_0 19 | 20 | Input 21 | b[32] 22 | 23 | 24 | 25 | GreaterThan2_0 26 | 27 | GreaterThan 28 | b 29 | 30 | 31 | 32 | Input0_0->GreaterThan2_0 33 | 34 | 35 | 36 | 37 | 38 | Input1_0 39 | 40 | Input 41 | b[32] 42 | 43 | 44 | 45 | Input1_0->GreaterThan2_0 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /reference/images/tutorial_graph_plain.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | %3 11 | 12 | 13 | cluster0 14 | 15 | 16 | 17 | 18 | Input0_0 19 | 20 | Input 21 | i64[5, 5] 22 | 23 | 24 | 25 | Matmul2_0 26 | 27 | Matmul 28 | i64[5, 5] 29 | 30 | 31 | 32 | Input0_0->Matmul2_0 33 | 34 | 35 | 36 | 37 | 38 | Input1_0 39 | 40 | Input 41 | i64[5, 5] 42 | 43 | 44 | 45 | Input1_0->Matmul2_0 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /runtime/example/data/two_millionaires/party0/input.txt: -------------------------------------------------------------------------------- 1 | [{"kind":"scalar","type":"u32","value":3000},{"kind":"scalar","type":"u32","value":31415}] 2 | -------------------------------------------------------------------------------- /runtime/example/data/two_millionaires/party1/input.txt: -------------------------------------------------------------------------------- 1 | [{"kind":"scalar","type":"u32","value":31415},{"kind":"scalar","type":"u32","value":2000}] 2 | -------------------------------------------------------------------------------- /runtime/example/data/two_millionaires/party2/input.txt: -------------------------------------------------------------------------------- 1 | [{"kind":"scalar","type":"u32","value":314159},{"kind":"scalar","type":"u32","value":31415}] 2 | -------------------------------------------------------------------------------- /runtime/example/scripts/do_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -xe 4 | 5 | echo "Creating workdir..." 6 | export WORK_DIR="$1" 7 | mkdir -p "$WORK_DIR" 8 | mkdir -p "$WORK_DIR"/data 9 | 10 | echo "Pulling the docker..." 11 | docker login -u runtimecm 12 | docker pull ciphermodelabs/runtime_example:latest 13 | 14 | echo "Generating tls certificates..." 15 | docker run --rm -u $(id -u):$(id -g) -v "$WORK_DIR":/mnt/external ciphermodelabs/runtime_example:latest /usr/local/ciphercore/run.sh generate_certs certificates/ localhost 16 | 17 | echo "Copying the median example data..." 18 | cp -rf runtime/example/data/two_millionaires/* "$WORK_DIR"/data/ 19 | 20 | echo "Starting data nodes..." 21 | sh runtime/example/scripts/run_data_nodes.sh || sh runtime/example/scripts/tear_down.sh 22 | 23 | echo "Starting compute nodes..." 24 | sh runtime/example/scripts/run_compute_nodes.sh || sh runtime/example/scripts/tear_down.sh 25 | 26 | echo "Performing computation..." 27 | sh runtime/example/scripts/run_orchestrator.sh || sh runtime/example/scripts/tear_down.sh 28 | 29 | echo "Releasing all processes..." 30 | sh runtime/example/scripts/tear_down.sh 31 | 32 | echo "Done." 33 | -------------------------------------------------------------------------------- /runtime/example/scripts/run_compute_nodes.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -xe 4 | 5 | docker run -d --network host -it --name compute_node0 -v "$WORK_DIR":/mnt/external --rm ciphermodelabs/runtime_example:latest /usr/local/ciphercore/run.sh compute_node 0.0.0.0 4203 127.0.0.1 4201 127.0.0.1 4202 certificates/ca.pem certificates/party0/cert.pem certificates/party0/key.pem localhost 6 | docker run -d --network host -it --name compute_node1 -v "$WORK_DIR":/mnt/external --rm ciphermodelabs/runtime_example:latest /usr/local/ciphercore/run.sh compute_node 0.0.0.0 4213 127.0.0.1 4211 127.0.0.1 4212 certificates/ca.pem certificates/party1/cert.pem certificates/party1/key.pem localhost 7 | docker run -d --network host -it --name compute_node2 -v "$WORK_DIR":/mnt/external --rm ciphermodelabs/runtime_example:latest /usr/local/ciphercore/run.sh compute_node 0.0.0.0 4223 127.0.0.1 4221 127.0.0.1 4222 certificates/ca.pem certificates/party2/cert.pem certificates/party2/key.pem localhost 8 | -------------------------------------------------------------------------------- /runtime/example/scripts/run_data_nodes.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -xe 4 | 5 | docker run -d --network host -it --name data_node0 -v "$WORK_DIR":/mnt/external --rm ciphermodelabs/runtime_example:latest /usr/local/ciphercore/run.sh data_node 0.0.0.0 4201 4202 data/party0/input.txt certificates/ca.pem certificates/party0/data_cert.pem certificates/party0/data_key.pem 6 | docker run -d --network host -it --name data_node1 -v "$WORK_DIR":/mnt/external --rm ciphermodelabs/runtime_example:latest /usr/local/ciphercore/run.sh data_node 0.0.0.0 4211 4212 data/party1/input.txt certificates/ca.pem certificates/party1/data_cert.pem certificates/party1/data_key.pem 7 | docker run -d --network host -it --name data_node2 -v "$WORK_DIR":/mnt/external --rm ciphermodelabs/runtime_example:latest /usr/local/ciphercore/run.sh data_node 0.0.0.0 4221 4222 data/party2/input.txt certificates/ca.pem certificates/party2/data_cert.pem certificates/party2/data_key.pem 8 | -------------------------------------------------------------------------------- /runtime/example/scripts/run_orchestrator.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -xe 4 | 5 | docker run --network host -it --name do -v "$WORK_DIR":/mnt/external --rm ciphermodelabs/runtime_example:latest /usr/local/ciphercore/run.sh orchestrator data/graph.json 127.0.0.1 4203 127.0.0.1 4213 127.0.0.1 4223 certificates/ca.pem certificates/orchestrator_cert.pem certificates/orchestrator_key.pem 6 | 7 | echo "Party 0 got result:" 8 | docker logs data_node0 --tail 20 9 | echo "Party 1 got result:" 10 | docker logs data_node1 --tail 20 11 | echo "Party 2 got result:" 12 | docker logs data_node2 --tail 20 13 | -------------------------------------------------------------------------------- /runtime/example/scripts/tear_down.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -xe 4 | 5 | docker kill data_node0 || true 6 | docker kill data_node1 || true 7 | docker kill data_node2 || true 8 | 9 | docker kill compute_node0 || true 10 | docker kill compute_node1 || true 11 | docker kill compute_node2 || true 12 | -------------------------------------------------------------------------------- /runtime/proto/data.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | import "value.proto"; 4 | 5 | package ciphercore; 6 | 7 | service DataManagerService { 8 | rpc GetValue(GetValueRequest) returns (GetValueResponse) {} 9 | } 10 | 11 | message GetValueRequest { 12 | bytes key = 1; 13 | } 14 | 15 | message GetValueResponse { 16 | enum GetValueStatus { 17 | OK = 0; 18 | ERROR = 1; 19 | } 20 | GetValueStatus status = 1; 21 | string error = 2; 22 | repeated TypedValue typed_value = 3; 23 | } 24 | 25 | message DataRequest { 26 | bytes key = 1; 27 | } -------------------------------------------------------------------------------- /runtime/proto/party.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | import "value.proto"; 4 | 5 | package ciphercore; 6 | 7 | service PartyService { 8 | rpc RegisterGraph(RegisterGraphRequest) returns (RegisterGraphResponse) {} 9 | rpc CreateSession(CreateSessionRequest) returns (CreateSessionResponse) {} 10 | rpc FinishSession(FinishSessionRequest) returns (FinishSessionResponse) {} 11 | rpc ListSessions(SessionListRequest) returns (SessionListResponse) {} 12 | rpc SendValue(stream SendValueRequest) returns (stream SendValueResponse) {} 13 | } 14 | 15 | message SendValueRequest { 16 | uint64 node_id = 1; 17 | Value data = 2; 18 | uint64 party_id = 3; 19 | string session_id = 4; 20 | } 21 | 22 | message SendValueResponse { 23 | enum SendValueStatus { 24 | SUCCESS = 0; 25 | DUPLICATE = 1; 26 | UNKNOWN_NODE = 2; 27 | UNKNOWN_SESSION = 3; 28 | WRONG_PARTY = 4; 29 | UNKNOWN_ERROR = 5; 30 | } 31 | SendValueStatus result = 1; 32 | string message = 2; 33 | uint64 node_id = 3; 34 | } 35 | 36 | message RegisterGraphRequest { 37 | // Should we serialize it in a more compact way? 38 | bytes graph_json = 1; 39 | string graph_id = 2; 40 | } 41 | 42 | message RegisterGraphResponse { 43 | enum RegisterGraphStatus { 44 | SUCCESS = 0; 45 | DUPLICATE = 1; 46 | GENERIC_FAILURE = 2; 47 | } 48 | RegisterGraphStatus result = 1; 49 | string message = 2; 50 | } 51 | 52 | message CreateSessionRequest { 53 | string session_id = 1; 54 | string graph_id = 2; 55 | uint64 party_id = 3; 56 | // Application-specific settings for input data for this session. 57 | bytes data_settings = 4; 58 | repeated Address party_address = 5; 59 | } 60 | 61 | message Address { 62 | string address = 1; 63 | uint32 port = 2; 64 | } 65 | 66 | message CreateSessionResponse { 67 | enum CreateSessionStatus { 68 | SUCCESS = 0; 69 | MISSING_GRAPH = 1; 70 | DUPLICATE_SESSION = 2; 71 | INVALID_CONFIGURATION = 3; 72 | GENERIC_FAILURE = 4; 73 | } 74 | CreateSessionStatus result = 1; 75 | string message = 2; 76 | } 77 | 78 | message FinishSessionRequest { 79 | string session_id = 1; 80 | } 81 | 82 | message FinishSessionResponse { 83 | enum FinishSessionStatus { 84 | SUCCESS = 0; 85 | MISSING_SESSION = 1; 86 | GENERIC_FAILURE = 2; 87 | } 88 | FinishSessionStatus result = 1; 89 | string message = 2; 90 | } 91 | 92 | message SessionListRequest { 93 | 94 | } 95 | 96 | message SessionListResponse { 97 | enum SessionStatus { 98 | CREATED = 0; 99 | RUNNING = 1; 100 | FINISHED = 2; 101 | FAILED = 3; 102 | } 103 | message SessionEntry { 104 | string session_id = 1; 105 | SessionStatus status = 2; 106 | } 107 | repeated SessionEntry session = 1; 108 | } -------------------------------------------------------------------------------- /runtime/proto/results.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | import "value.proto"; 4 | 5 | package ciphercore; 6 | 7 | service ResultProviderService { 8 | rpc ProvideResult(ProvideResultRequest) returns (ProvideResultResponse) {} 9 | } 10 | 11 | message ProvideResultRequest { 12 | string key = 1; 13 | string error = 2; 14 | TypedValue typed_value = 3; 15 | } 16 | 17 | message ProvideResultResponse { 18 | enum ProvideResultStatus { 19 | OK = 0; 20 | ERROR = 1; 21 | } 22 | ProvideResultStatus status = 1; 23 | string error = 2; 24 | } -------------------------------------------------------------------------------- /runtime/proto/value.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package ciphercore; 4 | 5 | message Value { 6 | oneof value { 7 | BytesValue bytes = 1; 8 | VectorValue vector = 2; 9 | } 10 | } 11 | 12 | message BytesValue { 13 | bytes data = 1; 14 | } 15 | 16 | message VectorValue { 17 | repeated Value value = 1; 18 | } 19 | 20 | message TypedValue { 21 | Value value = 1; 22 | string type_json = 2; 23 | } --------------------------------------------------------------------------------