chore(ci): add erc20 tests

This commit is contained in:
Agnes Leroy
2024-11-15 15:48:18 +01:00
committed by Agnès Leroy
parent 81e11a6d70
commit 832703a46a
11 changed files with 868 additions and 0 deletions

View File

@@ -0,0 +1,146 @@
name: AWS Long Run Tests on GPU
env:
CARGO_TERM_COLOR: always
ACTION_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
RUSTFLAGS: "-C target-cpu=native"
RUST_BACKTRACE: "full"
RUST_MIN_STACK: "8388608"
SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }}
SLACK_ICON: https://pbs.twimg.com/profile_images/1274014582265298945/OjBKP9kn_400x400.png
SLACK_USERNAME: ${{ secrets.BOT_USERNAME }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
on:
# Allows you to run this workflow manually from the Actions tab as an alternative.
workflow_dispatch:
schedule:
# Weekly tests will be triggered each Friday at 1a.m.
- cron: '0 1 * * FRI'
jobs:
setup-instance:
name: Setup instance (gpu-tests)
if: github.event_name != 'schedule' ||
(github.event_name == 'schedule' && github.repository == 'zama-ai/tfhe-rs')
runs-on: ubuntu-latest
outputs:
runner-name: ${{ steps.start-instance.outputs.label }}
steps:
- name: Start instance
id: start-instance
uses: zama-ai/slab-github-runner@801df0b8db5ea2b06128b7476c652f5ed5f193a8
with:
mode: start
github-token: ${{ secrets.SLAB_ACTION_TOKEN }}
slab-url: ${{ secrets.SLAB_BASE_URL }}
job-secret: ${{ secrets.JOB_SECRET }}
backend: hyperstack
profile: single-h100
cuda-tests:
name: Long run GPU H100 tests
needs: [ setup-instance ]
concurrency:
group: ${{ github.workflow }}_${{github.event_name}}_${{ github.ref }}
cancel-in-progress: true
runs-on: ${{ needs.setup-instance.outputs.runner-name }}
strategy:
fail-fast: false
# explicit include-based build matrix, of known valid options
matrix:
include:
- os: ubuntu-22.04
cuda: "12.2"
gcc: 11
env:
CUDA_PATH: /usr/local/cuda-${{ matrix.cuda }}
CMAKE_VERSION: 3.29.6
steps:
# Mandatory on hyperstack since a bootable volume is not re-usable yet.
- name: Install dependencies
run: |
sudo apt update
sudo apt install -y checkinstall zlib1g-dev libssl-dev libclang-dev
wget https://github.com/Kitware/CMake/releases/download/v${{ env.CMAKE_VERSION }}/cmake-${{ env.CMAKE_VERSION }}.tar.gz
tar -zxvf cmake-${{ env.CMAKE_VERSION }}.tar.gz
cd cmake-${{ env.CMAKE_VERSION }}
./bootstrap
make -j"$(nproc)"
sudo make install
- name: Checkout tfhe-rs
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- name: Set up home
run: |
echo "HOME=/home/ubuntu" >> "${GITHUB_ENV}"
- name: Install latest stable
uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a
with:
toolchain: stable
- name: Export CUDA variables
if: ${{ !cancelled() }}
run: |
echo "CUDA_PATH=$CUDA_PATH" >> "${GITHUB_ENV}"
echo "$CUDA_PATH/bin" >> "${GITHUB_PATH}"
echo "LD_LIBRARY_PATH=$CUDA_PATH/lib:$LD_LIBRARY_PATH" >> "${GITHUB_ENV}"
echo "CUDACXX=/usr/local/cuda-${{ matrix.cuda }}/bin/nvcc" >> "${GITHUB_ENV}"
# Specify the correct host compilers
- name: Export gcc and g++ variables
if: ${{ !cancelled() }}
run: |
{
echo "CC=/usr/bin/gcc-${{ matrix.gcc }}";
echo "CXX=/usr/bin/g++-${{ matrix.gcc }}";
echo "CUDAHOSTCXX=/usr/bin/g++-${{ matrix.gcc }}";
echo "HOME=/home/ubuntu";
} >> "${GITHUB_ENV}"
- name: Check device is detected
if: ${{ !cancelled() }}
run: nvidia-smi
- name: Run tests
run: |
make test_integer_long_run_gpu
slack-notify:
name: Slack Notification
needs: [ setup-instance, cuda-tests ]
runs-on: ubuntu-latest
if: ${{ always() && needs.cuda-tests.result != 'skipped' && failure() }}
continue-on-error: true
steps:
- name: Send message
uses: rtCamp/action-slack-notify@c33737706dea87cd7784c687dadc9adf1be59990
env:
SLACK_COLOR: ${{ needs.cuda-tests.result }}
SLACK_MESSAGE: "Integer GPU H100 long run tests finished with status: ${{ needs.cuda-tests.result }}. (${{ env.ACTION_RUN_URL }})"
teardown-instance:
name: Teardown instance (gpu-tests)
if: ${{ always() && needs.setup-instance.result != 'skipped' }}
needs: [ setup-instance, cuda-tests ]
runs-on: ubuntu-latest
steps:
- name: Stop instance
id: stop-instance
uses: zama-ai/slab-github-runner@801df0b8db5ea2b06128b7476c652f5ed5f193a8
with:
mode: stop
github-token: ${{ secrets.SLAB_ACTION_TOKEN }}
slab-url: ${{ secrets.SLAB_BASE_URL }}
job-secret: ${{ secrets.JOB_SECRET }}
label: ${{ needs.setup-instance.outputs.runner-name }}
- name: Slack Notification
if: ${{ failure() }}
continue-on-error: true
uses: rtCamp/action-slack-notify@c33737706dea87cd7784c687dadc9adf1be59990
env:
SLACK_COLOR: ${{ job.status }}
SLACK_MESSAGE: "Instance teardown (gpu-long-run-tests) finished with status: ${{ job.status }}. (${{ env.ACTION_RUN_URL }})"

View File

@@ -0,0 +1,94 @@
name: AWS Long Run Tests on CPU
env:
CARGO_TERM_COLOR: always
ACTION_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
RUSTFLAGS: "-C target-cpu=native"
RUST_BACKTRACE: "full"
RUST_MIN_STACK: "8388608"
SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }}
SLACK_ICON: https://pbs.twimg.com/profile_images/1274014582265298945/OjBKP9kn_400x400.png
SLACK_USERNAME: ${{ secrets.BOT_USERNAME }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
on:
# Allows you to run this workflow manually from the Actions tab as an alternative.
workflow_dispatch:
schedule:
# Weekly tests will be triggered each Friday at 1a.m.
- cron: '0 1 * * FRI'
jobs:
setup-instance:
name: Setup instance (cpu-tests)
if: github.event_name != 'schedule' ||
(github.event_name == 'schedule' && github.repository == 'zama-ai/tfhe-rs')
runs-on: ubuntu-latest
outputs:
runner-name: ${{ steps.start-instance.outputs.label }}
steps:
- name: Start instance
id: start-instance
uses: zama-ai/slab-github-runner@801df0b8db5ea2b06128b7476c652f5ed5f193a8
with:
mode: start
github-token: ${{ secrets.SLAB_ACTION_TOKEN }}
slab-url: ${{ secrets.SLAB_BASE_URL }}
job-secret: ${{ secrets.JOB_SECRET }}
backend: aws
profile: cpu-big
cpu-tests:
name: Long run CPU tests
needs: [ setup-instance ]
concurrency:
group: ${{ github.workflow }}_${{github.event_name}}_${{ github.ref }}
cancel-in-progress: true
runs-on: ${{ needs.setup-instance.outputs.runner-name }}
steps:
- name: Checkout tfhe-rs
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
persist-credentials: 'false'
token: ${{ secrets.FHE_ACTIONS_TOKEN }}
- name: Install latest stable
uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a
with:
toolchain: stable
- name: Run tests
run: |
make test_integer_long_run
- name: Slack Notification
if: ${{ failure() }}
continue-on-error: true
uses: rtCamp/action-slack-notify@c33737706dea87cd7784c687dadc9adf1be59990
env:
SLACK_COLOR: ${{ job.status }}
SLACK_MESSAGE: "CPU long run tests finished with status: ${{ job.status }}. (${{ env.ACTION_RUN_URL }})"
teardown-instance:
name: Teardown instance (cpu-tests)
if: ${{ always() && needs.setup-instance.result != 'skipped' }}
needs: [ setup-instance, cpu-tests ]
runs-on: ubuntu-latest
steps:
- name: Stop instance
id: stop-instance
uses: zama-ai/slab-github-runner@801df0b8db5ea2b06128b7476c652f5ed5f193a8
with:
mode: stop
github-token: ${{ secrets.SLAB_ACTION_TOKEN }}
slab-url: ${{ secrets.SLAB_BASE_URL }}
job-secret: ${{ secrets.JOB_SECRET }}
label: ${{ needs.setup-instance.outputs.runner-name }}
- name: Slack Notification
if: ${{ failure() }}
continue-on-error: true
uses: rtCamp/action-slack-notify@c33737706dea87cd7784c687dadc9adf1be59990
env:
SLACK_COLOR: ${{ job.status }}
SLACK_MESSAGE: "Instance teardown (cpu-long-run-tests) finished with status: ${{ job.status }}. (${{ env.ACTION_RUN_URL }})"

View File

@@ -585,6 +585,11 @@ test_integer_gpu: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --doc --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),integer,gpu -p $(TFHE_SPEC) -- integer::gpu::server_key::
.PHONY: test_integer_long_run_gpu # Run the tests of the integer module including experimental on the gpu backend
test_integer_long_run_gpu: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),integer,gpu,__long_run_tests -p $(TFHE_SPEC) -- integer::gpu::server_key::radix::tests_long_run --test-threads=6
.PHONY: test_integer_compression
test_integer_compression: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
@@ -765,6 +770,12 @@ test_signed_integer_multi_bit_ci: install_rs_check_toolchain install_cargo_nexte
--cargo-profile "$(CARGO_PROFILE)" --multi-bit --avx512-support "$(AVX512_SUPPORT)" \
--signed-only --tfhe-package "$(TFHE_SPEC)"
.PHONY: test_integer_long_run # Run the long run tests for integer
test_integer_long_run: install_rs_build_toolchain
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \
--features=$(TARGET_ARCH_FEATURE),integer,internal-keycache,__long_run_tests -p $(TFHE_SPEC) -- integer::server_key::radix_parallel::tests_long_run
.PHONY: test_safe_serialization # Run the tests for safe serialization
test_safe_serialization: install_rs_build_toolchain install_cargo_nextest
RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_BUILD_TOOLCHAIN) test --profile $(CARGO_PROFILE) \

View File

@@ -141,6 +141,7 @@ generator_aarch64_aes = ["tfhe-csprng/generator_aarch64_aes"]
# Private features
__profiling = []
__long_run_tests = []
seeder_unix = ["tfhe-csprng/seeder_unix"]
seeder_x86_64_rdseed = ["tfhe-csprng/seeder_x86_64_rdseed"]

View File

@@ -48,6 +48,8 @@ mod scalar_sub;
mod shift;
mod sub;
#[cfg(all(test, feature = "__long_run_tests"))]
mod tests_long_run;
#[cfg(test)]
mod tests_signed;
#[cfg(test)]

View File

@@ -0,0 +1 @@
pub(crate) mod test_erc20;

View File

@@ -0,0 +1,65 @@
use crate::integer::gpu::server_key::radix::tests_unsigned::{
create_gpu_parametrized_test, GpuFunctionExecutor,
};
use crate::integer::gpu::CudaServerKey;
use crate::integer::server_key::radix_parallel::tests_long_run::test_erc20::{
no_cmux_erc20_test, safe_erc20_test, whitepaper_erc20_test,
};
use crate::shortint::parameters::*;
create_gpu_parametrized_test!(safe_erc20 {
PARAM_GPU_MULTI_BIT_GROUP_3_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64
});
create_gpu_parametrized_test!(whitepaper_erc20 {
PARAM_GPU_MULTI_BIT_GROUP_3_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64
});
create_gpu_parametrized_test!(no_cmux_erc20 {
PARAM_GPU_MULTI_BIT_GROUP_3_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64
});
fn safe_erc20<P>(param: P)
where
P: Into<PBSParameters>,
{
let overflowing_add_executor =
GpuFunctionExecutor::new(&CudaServerKey::unsigned_overflowing_add);
let overflowing_sub_executor =
GpuFunctionExecutor::new(&CudaServerKey::unsigned_overflowing_sub);
let if_then_else_executor = GpuFunctionExecutor::new(&CudaServerKey::if_then_else);
let bitwise_or_executor = GpuFunctionExecutor::new(&CudaServerKey::bitor);
safe_erc20_test(
param,
overflowing_add_executor,
overflowing_sub_executor,
if_then_else_executor,
bitwise_or_executor,
);
}
fn whitepaper_erc20<P>(param: P)
where
P: Into<PBSParameters>,
{
let ge_executor = GpuFunctionExecutor::new(&CudaServerKey::ge);
let add_executor = GpuFunctionExecutor::new(&CudaServerKey::add);
let if_then_else_executor = GpuFunctionExecutor::new(&CudaServerKey::if_then_else);
let sub_executor = GpuFunctionExecutor::new(&CudaServerKey::sub);
whitepaper_erc20_test(
param,
ge_executor,
add_executor,
if_then_else_executor,
sub_executor,
);
}
fn no_cmux_erc20<P>(param: P)
where
P: Into<PBSParameters>,
{
let ge_executor = GpuFunctionExecutor::new(&CudaServerKey::ge);
let mul_executor = GpuFunctionExecutor::new(&CudaServerKey::mul);
let add_executor = GpuFunctionExecutor::new(&CudaServerKey::add);
let sub_executor = GpuFunctionExecutor::new(&CudaServerKey::sub);
no_cmux_erc20_test(param, ge_executor, mul_executor, add_executor, sub_executor);
}

View File

@@ -28,6 +28,8 @@ mod reverse_bits;
mod slice;
#[cfg(test)]
pub(crate) mod tests_cases_unsigned;
#[cfg(all(test, feature = "__long_run_tests"))]
pub(crate) mod tests_long_run;
#[cfg(test)]
pub(crate) mod tests_signed;
#[cfg(test)]

View File

@@ -0,0 +1,3 @@
pub(crate) mod test_erc20;
pub(crate) const NB_CTXT_LONG_RUN: usize = 32;
pub(crate) const NB_TESTS_LONG_RUN: usize = 1000;

View File

@@ -0,0 +1,528 @@
use crate::integer::keycache::KEY_CACHE;
use crate::integer::server_key::radix_parallel::tests_cases_unsigned::FunctionExecutor;
use crate::integer::server_key::radix_parallel::tests_long_run::{
NB_CTXT_LONG_RUN, NB_TESTS_LONG_RUN,
};
use crate::integer::server_key::radix_parallel::tests_unsigned::CpuFunctionExecutor;
use crate::integer::tests::create_parametrized_test;
use crate::integer::{
BooleanBlock, IntegerCiphertext, IntegerKeyKind, RadixCiphertext, RadixClientKey, ServerKey,
};
use crate::shortint::parameters::*;
use rand::Rng;
use std::sync::Arc;
create_parametrized_test!(safe_erc20 {
PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64
});
create_parametrized_test!(whitepaper_erc20 {
PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64
});
create_parametrized_test!(no_cmux_erc20 {
PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64
});
create_parametrized_test!(overflow_erc20 {
PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M64
});
fn safe_erc20<P>(param: P)
where
P: Into<PBSParameters>,
{
let overflowing_add_executor =
CpuFunctionExecutor::new(&ServerKey::unsigned_overflowing_add_parallelized);
let overflowing_sub_executor =
CpuFunctionExecutor::new(&ServerKey::unsigned_overflowing_sub_parallelized);
let if_then_else_executor = CpuFunctionExecutor::new(&ServerKey::cmux_parallelized);
let bitwise_or_executor = CpuFunctionExecutor::new(&ServerKey::bitor_parallelized);
safe_erc20_test(
param,
overflowing_add_executor,
overflowing_sub_executor,
if_then_else_executor,
bitwise_or_executor,
);
}
fn whitepaper_erc20<P>(param: P)
where
P: Into<PBSParameters>,
{
let ge_executor = CpuFunctionExecutor::new(&ServerKey::ge_parallelized);
let add_executor = CpuFunctionExecutor::new(&ServerKey::add_parallelized);
let if_then_else_executor = CpuFunctionExecutor::new(&ServerKey::cmux_parallelized);
let sub_executor = CpuFunctionExecutor::new(&ServerKey::sub_parallelized);
whitepaper_erc20_test(
param,
ge_executor,
add_executor,
if_then_else_executor,
sub_executor,
);
}
fn no_cmux_erc20<P>(param: P)
where
P: Into<PBSParameters>,
{
let ge_executor = CpuFunctionExecutor::new(&ServerKey::ge_parallelized);
let mul_executor = CpuFunctionExecutor::new(&ServerKey::mul_parallelized);
let add_executor = CpuFunctionExecutor::new(&ServerKey::add_parallelized);
let sub_executor = CpuFunctionExecutor::new(&ServerKey::sub_parallelized);
no_cmux_erc20_test(param, ge_executor, mul_executor, add_executor, sub_executor);
}
fn overflow_erc20<P>(param: P)
where
P: Into<PBSParameters>,
{
let overflowing_sub_executor =
CpuFunctionExecutor::new(&ServerKey::unsigned_overflowing_sub_parallelized);
let if_then_else_executor = CpuFunctionExecutor::new(&ServerKey::cmux_parallelized);
let not_executor = CpuFunctionExecutor::new(&ServerKey::boolean_bitnot);
let mul_executor = CpuFunctionExecutor::new(&ServerKey::mul_parallelized);
let add_executor = CpuFunctionExecutor::new(&ServerKey::add_parallelized);
overflow_erc20_test(
param,
overflowing_sub_executor,
if_then_else_executor,
not_executor,
mul_executor,
add_executor,
);
}
pub(crate) fn safe_erc20_test<P, T1, T2, T3, T4>(
param: P,
mut overflowing_add_executor: T1,
mut overflowing_sub_executor: T2,
mut if_then_else_executor: T3,
mut bitor_executor: T4,
) where
P: Into<PBSParameters>,
T1: for<'a> FunctionExecutor<
(&'a RadixCiphertext, &'a RadixCiphertext),
(RadixCiphertext, BooleanBlock),
>,
T2: for<'a> FunctionExecutor<
(&'a RadixCiphertext, &'a RadixCiphertext),
(RadixCiphertext, BooleanBlock),
>,
T3: for<'a> FunctionExecutor<
(&'a BooleanBlock, &'a RadixCiphertext, &'a RadixCiphertext),
RadixCiphertext,
>,
T4: for<'a> FunctionExecutor<(&'a RadixCiphertext, &'a RadixCiphertext), RadixCiphertext>,
{
let param = param.into();
let (cks, mut sks) = KEY_CACHE.get_from_params(param, IntegerKeyKind::Radix);
sks.set_deterministic_pbs_execution(true);
let sks = Arc::new(sks);
let cks = RadixClientKey::from((cks, NB_CTXT_LONG_RUN));
let mut rng = rand::thread_rng();
overflowing_add_executor.setup(&cks, sks.clone());
overflowing_sub_executor.setup(&cks, sks.clone());
if_then_else_executor.setup(&cks, sks.clone());
bitor_executor.setup(&cks, sks);
for _ in 0..NB_TESTS_LONG_RUN {
let clear_from_amount = rng.gen::<u64>();
let clear_to_amount = rng.gen::<u64>();
let clear_amount = rng.gen::<u64>();
let from_amount = cks.encrypt(clear_from_amount);
let to_amount = cks.encrypt(clear_to_amount);
let amount = cks.encrypt(clear_amount);
let (new_from, did_not_have_enough_funds) =
overflowing_sub_executor.execute((&from_amount, &amount));
let (new_to, did_not_have_enough_space) =
overflowing_add_executor.execute((&to_amount, &amount));
let decrypted_did_not_have_enough_funds: bool =
cks.decrypt_bool(&did_not_have_enough_funds);
let decrypted_did_not_have_enough_space: bool =
cks.decrypt_bool(&did_not_have_enough_space);
let did_not_have_enough_space_ct =
RadixCiphertext::from_blocks(vec![did_not_have_enough_space.0]);
let did_not_have_enough_funds_ct =
RadixCiphertext::from_blocks(vec![did_not_have_enough_funds.0]);
let something_not_ok_ct =
bitor_executor.execute((&did_not_have_enough_funds_ct, &did_not_have_enough_space_ct));
let something_not_ok = BooleanBlock(something_not_ok_ct.blocks.first().unwrap().clone());
let new_from_amount =
if_then_else_executor.execute((&something_not_ok, &from_amount, &new_from));
let new_to_amount = if_then_else_executor.execute((&something_not_ok, &to_amount, &new_to));
let decrypted_new_from_amount: u64 = cks.decrypt(&new_from_amount);
let decrypted_new_to_amount: u64 = cks.decrypt(&new_to_amount);
let decrypted_something_not_ok: bool = cks.decrypt_bool(&something_not_ok);
let (expected_new_from, expected_did_not_have_enough_funds) =
clear_from_amount.overflowing_sub(clear_amount);
let (expected_new_to, expected_did_not_have_enough_space) =
clear_to_amount.overflowing_add(clear_amount);
let expected_something_not_ok =
expected_did_not_have_enough_funds | expected_did_not_have_enough_space;
let expected_new_to_amount = if expected_something_not_ok {
clear_to_amount
} else {
expected_new_to
};
let expected_new_from_amount = if expected_something_not_ok {
clear_from_amount
} else {
expected_new_from
};
assert_eq!(
decrypted_did_not_have_enough_funds, expected_did_not_have_enough_funds,
"Invalid erc20 result on enough funds: original from amount: {clear_from_amount}, amount: {clear_amount}, to amount: {clear_to_amount}."
);
assert_eq!(
decrypted_did_not_have_enough_space, expected_did_not_have_enough_space,
"Invalid erc20 result on enough space: amount: {clear_amount}, to amount: {clear_to_amount}."
);
assert_eq!(
decrypted_something_not_ok, expected_something_not_ok,
"Invalid erc20 result on something nok: original from amount: {clear_from_amount}, amount: {clear_amount}, to amount: {clear_to_amount}."
);
assert_eq!(
decrypted_new_from_amount, expected_new_from_amount,
"Invalid erc20 result on from amount: original from amount: {clear_from_amount}, amount: {clear_amount}, to amount: {clear_to_amount}, expected new from amount: {expected_new_from_amount}."
);
assert_eq!(
decrypted_new_to_amount, expected_new_to_amount,
"Invalid erc20 result on to amount."
);
// Determinism check
let (new_from_1, did_not_have_enough_funds_1) =
overflowing_sub_executor.execute((&from_amount, &amount));
let (new_to_1, did_not_have_enough_space_1) =
overflowing_add_executor.execute((&to_amount, &amount));
let did_not_have_enough_space_ct_1 =
RadixCiphertext::from_blocks(vec![did_not_have_enough_space_1.0]);
let did_not_have_enough_funds_ct_1 =
RadixCiphertext::from_blocks(vec![did_not_have_enough_funds_1.0]);
let something_not_ok_ct_1 = bitor_executor.execute((
&did_not_have_enough_funds_ct_1,
&did_not_have_enough_space_ct_1,
));
let something_not_ok_1 =
BooleanBlock(something_not_ok_ct_1.blocks.first().unwrap().clone());
let new_from_amount_1 =
if_then_else_executor.execute((&something_not_ok_1, &from_amount, &new_from_1));
let new_to_amount_1 =
if_then_else_executor.execute((&something_not_ok_1, &to_amount, &new_to_1));
assert_eq!(
new_from_amount, new_from_amount_1,
"Determinism check failed on erc20 from amount"
);
assert_eq!(
new_to_amount, new_to_amount_1,
"Determinism check failed on erc20 to amount"
);
}
}
pub(crate) fn whitepaper_erc20_test<P, T1, T2, T3, T4>(
param: P,
mut ge_executor: T1,
mut add_executor: T2,
mut if_then_else_executor: T3,
mut sub_executor: T4,
) where
P: Into<PBSParameters>,
T1: for<'a> FunctionExecutor<(&'a RadixCiphertext, &'a RadixCiphertext), BooleanBlock>,
T2: for<'a> FunctionExecutor<(&'a RadixCiphertext, &'a RadixCiphertext), RadixCiphertext>,
T3: for<'a> FunctionExecutor<
(&'a BooleanBlock, &'a RadixCiphertext, &'a RadixCiphertext),
RadixCiphertext,
>,
T4: for<'a> FunctionExecutor<(&'a RadixCiphertext, &'a RadixCiphertext), RadixCiphertext>,
{
let param = param.into();
let (cks, mut sks) = KEY_CACHE.get_from_params(param, IntegerKeyKind::Radix);
sks.set_deterministic_pbs_execution(true);
let sks = Arc::new(sks);
let cks = RadixClientKey::from((cks, NB_CTXT_LONG_RUN));
let mut rng = rand::thread_rng();
ge_executor.setup(&cks, sks.clone());
add_executor.setup(&cks, sks.clone());
if_then_else_executor.setup(&cks, sks.clone());
sub_executor.setup(&cks, sks);
for _ in 0..NB_TESTS_LONG_RUN {
let clear_from_amount = rng.gen::<u64>();
let clear_to_amount = rng.gen::<u64>();
let clear_amount = rng.gen::<u64>();
let from_amount = cks.encrypt(clear_from_amount);
let to_amount = cks.encrypt(clear_to_amount);
let amount = cks.encrypt(clear_amount);
let has_enough_funds = ge_executor.execute((&from_amount, &amount));
let mut new_to_amount = add_executor.execute((&to_amount, &amount));
new_to_amount =
if_then_else_executor.execute((&has_enough_funds, &new_to_amount, &to_amount));
let mut new_from_amount = sub_executor.execute((&from_amount, &amount));
new_from_amount =
if_then_else_executor.execute((&has_enough_funds, &new_from_amount, &from_amount));
let decrypted_new_from_amount: u64 = cks.decrypt(&new_from_amount);
let decrypted_new_to_amount: u64 = cks.decrypt(&new_to_amount);
let expected_new_from_amount = if clear_from_amount >= clear_amount {
clear_from_amount - clear_amount
} else {
clear_from_amount
};
let expected_new_to_amount = if clear_from_amount >= clear_amount {
clear_to_amount + clear_amount
} else {
clear_to_amount
};
assert_eq!(
decrypted_new_from_amount, expected_new_from_amount,
"Invalid erc20 result on from amount: original from amount: {clear_from_amount}, amount: {clear_amount}, to amount: {clear_to_amount}, expected new from amount: {expected_new_from_amount}."
);
assert_eq!(
decrypted_new_to_amount, expected_new_to_amount,
"Invalid erc20 result on to amount."
);
// Determinism check
let has_enough_funds_1 = ge_executor.execute((&from_amount, &amount));
let mut new_to_amount_1 = add_executor.execute((&to_amount, &amount));
new_to_amount_1 =
if_then_else_executor.execute((&has_enough_funds_1, &new_to_amount_1, &to_amount));
let mut new_from_amount_1 = sub_executor.execute((&from_amount, &amount));
new_from_amount_1 =
if_then_else_executor.execute((&has_enough_funds_1, &new_from_amount_1, &from_amount));
assert_eq!(
new_from_amount, new_from_amount_1,
"Determinism check failed on erc20 from amount"
);
assert_eq!(
new_to_amount, new_to_amount_1,
"Determinism check failed on erc20 to amount"
);
}
}
pub(crate) fn no_cmux_erc20_test<P, T1, T2, T3, T4>(
param: P,
mut ge_executor: T1,
mut mul_executor: T2,
mut add_executor: T3,
mut sub_executor: T4,
) where
P: Into<PBSParameters>,
T1: for<'a> FunctionExecutor<(&'a RadixCiphertext, &'a RadixCiphertext), BooleanBlock>,
T2: for<'a> FunctionExecutor<(&'a RadixCiphertext, &'a RadixCiphertext), RadixCiphertext>,
T3: for<'a> FunctionExecutor<(&'a RadixCiphertext, &'a RadixCiphertext), RadixCiphertext>,
T4: for<'a> FunctionExecutor<(&'a RadixCiphertext, &'a RadixCiphertext), RadixCiphertext>,
{
let param = param.into();
let (cks, mut sks) = KEY_CACHE.get_from_params(param, IntegerKeyKind::Radix);
sks.set_deterministic_pbs_execution(true);
let sks = Arc::new(sks);
let cks = RadixClientKey::from((cks, NB_CTXT_LONG_RUN));
let mut rng = rand::thread_rng();
ge_executor.setup(&cks, sks.clone());
mul_executor.setup(&cks, sks.clone());
add_executor.setup(&cks, sks.clone());
sub_executor.setup(&cks, sks);
for _ in 0..NB_TESTS_LONG_RUN {
let clear_from_amount = rng.gen::<u64>();
let clear_to_amount = rng.gen::<u64>();
let clear_amount = rng.gen::<u64>();
let from_amount = cks.encrypt(clear_from_amount);
let to_amount = cks.encrypt(clear_to_amount);
let amount = cks.encrypt(clear_amount);
let has_enough_funds = ge_executor.execute((&from_amount, &amount));
let has_enough_funds_ct = RadixCiphertext::from_blocks(vec![has_enough_funds.0]);
let new_amount = mul_executor.execute((&amount, &has_enough_funds_ct));
let new_to_amount = add_executor.execute((&to_amount, &new_amount));
let new_from_amount = sub_executor.execute((&from_amount, &new_amount));
let decrypted_new_from_amount: u64 = cks.decrypt(&new_from_amount);
let decrypted_new_to_amount: u64 = cks.decrypt(&new_to_amount);
let expected_new_from_amount = if clear_from_amount >= clear_amount {
clear_from_amount - clear_amount
} else {
clear_from_amount
};
let expected_new_to_amount = if clear_from_amount >= clear_amount {
clear_to_amount + clear_amount
} else {
clear_to_amount
};
assert_eq!(
decrypted_new_from_amount, expected_new_from_amount,
"Invalid erc20 result on from amount: original from amount: {clear_from_amount}, amount: {clear_amount}, to amount: {clear_to_amount}, expected new from amount: {expected_new_from_amount}."
);
assert_eq!(
decrypted_new_to_amount, expected_new_to_amount,
"Invalid erc20 result on to amount."
);
// Determinism check
let has_enough_funds_1 = ge_executor.execute((&from_amount, &amount));
let has_enough_funds_ct_1 = RadixCiphertext::from_blocks(vec![has_enough_funds_1.0]);
let new_amount_1 = mul_executor.execute((&amount, &has_enough_funds_ct_1));
let new_to_amount_1 = add_executor.execute((&to_amount, &new_amount_1));
let new_from_amount_1 = sub_executor.execute((&from_amount, &new_amount_1));
assert_eq!(
new_from_amount, new_from_amount_1,
"Determinism check failed on no cmux erc20 from amount"
);
assert_eq!(
new_to_amount, new_to_amount_1,
"Determinism check failed on no cmux erc20 to amount"
);
}
}
pub(crate) fn overflow_erc20_test<P, T1, T2, T3, T4, T5>(
param: P,
mut overflowing_sub_executor: T1,
mut if_then_else_executor: T2,
mut not_executor: T3,
mut mul_executor: T4,
mut add_executor: T5,
) where
P: Into<PBSParameters>,
T1: for<'a> FunctionExecutor<
(&'a RadixCiphertext, &'a RadixCiphertext),
(RadixCiphertext, BooleanBlock),
>,
T2: for<'a> FunctionExecutor<
(&'a BooleanBlock, &'a RadixCiphertext, &'a RadixCiphertext),
RadixCiphertext,
>,
T3: for<'a> FunctionExecutor<&'a BooleanBlock, BooleanBlock>,
T4: for<'a> FunctionExecutor<(&'a RadixCiphertext, &'a RadixCiphertext), RadixCiphertext>,
T5: for<'a> FunctionExecutor<(&'a RadixCiphertext, &'a RadixCiphertext), RadixCiphertext>,
{
let param = param.into();
let (cks, mut sks) = KEY_CACHE.get_from_params(param, IntegerKeyKind::Radix);
sks.set_deterministic_pbs_execution(true);
let sks = Arc::new(sks);
let cks = RadixClientKey::from((cks, NB_CTXT_LONG_RUN));
let mut rng = rand::thread_rng();
overflowing_sub_executor.setup(&cks, sks.clone());
if_then_else_executor.setup(&cks, sks.clone());
not_executor.setup(&cks, sks.clone());
mul_executor.setup(&cks, sks.clone());
add_executor.setup(&cks, sks);
for _ in 0..NB_TESTS_LONG_RUN {
let clear_from_amount = rng.gen::<u64>();
let clear_to_amount = rng.gen::<u64>();
let clear_amount = rng.gen::<u64>();
let from_amount = cks.encrypt(clear_from_amount);
let to_amount = cks.encrypt(clear_to_amount);
let amount = cks.encrypt(clear_amount);
let (new_from, did_not_have_enough_funds) =
overflowing_sub_executor.execute((&from_amount, &amount));
let new_from_amount =
if_then_else_executor.execute((&did_not_have_enough_funds, &from_amount, &new_from));
let had_enough_funds = not_executor.execute(&did_not_have_enough_funds);
let had_enough_funds_ct = RadixCiphertext::from_blocks(vec![had_enough_funds.0]);
let new_amount = mul_executor.execute((&amount, &had_enough_funds_ct));
let new_to_amount = add_executor.execute((&to_amount, &new_amount));
let decrypted_did_not_have_enough_funds: bool =
cks.decrypt_bool(&did_not_have_enough_funds);
let decrypted_new_from_amount: u64 = cks.decrypt(&new_from_amount);
let decrypted_new_to_amount: u64 = cks.decrypt(&new_to_amount);
let (expected_new_from, expected_did_not_have_enough_funds) =
clear_from_amount.overflowing_sub(clear_amount);
let expected_new_from_amount = if expected_did_not_have_enough_funds {
clear_from_amount
} else {
expected_new_from
};
let expected_had_enough_funds = !expected_did_not_have_enough_funds;
let expected_new_amount = if expected_had_enough_funds {
clear_amount
} else {
0
};
let expected_new_to_amount = clear_to_amount + expected_new_amount;
assert_eq!(
decrypted_did_not_have_enough_funds, expected_did_not_have_enough_funds,
"Invalid erc20 result on enough funds: original from amount: {clear_from_amount}, amount: {clear_amount}, to amount: {clear_to_amount}."
);
assert_eq!(
decrypted_new_from_amount, expected_new_from_amount,
"Invalid erc20 result on from amount: original from amount: {clear_from_amount}, amount: {clear_amount}, to amount: {clear_to_amount}, expected new from amount: {expected_new_from_amount}."
);
assert_eq!(
decrypted_new_to_amount, expected_new_to_amount,
"Invalid erc20 result on to amount."
);
// Determinism check
let (new_from_1, did_not_have_enough_funds_1) =
overflowing_sub_executor.execute((&from_amount, &amount));
let new_from_amount_1 = if_then_else_executor.execute((
&did_not_have_enough_funds_1,
&from_amount,
&new_from_1,
));
let had_enough_funds_1 = not_executor.execute(&did_not_have_enough_funds_1);
let had_enough_funds_ct_1 = RadixCiphertext::from_blocks(vec![had_enough_funds_1.0]);
let new_amount_1 = mul_executor.execute((&amount, &had_enough_funds_ct_1));
let new_to_amount_1 = add_executor.execute((&to_amount, &new_amount_1));
assert_eq!(
new_from_amount, new_from_amount_1,
"Determinism check failed on erc20 from amount"
);
assert_eq!(
new_to_amount, new_to_amount_1,
"Determinism check failed on erc20 to amount"
);
}
}

View File

@@ -654,6 +654,21 @@ where
}
}
// Bitnot
impl<'a, F> FunctionExecutor<&'a BooleanBlock, BooleanBlock> for CpuFunctionExecutor<F>
where
F: Fn(&ServerKey, &BooleanBlock) -> BooleanBlock,
{
fn setup(&mut self, _cks: &RadixClientKey, sks: Arc<ServerKey>) {
self.sks = Some(sks);
}
fn execute(&mut self, input: &'a BooleanBlock) -> BooleanBlock {
let sks = self.sks.as_ref().expect("setup was not properly called");
(self.func)(sks, input)
}
}
//=============================================================================
// Unchecked Tests
//=============================================================================