Compare commits

...

5 Commits

Author SHA1 Message Date
Nicolas Sarlin
94e92c65f2 chore(ci): send slack notif on merge/close failure 2024-07-23 12:57:19 +02:00
Nicolas Sarlin
58d3874d6d chore(ci): automatically merge pr in the data repo 2024-07-22 18:04:27 +02:00
Nicolas Sarlin
f6541a680b chore(ci): use specific workflow for data compatibility tests 2024-07-19 15:05:45 +02:00
Nicolas Sarlin
5d1607d25c chore(hl): add data tests for heterogeneous lists 2024-07-19 12:58:52 +02:00
Arthur Meyre
58344fd820 chore(ci): auto data branch 2024-07-19 11:24:08 +02:00
5 changed files with 476 additions and 23 deletions

View File

@@ -0,0 +1,119 @@
# Run backward compatibility tests
name: Backward compatibility 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:
pull_request:
jobs:
setup-instance:
name: Setup instance (backward-compat-tests)
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@447a2d0fd2d1a9d647aa0d0723a6e9255372f261
with:
mode: start
github-token: ${{ secrets.SLAB_ACTION_TOKEN }}
slab-url: ${{ secrets.SLAB_BASE_URL }}
job-secret: ${{ secrets.JOB_SECRET }}
backend: aws
profile: cpu-small
backward-compat-tests:
name: Backward compatibility tests
needs: [ setup-instance ]
concurrency:
group: ${{ github.workflow }}_${{ github.ref }}
cancel-in-progress: true
runs-on: ${{ needs.setup-instance.outputs.runner-name }}
steps:
- name: Checkout tfhe-rs
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
with:
persist-credentials: 'false'
- name: Set up home
run: |
echo "HOME=/home/ubuntu" >> "${GITHUB_ENV}"
- name: Install latest stable
uses: dtolnay/rust-toolchain@21dc36fb71dd22e3317045c0c31a3f4249868b17
with:
toolchain: stable
- name: Install git-lfs
run: |
sudo apt update && sudo apt -y install git-lfs
- name: Use specific data branch
if: ${{ contains(github.event.pull_request.labels.*.name, 'data_PR') }}
env:
PR_BRANCH: ${{ github.head_ref || github.ref_name }}
run: |
echo "BACKWARD_COMPAT_DATA_BRANCH=${PR_BRANCH}" >> "${GITHUB_ENV}"
- name: Get backward compat branch
id: backward_compat_branch
run: |
BRANCH="$(make backward_compat_branch)"
echo "branch=${BRANCH}" >> "${GITHUB_OUTPUT}"
- name: Clone test data
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
with:
repository: zama-ai/tfhe-backward-compat-data
path: tfhe/tfhe-backward-compat-data
lfs: 'true'
ref: ${{ steps.backward_compat_branch.outputs.branch }}
- name: Run backward compatibility tests
run: |
make test_backward_compatibility_ci
- name: Slack Notification
if: ${{ always() }}
continue-on-error: true
uses: rtCamp/action-slack-notify@4e5fb42d249be6a45a298f3c9543b111b02f7907
env:
SLACK_COLOR: ${{ job.status }}
SLACK_MESSAGE: "Backward compatibility tests finished with status: ${{ job.status }}. (${{ env.ACTION_RUN_URL }})"
teardown-instance:
name: Teardown instance (backward-compat-tests)
if: ${{ always() && needs.setup-instance.result != 'skipped' }}
needs: [ setup-instance, backward-compat-tests ]
runs-on: ubuntu-latest
steps:
- name: Stop instance
id: stop-instance
uses: zama-ai/slab-github-runner@447a2d0fd2d1a9d647aa0d0723a6e9255372f261
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@4e5fb42d249be6a45a298f3c9543b111b02f7907
env:
SLACK_COLOR: ${{ job.status }}
SLACK_MESSAGE: "Instance teardown (backward-compat-tests) finished with status: ${{ job.status }}. (${{ env.ACTION_RUN_URL }})"

View File

@@ -157,10 +157,6 @@ jobs:
with:
toolchain: stable
- name: Install git-lfs
run: |
sudo apt update && sudo apt -y install git-lfs
- name: Run concrete-csprng tests
if: needs.should-run.outputs.csprng_test == 'true'
run: |
@@ -216,17 +212,6 @@ jobs:
run: |
make test_safe_deserialization
- name: Clone test data
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
with:
repository: zama-ai/tfhe-backward-compat-data
path: tfhe/tfhe-backward-compat-data
lfs: 'true'
- name: Run backward compatibility tests
run: |
make test_backward_compatibility_ci
- name: Slack Notification
if: ${{ always() }}
continue-on-error: true

174
.github/workflows/data_pr_close.yml vendored Normal file
View File

@@ -0,0 +1,174 @@
name: Close or Merge corresponding PR on the data repo
# When a PR with the data_PR tag is closed or merged, this will close the corresponding PR in the data repo.
env:
TARGET_REPO_API_URL: ${{ github.api_url }}/repos/zama-ai/tfhe-backward-compat-data
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 }}
# only trigger on pull request closed events
on:
pull_request:
types: [ closed ]
jobs:
merge_job:
# this job will only run if the PR has been merged
if: ${{ github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'data_PR') }}
runs-on: ubuntu-latest
steps:
- name: Find corresponding Pull Request in the data repo
env:
PR_BRANCH: ${{ github.head_ref || github.ref_name }}
run: |
{
set +e
set -o pipefail
echo 'GH_API_RES<<EOF'
curl --fail-with-body --no-progress-meter -L -X GET \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
${{ env.TARGET_REPO_API_URL }}/pulls\?head=${{ github.repository_owner }}:${{ env.PR_BRANCH }} | jq -e '.[0]' | sed 's/null/{ "message": "corresponding PR not found" }/'
RES="$?"
echo EOF
} >> "${GITHUB_ENV}"
exit $RES
- name: Comment on the PR to indicate the reason of the merge
run: |
{
set +e
echo 'GH_API_RES<<EOF'
curl --fail-with-body --no-progress-meter -L -X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.FHE_ACTIONS_TOKEN }}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
${{ fromJson(env.GH_API_RES).comments_url }} \
-d '{ "body": "PR merged because the corresponding PR in main repo was merged: ${{ github.repository }}#${{ github.event.number }}" }'
RES="$?"
echo EOF
} >> "${GITHUB_ENV}"
exit $RES
- name: Merge the Pull Request in the data repo
run: |
{
set +e
echo 'GH_API_RES<<EOF'
curl --fail-with-body --no-progress-meter -L -X PUT \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.FHE_ACTIONS_TOKEN }}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
${{ fromJson(env.TARGET_REPO_PR).url }}/merge \
-d '{ "merge_method": "rebase" }'
RES="$?"
echo EOF
} >> "${GITHUB_ENV}"
exit $RES
- name: Delete the associated branch in the data repo
env:
PR_BRANCH: ${{ github.head_ref || github.ref_name }}
run: |
{
set +e
echo 'GH_API_RES<<EOF'
curl --fail-with-body --no-progress-meter -L -X DELETE \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.FHE_ACTIONS_TOKEN }}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
${{ env.TARGET_REPO_API_URL }}/git/refs/heads/${{ env.PR_BRANCH }}
RES="$?"
echo EOF
} >> "${GITHUB_ENV}"
exit $RES
- name: Slack Notification
if: ${{ always() && job.status == 'failure' }}
continue-on-error: true
uses: rtCamp/action-slack-notify@4e5fb42d249be6a45a298f3c9543b111b02f7907
env:
SLACK_COLOR: ${{ job.status }}
SLACK_MESSAGE: "Failed to auto-merge PR on data repo: ${{ fromJson(env.GH_API_RES).message }}"
close_job:
# this job will only run if the PR has been closed without being merged
if: ${{ github.event.pull_request.merged == false && contains(github.event.pull_request.labels.*.name, 'data_PR') }}
runs-on: ubuntu-latest
steps:
- name: Find corresponding Pull Request in the data repo
env:
PR_BRANCH: ${{ github.head_ref || github.ref_name }}
run: |
{
set +e
set -o pipefail
echo 'GH_API_RES<<EOF'
curl --fail-with-body --no-progress-meter -L -X GET \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
${{ env.TARGET_REPO_API_URL }}/pulls\?head=${{ github.repository_owner }}:${{ env.PR_BRANCH }} | jq -e '.[0]' | sed 's/null/{ "message": "corresponding PR not found" }/'
RES=$?
echo EOF
} >> "${GITHUB_ENV}"
exit $RES
- name: Comment on the PR to indicate the reason of the close
run: |
{
set +e
echo 'GH_API_RES<<EOF'
curl --fail-with-body --no-progress-meter -L -X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.FHE_ACTIONS_TOKEN }}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
${{ fromJson(env.GH_API_RES).comments_url }} \
-d '{ "body": "PR closed because the corresponding PR in main repo was closed: ${{ github.repository }}#${{ github.event.number }}" }'
RES="$?"
echo EOF
} >> "${GITHUB_ENV}"
exit $RES
- name: Close the Pull Request in the data repo
run: |
{
set +e
echo 'GH_API_RES<<EOF'
curl --fail-with-body --no-progress-meter -L -X PATCH \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.FHE_ACTIONS_TOKEN }}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
${{ fromJson(env.TARGET_REPO_PR).url }} \
-d '{ "state": "closed" }'
RES="$?"
echo EOF
} >> "${GITHUB_ENV}"
exit $RES
- name: Delete the associated branch in the data repo
env:
PR_BRANCH: ${{ github.head_ref || github.ref_name }}
run: |
{
set +e
echo 'GH_API_RES<<EOF'
curl --fail-with-body --no-progress-meter -L -X DELETE \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.FHE_ACTIONS_TOKEN }}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
${{ env.TARGET_REPO_API_URL }}/git/refs/heads/${{ env.PR_BRANCH }}
RES="$?"
echo EOF
} >> "${GITHUB_ENV}"
exit $RES
- name: Slack Notification
if: ${{ always() && job.status == 'failure' }}
continue-on-error: true
uses: rtCamp/action-slack-notify@4e5fb42d249be6a45a298f3c9543b111b02f7907
env:
SLACK_COLOR: ${{ job.status }}
SLACK_MESSAGE: "Failed to auto-close PR on data repo: ${{ fromJson(env.GH_API_RES).message }}"

View File

@@ -756,6 +756,10 @@ test_backward_compatibility_ci: install_rs_build_toolchain
.PHONY: test_backward_compatibility # Same as test_backward_compatibility_ci but tries to clone the data repo first if needed
test_backward_compatibility: tfhe/$(BACKWARD_COMPAT_DATA_DIR) test_backward_compatibility_ci
.PHONY: backward_compat_branch # Prints the required backward compatibility branch
backward_compat_branch:
@echo "$(BACKWARD_COMPAT_DATA_BRANCH)"
.PHONY: doc # Build rust doc
doc: install_rs_check_toolchain
@# Even though we are not in docs.rs, this allows to "just" build the doc

View File

@@ -5,20 +5,22 @@ use tfhe::backward_compatibility::booleans::{CompactFheBool, CompactFheBoolList}
use tfhe::backward_compatibility::integers::{
CompactFheInt8, CompactFheInt8List, CompactFheUint8, CompactFheUint8List,
};
use tfhe::prelude::{FheDecrypt, FheEncrypt};
use tfhe::shortint::PBSParameters;
use tfhe::{
set_server_key, ClientKey, CompactCiphertextList, CompressedCompactPublicKey,
CompressedFheBool, CompressedFheInt8, CompressedFheUint8, CompressedPublicKey,
CompressedServerKey, FheUint8,
set_server_key, ClientKey, CompactCiphertextList, CompressedCiphertextList,
CompressedCompactPublicKey, CompressedFheBool, CompressedFheInt8, CompressedFheUint8,
CompressedPublicKey, CompressedServerKey, FheBool, FheInt8, FheUint8,
};
use tfhe_backward_compat_data::load::{
load_versioned_auxiliary, DataFormat, TestFailure, TestResult, TestSuccess,
};
use tfhe_backward_compat_data::{
HlBoolCiphertextListTest, HlBoolCiphertextTest, HlCiphertextListTest, HlCiphertextTest,
HlClientKeyTest, HlPublicKeyTest, HlServerKeyTest, HlSignedCiphertextListTest,
HlSignedCiphertextTest, TestMetadata, TestParameterSet, TestType, Testcase,
DataKind, HlBoolCiphertextListTest, HlBoolCiphertextTest, HlCiphertextListTest,
HlCiphertextTest, HlClientKeyTest, HlHeterogeneousCiphertextListTest, HlPublicKeyTest,
HlServerKeyTest, HlSignedCiphertextListTest, HlSignedCiphertextTest, TestMetadata,
TestParameterSet, TestType, Testcase,
};
use tfhe_versionable::Unversionize;
@@ -257,6 +259,129 @@ pub fn test_hl_bool_ciphertext_list(
}
}
/// Test HL ciphertext list: loads the ciphertext list and compare the decrypted values to the ones
/// in the metadata.
pub fn test_hl_heterogeneous_ciphertext_list(
dir: &Path,
test: &HlHeterogeneousCiphertextListTest,
format: DataFormat,
) -> Result<TestSuccess, TestFailure> {
let key_file = dir.join(&*test.key_filename);
let key = ClientKey::unversionize(
load_versioned_auxiliary(key_file).map_err(|e| test.failure(e, format))?,
)
.map_err(|e| test.failure(e, format))?;
let server_key = key.generate_server_key();
set_server_key(server_key);
if test.compressed {
test_hl_heterogeneous_ciphertext_list_compressed(
load_and_unversionize(dir, test, format)?,
&key,
test,
)
} else {
test_hl_heterogeneous_ciphertext_list_compact(
load_and_unversionize(dir, test, format)?,
&key,
test,
)
}
.map(|_| test.success(format))
.map_err(|msg| test.failure(msg, format))
}
pub fn test_hl_heterogeneous_ciphertext_list_compact(
list: CompactCiphertextList,
key: &ClientKey,
test: &HlHeterogeneousCiphertextListTest,
) -> Result<(), String> {
let ct_list = list.expand().unwrap();
for idx in 0..(ct_list.len()) {
match test.data_kinds[idx] {
DataKind::Bool => {
let ct: FheBool = ct_list.get(idx).unwrap().unwrap();
let clear = ct.decrypt(key);
if clear != (test.clear_values[idx] != 0) {
return Err(format!(
"Invalid decrypted cleartext:\n Expected :\n{:?}\nGot:\n{:?}",
clear, test.clear_values[idx]
));
}
}
DataKind::Signed => {
let ct: FheInt8 = ct_list.get(idx).unwrap().unwrap();
let clear: i8 = ct.decrypt(key);
if clear != test.clear_values[idx] as i8 {
return Err(format!(
"Invalid decrypted cleartext:\n Expected :\n{:?}\nGot:\n{:?}",
clear,
(test.clear_values[idx] as i8)
));
}
}
DataKind::Unsigned => {
let ct: FheUint8 = ct_list.get(idx).unwrap().unwrap();
let clear: u8 = ct.decrypt(key);
if clear != test.clear_values[idx] as u8 {
return Err(format!(
"Invalid decrypted cleartext:\n Expected :\n{:?}\nGot:\n{:?}",
clear, test.clear_values[idx]
));
}
}
};
}
Ok(())
}
pub fn test_hl_heterogeneous_ciphertext_list_compressed(
list: CompressedCiphertextList,
key: &ClientKey,
test: &HlHeterogeneousCiphertextListTest,
) -> Result<(), String> {
let ct_list = list;
for idx in 0..(ct_list.len()) {
match test.data_kinds[idx] {
DataKind::Bool => {
let ct: FheBool = ct_list.get(idx).unwrap().unwrap();
let clear = ct.decrypt(key);
if clear != (test.clear_values[idx] != 0) {
return Err(format!(
"Invalid decrypted cleartext:\n Expected :\n{:?}\nGot:\n{:?}",
clear, test.clear_values[idx]
));
}
}
DataKind::Signed => {
let ct: FheInt8 = ct_list.get(idx).unwrap().unwrap();
let clear: i8 = ct.decrypt(key);
if clear != test.clear_values[idx] as i8 {
return Err(format!(
"Invalid decrypted cleartext:\n Expected :\n{:?}\nGot:\n{:?}",
clear,
(test.clear_values[idx] as i8)
));
}
}
DataKind::Unsigned => {
let ct: FheUint8 = ct_list.get(idx).unwrap().unwrap();
let clear: u8 = ct.decrypt(key);
if clear != test.clear_values[idx] as u8 {
return Err(format!(
"Invalid decrypted cleartext:\n Expected :\n{:?}\nGot:\n{:?}",
clear, test.clear_values[idx]
));
}
}
};
}
Ok(())
}
/// Test HL client key: loads the key and checks the parameters using the values stored in
/// the test metadata.
pub fn test_hl_clientkey(
@@ -333,8 +458,8 @@ pub fn test_hl_pubkey(
}
}
/// Test HL server key: encrypt to values with a client key, add them using the server key and check
/// that the decrypted sum is valid.
/// Test HL server key: encrypt two values with a client key, add them using the server key and
/// check that the decrypted sum is valid.
pub fn test_hl_serverkey(
dir: &Path,
test: &HlServerKeyTest,
@@ -376,6 +501,49 @@ pub fn test_hl_serverkey(
}
}
// /// Test HL KeySwitchingKey: encrypt a value with the source key, perform a keyswitch with the
// ksk /// and try to decrypt the value with the destination key.
// pub fn test_hl_ksk(
// dir: &Path,
// test: &HlKskTest,
// format: DataFormat,
// ) -> Result<TestSuccess, TestFailure> {
// let client_key_file = dir.join(&*test.client_key_filename);
// let client_key = ClientKey::unversionize(
// load_versioned_auxiliary(client_key_file).map_err(|e| test.failure(e, format))?,
// )
// .map_err(|e| test.failure(e, format))?;
// let v1 = 73u8;
// let ct1 = FheUint8::encrypt(v1, &client_key);
// let v2 = 102u8;
// let ct2 = FheUint8::encrypt(v2, &client_key);
// let key = if test.compressed {
// let compressed: CompressedServerKey = load_and_unversionize(dir, test, format)?;
// compressed.decompress()
// } else {
// load_and_unversionize(dir, test, format)?
// };
// set_server_key(key);
// let ct_sum = ct1 + ct2;
// let sum: u8 = ct_sum.decrypt(&client_key);
// if sum != v1 + v2 {
// Err(test.failure(
// format!(
// "Invalid result for addition using loaded server key, expected {} got {}",
// v1 + v2,
// sum,
// ),
// format,
// ))
// } else {
// Ok(test.success(format))
// }
// }
pub struct Hl;
impl TestedModule for Hl {
@@ -406,6 +574,9 @@ impl TestedModule for Hl {
TestMetadata::HlSignedCiphertextList(test) => {
test_hl_signed_ciphertext_list(test_dir.as_ref(), test, format).into()
}
TestMetadata::HlHeterogeneousCiphertextList(test) => {
test_hl_heterogeneous_ciphertext_list(test_dir.as_ref(), test, format).into()
}
TestMetadata::HlClientKey(test) => {
test_hl_clientkey(test_dir.as_ref(), test, format).into()
}