From e6e574607fbd718e72e7c6f7ea58c32d9565ae00 Mon Sep 17 00:00:00 2001 From: Carlos Segarra Date: Sun, 7 Dec 2025 23:08:44 +0000 Subject: [PATCH 1/5] [accless] F: Add CP-ABE Hybrid Wrappers --- Cargo.lock | 26 +++- Cargo.toml | 2 + accless/libs/abe4/Cargo.toml | 3 + accless/libs/abe4/src/hybrid.rs | 186 ++++++++++++++++++++++++++ accless/libs/abe4/src/lib.rs | 2 + accless/libs/abe4/src/scheme/types.rs | 2 +- accless/libs/abe4/tests/api_tests.rs | 91 ++++++++++++- 7 files changed, 303 insertions(+), 9 deletions(-) create mode 100644 accless/libs/abe4/src/hybrid.rs diff --git a/Cargo.lock b/Cargo.lock index f25b910b..2a7da562 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,6 +6,7 @@ version = 4 name = "abe4" version = "0.9.2" dependencies = [ + "aes-gcm", "anyhow", "ark-bls12-381", "ark-ec", @@ -13,11 +14,13 @@ dependencies = [ "ark-serialize", "ark-std", "base64 0.22.1", + "hkdf", "log", "rand 0.8.5", "serde", "serde_json", "sha2", + "zeroize", ] [[package]] @@ -1394,7 +1397,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1571,7 +1574,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2043,6 +2046,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" @@ -2501,7 +2513,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3551,7 +3563,7 @@ dependencies = [ "once_cell", "socket2 0.6.1", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -3863,7 +3875,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -4504,7 +4516,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -5244,7 +5256,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index b037cea2..3d19e10f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ env_logger = "0.11.8" futures = "^0.3" futures-util = "0.3" hex = "0.4.3" +hkdf = "0.12.4" hyper = { version = "1.6.0" } hyper-util = { version = "0.1" } indicatif = "^0.17" @@ -81,3 +82,4 @@ uuid = { version = "^1.3" } walkdir = "2" warp = "^0.3" x509-parser = { version = "0.16.0" } +zeroize = "1.8.2" diff --git a/accless/libs/abe4/Cargo.toml b/accless/libs/abe4/Cargo.toml index 8a15b2fd..484e7942 100644 --- a/accless/libs/abe4/Cargo.toml +++ b/accless/libs/abe4/Cargo.toml @@ -14,6 +14,7 @@ crate-type=["rlib", "staticlib"] path = "src/lib.rs" [dependencies] +aes-gcm = { workspace = true, features = ["aes"] } anyhow.workspace = true ark-bls12-381.workspace = true ark-ec.workspace = true @@ -21,8 +22,10 @@ ark-ff.workspace = true ark-std = { workspace = true, features = ["std"] } ark-serialize = { workspace = true, features = ["derive"] } base64.workspace = true +hkdf.workspace = true log.workspace = true rand = { workspace = true, features = ["std", "std_rng"] } serde = { workspace = true, features = ["derive"] } serde_json.workspace = true sha2.workspace = true +zeroize.workspace = true diff --git a/accless/libs/abe4/src/hybrid.rs b/accless/libs/abe4/src/hybrid.rs new file mode 100644 index 00000000..75de2c91 --- /dev/null +++ b/accless/libs/abe4/src/hybrid.rs @@ -0,0 +1,186 @@ +//! This module implements the hybrid CP-ABE scheme that derives a CP-ABE scheme +//! from a CP-ABE Key Encapsulation Mechanism and a symmetric encryption +//! function. We use AES-GCM-128 as that is all the randomness we can get out +//! of our CP-ABEKEM scheme. + +use crate::{Ciphertext, Gt, Iota, MPK, Policy, Tau, USK, decrypt, encrypt}; +use aes_gcm::{ + Aes128Gcm, Nonce, + aead::{Aead, KeyInit, Payload}, +}; +use anyhow::Result; +use ark_serialize::CanonicalSerialize; +use ark_std::rand::{CryptoRng, RngCore}; +use hkdf::Hkdf; +use log::error; +use sha2::Sha256; +use zeroize::Zeroize; + +const ABE4_KDF_SALT: &[u8] = b"accless-abe4-kem-salt"; +const ABE4_KDF_INFO: &[u8] = b"accless-abe4-aes-gcm-128"; + +#[derive(Clone)] +pub struct HybridCiphertext { + /// CP-ABE ciphertext. + pub abe_ct: Ciphertext, + /// Symmetric ciphertext CTsym = nonce || AES-GCM ciphertext+tag. + pub sym_ct: Vec, +} + +impl HybridCiphertext { + pub fn new(abe_ct: Ciphertext, sym_ct: Vec) -> Self { + Self { abe_ct, sym_ct } + } +} + +/// Derive an AES128 key from an element in Gt. +/// +/// gt is, precisely, what we get after a successful call to encrypt of a +/// CP-ABEKEM scheme (i.e. the scheme we implement in the `scheme` module). +fn derive_aes128_key_from_gt(gt: &Gt) -> [u8; 16] { + let mut gt_bytes = Vec::new(); + // This should never fail for a valid group element + gt.serialize_compressed(&mut gt_bytes) + .expect("Gt serialization failed"); + + let hk = Hkdf::::new(Some(ABE4_KDF_SALT), >_bytes); + + let mut key = [0u8; 16]; + hk.expand(ABE4_KDF_INFO, &mut key) + .expect("HKDF expand failed"); + + // gt_bytes only holds public data, no need to zeroize, but we could: + gt_bytes.zeroize(); + + key +} + +/// Encrypt `plaintext` using AES-GCM-128 under a key derived from `gt`. +/// +/// # Returns +/// +/// A symmetrically encrypted CT as : nonce (12 bytes) || ciphertext + tag. +fn sym_encrypt_gt( + rng: &mut R, + gt: &Gt, + plaintext: &[u8], + aad: &[u8], +) -> Result> { + let mut key_bytes = derive_aes128_key_from_gt(gt); + let cipher = Aes128Gcm::new_from_slice(&key_bytes)?; + + // 96-bit nonce as recommended for GCM. + let mut nonce_bytes = [0u8; 12]; + rng.fill_bytes(&mut nonce_bytes); + let nonce = Nonce::from(nonce_bytes); + + let payload = Payload { + msg: plaintext, + aad, + }; + + let mut ct = cipher.encrypt(&nonce, payload).map_err(|e| { + let reason = format!("error running AES-128-GCM encryption (error={e:?})"); + error!("sym_encrypt_gt(): {reason}"); + anyhow::anyhow!(reason) + })?; + + // CTsym = nonce || ct. + let mut out = Vec::with_capacity(12 + ct.len()); + out.extend_from_slice(&nonce_bytes); + out.append(&mut ct); + + // Zeroize key material. + key_bytes.zeroize(); + + Ok(out) +} + +/// Decrypt `sym_ct` using AES-GCM-128 with key derived from `gt`. +/// Expects sym_ct = nonce (12 bytes) || ciphertext + tag. +fn sym_decrypt_gt(gt: &Gt, sym_ct: &[u8], aad: &[u8]) -> Result> { + if sym_ct.len() < 12 { + let reason = "ciphertext too short"; + error!("sym_decrypt_gt(): {reason}"); + anyhow::bail!(reason); + } + + let (nonce_bytes, ct_bytes) = sym_ct.split_at(12); + let nonce_arr: [u8; 12] = nonce_bytes.try_into().map_err(|e| { + let reason = format!("ciphertext too short for nonce (error={e:?})"); + error!("sym_decrypt_gt(): {reason}"); + anyhow::anyhow!(reason) + })?; + let nonce = Nonce::from(nonce_arr); + + let mut key_bytes = derive_aes128_key_from_gt(gt); + let cipher = Aes128Gcm::new_from_slice(&key_bytes)?; + + let payload = Payload { msg: ct_bytes, aad }; + + let pt = cipher.decrypt(&nonce, payload).map_err(|e| { + let reason = format!("error running AES-128-GCM decryption (error={e:?})"); + error!("sym_decrypt_gt(): {reason}"); + anyhow::anyhow!(reason) + })?; + + key_bytes.zeroize(); + + Ok(pt) +} + +/// Hybrid CP-ABE + AES-GCM encryption. +/// +/// This is the ABE.Encrypt from Appendix A.3, instantiated with: +/// - The CP-ABE encryption method in `scheme` -> opt4 from Abe-Cubed. +/// - A symmetric encryption scheme -> AES-GCM-128 +/// - KDF -> Rely on the HKDF crate. +pub fn encrypt_hybrid( + rng: &mut R, + mpk: &MPK, + policy: &Policy, + plaintext: &[u8], + aad: &[u8], +) -> Result { + let tau = Tau::new(policy); + + // Encapsulate: (CTA, K) where K = Gt + let (gt, abe_ct) = encrypt(&mut *rng, mpk, policy, &tau); + + // Symmetric encryption under KDF(K) + let sym_ct = sym_encrypt_gt(rng, >, plaintext, aad)?; + + Ok(HybridCiphertext::new(abe_ct, sym_ct)) +} + +/// Hybrid CP-ABE + AES-GCM decryption. +/// +/// This is the ABE.Decrypt from Appendix A.3: +/// - K <- KEM.Decaps(...) +/// - M <- SE.SymDecrypt_{KDF(K)}(CTsym) +pub fn decrypt_hybrid( + usk: &USK, + gid: &str, + policy: &Policy, + abe_ct: &Ciphertext, + sym_ct: &[u8], + aad: &[u8], +) -> Result> { + let tau = Tau::new(policy); + let user_attrs = usk.get_user_attributes(); + let iota = Iota::new(&user_attrs); + + // KEM decapsulation step. + let gt_opt = decrypt(usk, gid, &iota, &tau, policy, abe_ct); + let gt = match gt_opt { + Some(g) => g, + None => { + let reason = "CP-ABE decryption failed"; + error!("decrypt_hybrid(): {reason}"); + anyhow::bail!(reason); + } + }; + + // Symmetric decryption under KDF(K) + sym_decrypt_gt(>, sym_ct, aad) +} diff --git a/accless/libs/abe4/src/lib.rs b/accless/libs/abe4/src/lib.rs index eac05c4a..2b589b0d 100644 --- a/accless/libs/abe4/src/lib.rs +++ b/accless/libs/abe4/src/lib.rs @@ -1,11 +1,13 @@ mod curve; mod hashing; +pub mod hybrid; pub mod policy; pub mod scheme; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use base64::engine::{Engine as _, general_purpose}; pub use curve::Gt; +pub use hybrid::{decrypt_hybrid, encrypt_hybrid}; pub use policy::{Policy, UserAttribute}; pub use scheme::{decrypt, encrypt, iota, keygen, setup, tau}; use scheme::{ diff --git a/accless/libs/abe4/src/scheme/types.rs b/accless/libs/abe4/src/scheme/types.rs index ab154270..58b2bdad 100644 --- a/accless/libs/abe4/src/scheme/types.rs +++ b/accless/libs/abe4/src/scheme/types.rs @@ -10,7 +10,7 @@ use std::collections::{HashMap, hash_map::Entry}; // ----------------------------------------------------------------------------------------------- /// Structure representing a cipher-text. -#[derive(PartialEq, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct Ciphertext { pub c_1_vec: Vec, pub c_2_vec: Vec, diff --git a/accless/libs/abe4/tests/api_tests.rs b/accless/libs/abe4/tests/api_tests.rs index 08b39a46..8b2d0e3b 100644 --- a/accless/libs/abe4/tests/api_tests.rs +++ b/accless/libs/abe4/tests/api_tests.rs @@ -1,4 +1,9 @@ -use abe4::{Gt, Policy, UserAttribute, decrypt, encrypt, iota::Iota, keygen, setup, tau::Tau}; +use abe4::{ + Gt, Policy, UserAttribute, decrypt, decrypt_hybrid, encrypt, encrypt_hybrid, iota::Iota, + keygen, setup, tau::Tau, +}; +use anyhow::Result; +use ark_std::rand::{SeedableRng, rngs::StdRng}; use std::collections::HashSet; const USER_ID: &str = "TEST_USER_ID"; @@ -47,6 +52,90 @@ pub fn assert_decryption_fail(user_attrs: Vec<&str>, policy: &str) { assert!(k_dec.is_none()); } +fn test_hybrid_scheme( + user_attrs: Vec<&str>, + policy: &str, + plaintext: &[u8], + aad: &[u8], +) -> Result> { + let (auths, user_attrs, policy) = prepare_test(&user_attrs, policy); + let mut rng = StdRng::seed_from_u64(0); + let auths: Vec<&str> = auths.iter().map(|s| s as &str).collect(); + let iota = Iota::new(&user_attrs); + let (msk, mpk) = setup(&mut rng, &auths); + let usk = keygen(&mut rng, USER_ID, &msk, &user_attrs, &iota); + + let hybrid_ct = encrypt_hybrid(&mut rng, &mpk, &policy, plaintext, aad)?; + decrypt_hybrid( + &usk, + USER_ID, + &policy, + &hybrid_ct.abe_ct, + &hybrid_ct.sym_ct, + aad, + ) +} + +#[test] +fn hybrid_round_trip_ok() { + let user_attrs = vec!["A.a:0", "A.c:1"]; + let policy = "A.a:0 & !A.c:0"; + let plaintext = b"hybrid plaintext payload"; + let aad = b"hybrid aad data"; + let decrypted = test_hybrid_scheme(user_attrs, policy, plaintext, aad) + .expect("hybrid decrypt_hybrid failed"); + assert_eq!(plaintext, decrypted.as_slice()); +} + +#[test] +fn hybrid_decrypt_fails_for_unauthorized_user() { + let user_attrs = vec![]; + let policy = "A.a:0"; + let plaintext = b"hybrid plaintext payload"; + let aad = b"hybrid aad data"; + let result = test_hybrid_scheme(user_attrs, policy, plaintext, aad); + assert!(result.is_err()); +} + +#[test] +fn hybrid_rejects_modified_aad() { + let user_attrs = vec!["A.a:0"]; + let policy = "A.a:0"; + let plaintext = b"hybrid plaintext payload"; + let aad = b"hybrid aad data"; + let wrong_aad = b"tampered aad"; + + let (auths, user_attrs, policy) = prepare_test(&user_attrs, policy); + let mut rng = StdRng::seed_from_u64(1); + let auths: Vec<&str> = auths.iter().map(|s| s as &str).collect(); + let iota = Iota::new(&user_attrs); + let (msk, mpk) = setup(&mut rng, &auths); + let usk = keygen(&mut rng, USER_ID, &msk, &user_attrs, &iota); + + let hybrid_ct = + encrypt_hybrid(&mut rng, &mpk, &policy, plaintext, aad).expect("encrypt_hybrid failed"); + let recovered = decrypt_hybrid( + &usk, + USER_ID, + &policy, + &hybrid_ct.abe_ct, + &hybrid_ct.sym_ct, + aad, + ) + .expect("decrypt_hybrid failed"); + assert_eq!(plaintext, recovered.as_slice()); + + let tampered = decrypt_hybrid( + &usk, + USER_ID, + &policy, + &hybrid_ct.abe_ct, + &hybrid_ct.sym_ct, + wrong_aad, + ); + assert!(tampered.is_err()); +} + // Handcrafted test cases (single auth) #[test] From 50b1573426deb71663f3be39db0790b738560ef0 Mon Sep 17 00:00:00 2001 From: Carlos Segarra Date: Sun, 7 Dec 2025 23:30:36 +0000 Subject: [PATCH 2/5] [accless] F: Add Hybrid Cpp Bindings --- accless/libs/abe4/cpp-bindings/abe4.cpp | 49 +++ accless/libs/abe4/cpp-bindings/abe4.h | 49 +++ accless/libs/abe4/cpp-bindings/api_tests.cpp | 92 +++++ accless/libs/abe4/src/lib.rs | 376 +++++++++++++++++++ 4 files changed, 566 insertions(+) diff --git a/accless/libs/abe4/cpp-bindings/abe4.cpp b/accless/libs/abe4/cpp-bindings/abe4.cpp index 2ec47de6..160ca489 100644 --- a/accless/libs/abe4/cpp-bindings/abe4.cpp +++ b/accless/libs/abe4/cpp-bindings/abe4.cpp @@ -127,6 +127,55 @@ std::optional decrypt(const std::string &usk, return gt_b64; } +namespace hybrid { + +EncryptOutput encrypt(const std::string &mpk, const std::string &policy, + const std::vector &plaintext, + const std::vector &aad) { + std::string plaintext_b64 = accless::base64::encode(plaintext); + std::string aad_b64 = accless::base64::encode(aad); + + char *result = encrypt_hybrid_abe4(mpk.c_str(), policy.c_str(), + plaintext_b64.c_str(), aad_b64.c_str()); + if (!result) { + std::cerr + << "accless(abe4): FFI call to encrypt_hybrid_abe4 failed. See " + "Rust logs for details." + << std::endl; + throw std::runtime_error( + "accless(abe4): encrypt_hybrid_abe4 FFI call failed"); + } + + auto result_json = nlohmann::json::parse(result); + free_string(result); + + return {result_json["abe_ct"], result_json["sym_ct"]}; +} + +std::optional> +decrypt(const std::string &usk, const std::string &gid, + const std::string &policy, const std::string &abe_ct, + const std::string &sym_ct, const std::vector &aad) { + std::string aad_b64 = accless::base64::encode(aad); + char *result = + decrypt_hybrid_abe4(usk.c_str(), gid.c_str(), policy.c_str(), + abe_ct.c_str(), sym_ct.c_str(), aad_b64.c_str()); + if (!result) { + std::cerr + << "accless(abe4): FFI call to decrypt_hybrid_abe4 failed. See " + "Rust logs for details." + << std::endl; + return std::nullopt; + } + + std::string plaintext_b64(result); + free_string(result); + + return accless::base64::decode(plaintext_b64); +} + +} // namespace hybrid + std::map> unpackFullKey(const std::vector &full_key_bytes) { std::map> result; diff --git a/accless/libs/abe4/cpp-bindings/abe4.h b/accless/libs/abe4/cpp-bindings/abe4.h index c81b44fa..a91b5acb 100644 --- a/accless/libs/abe4/cpp-bindings/abe4.h +++ b/accless/libs/abe4/cpp-bindings/abe4.h @@ -19,6 +19,11 @@ char *keygen_partial_abe4(const char *gid_cstr, const char *partial_msk_b64_cstr, const char *user_attrs_json); char *policy_authorities_abe4(const char *policy_str); +char *encrypt_hybrid_abe4(const char *mpk_b64, const char *policy_str, + const char *plaintext_b64, const char *aad_b64); +char *decrypt_hybrid_abe4(const char *usk_b64, const char *gid, + const char *policy_str, const char *abe_ct_b64, + const char *sym_ct_b64, const char *aad_b64); } // extern "C" @@ -208,4 +213,48 @@ std::string packFullKey(const std::vector &authorities, * @return A vector with all the attributes that appear in the policy. */ std::vector getPolicyAuthorities(const std::string &policy); + +namespace hybrid { +struct EncryptOutput { + std::string abe_ciphertext; + std::string sym_ciphertext; +}; + +/** + * @brief Encrypts plaintext and associated data using the hybrid CP-ABE scheme. + * + * This wrapper handles base64 encoding of the plaintext/AAD, calls the Rust + * FFI, and returns base64-encoded ciphertext components. + * + * @param mpk Base64-encoded master public key. + * @param policy Policy string. + * @param plaintext Plaintext bytes to encrypt. + * @param aad Associated data bound to the symmetric encryption. + * @return EncryptOutput containing the base64-encoded ABE and symmetric + * ciphertexts. + * @throws std::runtime_error on error. + */ +EncryptOutput encrypt(const std::string &mpk, const std::string &policy, + const std::vector &plaintext, + const std::vector &aad); + +/** + * @brief Decrypts a hybrid ciphertext using the provided keys and AAD. + * + * This wrapper calls the Rust FFI and base64-decodes the recovered plaintext. + * + * @param usk Base64-encoded user secret key. + * @param gid Group identifier. + * @param policy Policy string. + * @param abe_ct Base64-encoded ABE ciphertext. + * @param sym_ct Base64-encoded symmetric ciphertext. + * @param aad Associated data bound to the symmetric encryption. + * @return Optional plaintext bytes on success, or std::nullopt on failure. + */ +std::optional> +decrypt(const std::string &usk, const std::string &gid, + const std::string &policy, const std::string &abe_ct, + const std::string &sym_ct, const std::vector &aad); +} // namespace hybrid + } // namespace accless::abe4 diff --git a/accless/libs/abe4/cpp-bindings/api_tests.cpp b/accless/libs/abe4/cpp-bindings/api_tests.cpp index 98cfb1d7..9ef46cce 100644 --- a/accless/libs/abe4/cpp-bindings/api_tests.cpp +++ b/accless/libs/abe4/cpp-bindings/api_tests.cpp @@ -54,6 +54,31 @@ class Abe4ApiTest : public ::testing::Test { ASSERT_FALSE(decrypted_gt.has_value()); } + + void assert_hybrid_round_trip( + const std::vector &user_attrs, + const std::string &policy, const std::string &plaintext, + const std::string &aad) { + auto auths = gather_authorities(user_attrs, policy); + accless::abe4::SetupOutput setup_output = accless::abe4::setup(auths); + std::string gid = "test_gid"; + std::string usk_b64 = + accless::abe4::keygen(gid, setup_output.msk, user_attrs); + + std::vector plaintext_bytes(plaintext.begin(), + plaintext.end()); + std::vector aad_bytes(aad.begin(), aad.end()); + + auto hybrid_ct = accless::abe4::hybrid::encrypt( + setup_output.mpk, policy, plaintext_bytes, aad_bytes); + auto decrypted = accless::abe4::hybrid::decrypt( + usk_b64, gid, policy, hybrid_ct.abe_ciphertext, + hybrid_ct.sym_ciphertext, aad_bytes); + + ASSERT_TRUE(decrypted.has_value()); + std::string decrypted_str(decrypted->begin(), decrypted->end()); + EXPECT_EQ(plaintext, decrypted_str); + } }; TEST_F(Abe4ApiTest, SingleAuthSingleOk) { @@ -190,3 +215,70 @@ TEST_F(Abe4ApiTest, SimpleNegationOk) { std::string policy = "!A.c:2"; assert_decryption_ok(user_attrs, policy); } + +TEST_F(Abe4ApiTest, HybridRoundTripOk) { + std::vector user_attrs = { + {"A", "a", "0"}, + {"A", "c", "1"}, + }; + std::string policy = "A.a:0 & !A.c:0"; + std::string plaintext = "hybrid plaintext payload"; + std::string aad = "hybrid aad data"; + assert_hybrid_round_trip(user_attrs, policy, plaintext, aad); +} + +TEST_F(Abe4ApiTest, HybridDecryptFailsForUnauthorizedUser) { + std::vector user_attrs = {}; + std::string policy = "A.a:0"; + std::string plaintext = "hybrid plaintext payload"; + std::string aad = "hybrid aad data"; + + auto auths = gather_authorities(user_attrs, policy); + accless::abe4::SetupOutput setup_output = accless::abe4::setup(auths); + std::string gid = "test_gid"; + std::string usk_b64 = + accless::abe4::keygen(gid, setup_output.msk, user_attrs); + + std::vector plaintext_bytes(plaintext.begin(), plaintext.end()); + std::vector aad_bytes(aad.begin(), aad.end()); + auto hybrid_ct = accless::abe4::hybrid::encrypt(setup_output.mpk, policy, + plaintext_bytes, aad_bytes); + + auto decrypted = accless::abe4::hybrid::decrypt( + usk_b64, gid, policy, hybrid_ct.abe_ciphertext, + hybrid_ct.sym_ciphertext, aad_bytes); + ASSERT_FALSE(decrypted.has_value()); +} + +TEST_F(Abe4ApiTest, HybridRejectsModifiedAad) { + std::vector user_attrs = {{"A", "a", "0"}}; + std::string policy = "A.a:0"; + std::string plaintext = "hybrid plaintext payload"; + std::string aad = "hybrid aad data"; + std::string wrong_aad = "tampered aad"; + + auto auths = gather_authorities(user_attrs, policy); + accless::abe4::SetupOutput setup_output = accless::abe4::setup(auths); + std::string gid = "test_gid"; + std::string usk_b64 = + accless::abe4::keygen(gid, setup_output.msk, user_attrs); + + std::vector plaintext_bytes(plaintext.begin(), plaintext.end()); + std::vector aad_bytes(aad.begin(), aad.end()); + std::vector wrong_aad_bytes(wrong_aad.begin(), wrong_aad.end()); + + auto hybrid_ct = accless::abe4::hybrid::encrypt(setup_output.mpk, policy, + plaintext_bytes, aad_bytes); + + auto decrypted = accless::abe4::hybrid::decrypt( + usk_b64, gid, policy, hybrid_ct.abe_ciphertext, + hybrid_ct.sym_ciphertext, aad_bytes); + ASSERT_TRUE(decrypted.has_value()); + std::string decrypted_str(decrypted->begin(), decrypted->end()); + EXPECT_EQ(plaintext, decrypted_str); + + auto tampered = accless::abe4::hybrid::decrypt( + usk_b64, gid, policy, hybrid_ct.abe_ciphertext, + hybrid_ct.sym_ciphertext, wrong_aad_bytes); + ASSERT_FALSE(tampered.has_value()); +} diff --git a/accless/libs/abe4/src/lib.rs b/accless/libs/abe4/src/lib.rs index 2b589b0d..fd41e933 100644 --- a/accless/libs/abe4/src/lib.rs +++ b/accless/libs/abe4/src/lib.rs @@ -722,3 +722,379 @@ pub unsafe extern "C" fn decrypt_abe4( } } } + +#[derive(Serialize, Deserialize)] +struct HybridEncryptOutput { + abe_ct: String, + sym_ct: String, +} + +/// # Description +/// +/// FFI wrapper for the hybrid CP-ABE encryption function. +/// +/// This function takes a base64-encoded master public key, a policy string, a +/// base64-encoded plaintext, and a base64-encoded AAD. It encrypts the +/// plaintext using the hybrid scheme (CP-ABE + AES-GCM-128) and returns the +/// base64-encoded ABE ciphertext together with the base64-encoded symmetric +/// ciphertext. +/// +/// # Arguments +/// +/// * `mpk_b64`: A C-style string containing the base64-encoded master public +/// key. +/// * `policy_str`: A C-style string containing the policy string. +/// * `plaintext_b64`: A C-style string containing the base64-encoded plaintext +/// to encrypt. +/// * `aad_b64`: A C-style string containing the base64-encoded AAD to bind to +/// the symmetric encryption. +/// +/// # Returns +/// +/// A C-style string containing a JSON object with two fields: +/// - `abe_ct`: The base64-encoded ABE ciphertext. +/// - `sym_ct`: The base64-encoded symmetric ciphertext. +/// +/// Returns a null pointer on error. +#[allow(clippy::missing_safety_doc)] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn encrypt_hybrid_abe4( + mpk_b64: *const c_char, + policy_str: *const c_char, + plaintext_b64: *const c_char, + aad_b64: *const c_char, +) -> *mut c_char { + let mpk_b64_cstr = unsafe { CStr::from_ptr(mpk_b64) }; + let policy_cstr = unsafe { CStr::from_ptr(policy_str) }; + let plaintext_cstr = unsafe { CStr::from_ptr(plaintext_b64) }; + let aad_cstr = unsafe { CStr::from_ptr(aad_b64) }; + + let mpk_b64_str = match mpk_b64_cstr.to_str() { + Ok(s) => s, + Err(e) => { + eprintln!( + "[accless-abe4-rs] Failed to convert MPK C string to Rust string: {}", + e + ); + return std::ptr::null_mut(); + } + }; + + let mpk_bytes = match general_purpose::STANDARD.decode(mpk_b64_str) { + Ok(b) => b, + Err(e) => { + eprintln!("[accless-abe4-rs] Failed to decode MPK from base64: {}", e); + return std::ptr::null_mut(); + } + }; + + let mpk: MPK = match MPK::deserialize_compressed(&mpk_bytes[..]) { + Ok(m) => m, + Err(e) => { + eprintln!("[accless-abe4-rs] Failed to deserialize MPK: {}", e); + return std::ptr::null_mut(); + } + }; + + let policy_str = match policy_cstr.to_str() { + Ok(s) => s, + Err(e) => { + eprintln!( + "[accless-abe4-rs] Failed to convert policy C string to Rust string: {}", + e + ); + return std::ptr::null_mut(); + } + }; + + let policy = match Policy::parse(policy_str) { + Ok(p) => p, + Err(e) => { + eprintln!("[accless-abe4-rs] Failed to parse policy: {:?}", e); + return std::ptr::null_mut(); + } + }; + + let plaintext_b64_str = match plaintext_cstr.to_str() { + Ok(s) => s, + Err(e) => { + eprintln!( + "[accless-abe4-rs] Failed to convert plaintext C string to Rust string: {}", + e + ); + return std::ptr::null_mut(); + } + }; + + let plaintext = match general_purpose::STANDARD.decode(plaintext_b64_str) { + Ok(b) => b, + Err(e) => { + eprintln!( + "[accless-abe4-rs] Failed to decode plaintext from base64: {}", + e + ); + return std::ptr::null_mut(); + } + }; + + let aad_b64_str = match aad_cstr.to_str() { + Ok(s) => s, + Err(e) => { + eprintln!( + "[accless-abe4-rs] Failed to convert AAD C string to Rust string: {}", + e + ); + return std::ptr::null_mut(); + } + }; + + let aad = match general_purpose::STANDARD.decode(aad_b64_str) { + Ok(b) => b, + Err(e) => { + eprintln!("[accless-abe4-rs] Failed to decode AAD from base64: {}", e); + return std::ptr::null_mut(); + } + }; + + let mut rng = ark_std::rand::thread_rng(); + let hybrid_ct = match encrypt_hybrid(&mut rng, &mpk, &policy, &plaintext, &aad) { + Ok(ct) => ct, + Err(e) => { + eprintln!("[accless-abe4-rs] encrypt_hybrid failed: {}", e); + return std::ptr::null_mut(); + } + }; + + let mut abe_ct_bytes = Vec::new(); + if hybrid_ct + .abe_ct + .serialize_compressed(&mut abe_ct_bytes) + .is_err() + { + eprintln!("[accless-abe4-rs] Failed to serialize HybridCiphertext ABE part"); + return std::ptr::null_mut(); + } + + let output = HybridEncryptOutput { + abe_ct: general_purpose::STANDARD.encode(&abe_ct_bytes), + sym_ct: general_purpose::STANDARD.encode(&hybrid_ct.sym_ct), + }; + + let output_json = match serde_json::to_string(&output) { + Ok(s) => s, + Err(e) => { + eprintln!( + "[accless-abe4-rs] Failed to serialize hybrid output to JSON: {}", + e + ); + return std::ptr::null_mut(); + } + }; + + match CString::new(output_json) { + Ok(s) => s.into_raw(), + Err(e) => { + eprintln!( + "[accless-abe4-rs] Failed to create CString for hybrid output: {}", + e + ); + std::ptr::null_mut() + } + } +} + +/// # Description +/// +/// FFI wrapper for the hybrid CP-ABE decryption function. +/// +/// This function takes a base64-encoded user secret key, a global identifier, a +/// policy string, a base64-encoded ABE ciphertext, a base64-encoded symmetric +/// ciphertext, and a base64-encoded AAD. It attempts to decrypt the ciphertext +/// to recover the original plaintext. +/// +/// # Arguments +/// +/// * `usk_b64`: A C-style string containing the base64-encoded user secret key. +/// * `gid`: A C-style string containing the global identifier of the user. +/// * `policy_str`: A C-style string containing the policy string. +/// * `abe_ct_b64`: A C-style string containing the base64-encoded ABE +/// ciphertext. +/// * `sym_ct_b64`: A C-style string containing the base64-encoded symmetric +/// ciphertext. +/// * `aad_b64`: A C-style string containing the base64-encoded AAD bound to the +/// symmetric encryption. +/// +/// # Returns +/// +/// A C-style string containing the base64-encoded plaintext if decryption is +/// successful, or a null pointer otherwise. +#[allow(clippy::missing_safety_doc)] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn decrypt_hybrid_abe4( + usk_b64: *const c_char, + gid: *const c_char, + policy_str: *const c_char, + abe_ct_b64: *const c_char, + sym_ct_b64: *const c_char, + aad_b64: *const c_char, +) -> *mut c_char { + let usk_b64_cstr = unsafe { CStr::from_ptr(usk_b64) }; + let gid_cstr = unsafe { CStr::from_ptr(gid) }; + let policy_cstr = unsafe { CStr::from_ptr(policy_str) }; + let abe_ct_cstr = unsafe { CStr::from_ptr(abe_ct_b64) }; + let sym_ct_cstr = unsafe { CStr::from_ptr(sym_ct_b64) }; + let aad_cstr = unsafe { CStr::from_ptr(aad_b64) }; + + let usk_b64_str = match usk_b64_cstr.to_str() { + Ok(s) => s, + Err(e) => { + eprintln!( + "[accless-abe4-rs] Failed to convert USK C string to Rust string: {}", + e + ); + return std::ptr::null_mut(); + } + }; + + let usk_bytes = match general_purpose::STANDARD.decode(usk_b64_str) { + Ok(b) => b, + Err(e) => { + eprintln!("[accless-abe4-rs] Failed to decode USK from base64: {}", e); + return std::ptr::null_mut(); + } + }; + + let usk: USK = match USK::deserialize_compressed(&usk_bytes[..]) { + Ok(u) => u, + Err(e) => { + eprintln!("[accless-abe4-rs] Failed to deserialize USK: {}", e); + return std::ptr::null_mut(); + } + }; + + let policy_str_rs = match policy_cstr.to_str() { + Ok(s) => s, + Err(e) => { + eprintln!( + "[accless-abe4-rs] Failed to convert policy C string to Rust string: {}", + e + ); + return std::ptr::null_mut(); + } + }; + + let policy = match Policy::parse(policy_str_rs) { + Ok(p) => p, + Err(e) => { + eprintln!("[accless-abe4-rs] Failed to parse policy: {:?}", e); + return std::ptr::null_mut(); + } + }; + + let abe_ct_b64_str = match abe_ct_cstr.to_str() { + Ok(s) => s, + Err(e) => { + eprintln!( + "[accless-abe4-rs] Failed to convert ABE Ciphertext C string to Rust string: {}", + e + ); + return std::ptr::null_mut(); + } + }; + + let abe_ct_bytes = match general_purpose::STANDARD.decode(abe_ct_b64_str) { + Ok(b) => b, + Err(e) => { + eprintln!( + "[accless-abe4-rs] Failed to decode ABE Ciphertext from base64: {}", + e + ); + return std::ptr::null_mut(); + } + }; + + let abe_ct: Ciphertext = match Ciphertext::deserialize_compressed(&abe_ct_bytes[..]) { + Ok(c) => c, + Err(e) => { + eprintln!( + "[accless-abe4-rs] Failed to deserialize ABE Ciphertext: {}", + e + ); + return std::ptr::null_mut(); + } + }; + + let sym_ct_b64_str = match sym_ct_cstr.to_str() { + Ok(s) => s, + Err(e) => { + eprintln!( + "[accless-abe4-rs] Failed to convert symmetric Ciphertext C string to Rust string: {}", + e + ); + return std::ptr::null_mut(); + } + }; + + let sym_ct = match general_purpose::STANDARD.decode(sym_ct_b64_str) { + Ok(b) => b, + Err(e) => { + eprintln!( + "[accless-abe4-rs] Failed to decode symmetric Ciphertext from base64: {}", + e + ); + return std::ptr::null_mut(); + } + }; + + let aad_b64_str = match aad_cstr.to_str() { + Ok(s) => s, + Err(e) => { + eprintln!( + "[accless-abe4-rs] Failed to convert AAD C string to Rust string: {}", + e + ); + return std::ptr::null_mut(); + } + }; + + let aad = match general_purpose::STANDARD.decode(aad_b64_str) { + Ok(b) => b, + Err(e) => { + eprintln!("[accless-abe4-rs] Failed to decode AAD from base64: {}", e); + return std::ptr::null_mut(); + } + }; + + let gid_str = match gid_cstr.to_str() { + Ok(s) => s, + Err(e) => { + eprintln!( + "[accless-abe4-rs] Failed to convert GID C string to Rust string: {}", + e + ); + return std::ptr::null_mut(); + } + }; + + let result = decrypt_hybrid(&usk, gid_str, &policy, &abe_ct, &sym_ct, &aad); + + match result { + Ok(pt) => { + let pt_b64 = general_purpose::STANDARD.encode(&pt); + match CString::new(pt_b64) { + Ok(s) => s.into_raw(), + Err(e) => { + eprintln!( + "[accless-abe4-rs] Failed to create CString for plaintext: {}", + e + ); + std::ptr::null_mut() + } + } + } + Err(e) => { + eprintln!("[accless-abe4-rs] decrypt_hybrid failed: {}", e); + std::ptr::null_mut() + } + } +} From ec532732f7baca866c55248d35232fa98c9c8e19 Mon Sep 17 00:00:00 2001 From: Carlos Segarra Date: Mon, 8 Dec 2025 00:33:24 +0000 Subject: [PATCH 3/5] [accli] F: Add Decryption-Policy Benchmark --- Cargo.lock | 2 + accli/Cargo.toml | 2 + accli/src/main.rs | 12 +- accli/src/tasks/experiments/mod.rs | 17 ++ accli/src/tasks/experiments/plot.rs | 254 ++++++++++++++++++++++++- accli/src/tasks/experiments/profile.rs | 179 +++++++++++++++++ 6 files changed, 463 insertions(+), 3 deletions(-) create mode 100644 accli/src/tasks/experiments/profile.rs diff --git a/Cargo.lock b/Cargo.lock index 2a7da562..8ce13bc3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -91,7 +91,9 @@ dependencies = [ name = "accli" version = "0.9.2" dependencies = [ + "abe4", "anyhow", + "ark-std", "az-snp-vtpm", "base64 0.22.1", "bytes", diff --git a/accli/Cargo.toml b/accli/Cargo.toml index a9ae286b..2ad8567a 100644 --- a/accli/Cargo.toml +++ b/accli/Cargo.toml @@ -17,6 +17,8 @@ path = "src/lib.rs" [dependencies] anyhow.workspace = true +abe4.workspace = true +ark-std.workspace = true az-snp-vtpm.workspace = true base64.workspace = true bytes.workspace = true diff --git a/accli/src/main.rs b/accli/src/main.rs index f4cc8aaa..99446db6 100644 --- a/accli/src/main.rs +++ b/accli/src/main.rs @@ -8,7 +8,7 @@ use crate::{ cvm::{self, Component, parse_host_guest_path}, dev::Dev, docker::{Docker, DockerContainer}, - experiments::{self, E2eSubScommand, Experiment, UbenchSubCommand}, + experiments::{self, E2eSubScommand, Experiment, ProfileSubCommand, UbenchSubCommand}, s3::S3, }, }; @@ -587,6 +587,16 @@ async fn main() -> anyhow::Result<()> { tasks::experiments::plot::plot(exp)?; } }, + Experiment::PolicyDecryption { + profile_sub_command, + } => match profile_sub_command { + ProfileSubCommand::Run(run_args) => { + tasks::experiments::profile::run(run_args)?; + } + ProfileSubCommand::Plot {} => { + tasks::experiments::plot::plot(exp)?; + } + }, }, Command::S3 { s3_command } => match s3_command { S3Command::ClearBucket { bucket_name } => { diff --git a/accli/src/tasks/experiments/mod.rs b/accli/src/tasks/experiments/mod.rs index 664fc849..8e227e7a 100644 --- a/accli/src/tasks/experiments/mod.rs +++ b/accli/src/tasks/experiments/mod.rs @@ -6,6 +6,7 @@ pub mod baselines; pub mod color; pub mod e2e; pub mod plot; +pub mod profile; pub mod ubench; pub mod workflows; @@ -57,6 +58,11 @@ pub enum Experiment { #[command(subcommand)] ubench_sub_command: UbenchSubCommand, }, + /// Profile policy encryption/decryption latency as policy size grows + PolicyDecryption { + #[command(subcommand)] + profile_sub_command: ProfileSubCommand, + }, /// Evaluate the latency when scaling-up the number of functions in the /// workflow ScaleUpLatency { @@ -71,6 +77,7 @@ impl Experiment { pub const E2E_LATENCY_COLD_NAME: &'static str = "e2e-latency-cold"; pub const ESCROW_COST_NAME: &'static str = "escrow-cost"; pub const ESCROW_XPUT_NAME: &'static str = "escrow-xput"; + pub const POLICY_DECRYPTION_NAME: &'static str = "policy-decryption"; pub const SCALE_UP_LATENCY_NAME: &'static str = "scale-up-latency"; pub fn name(&self) -> &'static str { @@ -80,6 +87,7 @@ impl Experiment { Experiment::E2eLatencyCold { .. } => "e2e-latency-cold", Experiment::EscrowCost { .. } => "escrow-cost", Experiment::EscrowXput { .. } => "escrow-xput", + Experiment::PolicyDecryption { .. } => "policy-decryption", Experiment::ScaleUpLatency { .. } => "scale-up-latency", } } @@ -93,6 +101,7 @@ impl fmt::Display for Experiment { Experiment::E2eLatencyCold { .. } => write!(f, "{}", Self::E2E_LATENCY_COLD_NAME), Experiment::EscrowCost { .. } => write!(f, "{}", Self::ESCROW_COST_NAME), Experiment::EscrowXput { .. } => write!(f, "{}", Self::ESCROW_XPUT_NAME), + Experiment::PolicyDecryption { .. } => write!(f, "{}", Self::POLICY_DECRYPTION_NAME), Experiment::ScaleUpLatency { .. } => write!(f, "{}", Self::SCALE_UP_LATENCY_NAME), } } @@ -118,3 +127,11 @@ pub enum UbenchSubCommand { /// Plot Plot {}, } + +#[derive(Debug, Subcommand)] +pub enum ProfileSubCommand { + /// Run + Run(profile::ProfileRunArgs), + /// Plot + Plot {}, +} diff --git a/accli/src/tasks/experiments/plot.rs b/accli/src/tasks/experiments/plot.rs index 5ca9eb8f..53c4c63e 100644 --- a/accli/src/tasks/experiments/plot.rs +++ b/accli/src/tasks/experiments/plot.rs @@ -3,7 +3,7 @@ use crate::{ tasks::experiments::{ Experiment, baselines::{EscrowBaseline, SystemBaseline}, - color::{FONT_SIZE, STROKE_WIDTH}, + color::{FONT_SIZE, STROKE_WIDTH, get_color_from_label}, ubench::{REQUEST_COUNTS_MHSM, REQUEST_COUNTS_TRUSTEE}, workflows::Workflow, }, @@ -18,7 +18,7 @@ use std::{ env, fs::{self, File}, io::{BufRead, BufReader}, - path::PathBuf, + path::{Path, PathBuf}, str, }; @@ -1549,6 +1549,252 @@ fn plot_escrow_cost() { ); } +fn plot_policy_decryption(data_files: &Vec) -> Result<()> { + #[derive(Debug, Deserialize)] + struct Record { + #[serde(rename = "NumAuthorities")] + num_authorities: usize, + #[serde(rename = "EncryptMs")] + encrypt_ms: f64, + #[serde(rename = "DecryptMs")] + decrypt_ms: f64, + } + + #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] + enum PolicyShape { + Conjunction, + Disjunction, + } + + impl PolicyShape { + fn from_file(path: &Path) -> Option { + let stem = path.file_stem()?.to_str()?; + match stem { + "conjunction" => Some(Self::Conjunction), + "disjunction" => Some(Self::Disjunction), + _ => None, + } + } + + fn label(&self) -> &str { + match self { + Self::Conjunction => "all conjunction policy", + Self::Disjunction => "all disjunction policy", + } + } + + fn color(&self) -> Result { + match self { + Self::Conjunction => get_color_from_label("dark-red"), + Self::Disjunction => get_color_from_label("dark-blue"), + } + } + } + + let mut agg: BTreeMap> = BTreeMap::new(); + + for csv_file in data_files { + let Some(shape) = PolicyShape::from_file(csv_file) else { + continue; + }; + + let mut reader = ReaderBuilder::new().has_headers(true).from_path(csv_file)?; + + for result in reader.deserialize() { + let record: Record = result?; + let shape_map = agg.entry(shape).or_default(); + let entry = shape_map + .entry(record.num_authorities) + .or_insert((0.0, 0.0, 0)); + entry.0 += record.encrypt_ms; + entry.1 += record.decrypt_ms; + entry.2 += 1; + } + } + + if agg.is_empty() { + anyhow::bail!("no data files found for policy-decryption plot"); + } + + let mut averages: BTreeMap> = BTreeMap::new(); + for (shape, rows) in agg { + let mut shape_map = BTreeMap::new(); + for (num_auth, (enc_sum, dec_sum, count)) in rows { + if count == 0 { + continue; + } + shape_map.insert(num_auth, (enc_sum / count as f64, dec_sum / count as f64)); + } + averages.insert(shape, shape_map); + } + + let mut plot_dir = Env::experiments_root(); + plot_dir.push(Experiment::POLICY_DECRYPTION_NAME); + plot_dir.push("plots"); + fs::create_dir_all(&plot_dir)?; + + fn draw_chart( + plot_dir: &Path, + filename: &str, + y_label: &str, + selector: fn(&(f64, f64)) -> f64, + data: &BTreeMap>, + shape_in_legend: &PolicyShape, + ) -> Result<()> { + let plot_path = plot_dir.join(filename); + + let x_min: i32 = 1; + let mut x_max: i32 = 1; + let mut y_max: f64 = 0.0; + for rows in data.values() { + if let Some(max_x) = rows.keys().max() { + x_max = x_max.max(*max_x as i32); + } + for val in rows.values() { + y_max = y_max.max(selector(val)); + } + } + y_max = (y_max * 1.1).max(1.0); + + let root = SVGBackend::new(&plot_path, (400, 300)).into_drawing_area(); + root.fill(&WHITE)?; + + let mut chart = ChartBuilder::on(&root) + .x_label_area_size(40) + .y_label_area_size(40) + .margin(10) + .margin_top(40) + .margin_left(50) + .margin_right(25) + .margin_bottom(20) + .build_cartesian_2d(x_min..x_max, 0f64..y_max) + .unwrap(); + + chart + .configure_mesh() + .light_line_style(WHITE) + .x_labels(4) + .y_labels(4) + .x_label_style(("sans-serif", FONT_SIZE).into_font()) + .y_label_style(("sans-serif", FONT_SIZE).into_font()) + .draw() + .unwrap(); + + root.draw(&Text::new( + y_label, + (5, 220), + ("sans-serif", FONT_SIZE) + .into_font() + .transform(FontTransform::Rotate270) + .color(&BLACK), + ))?; + root.draw(&Text::new( + "# of attestation services", + (100, 275), + ("sans-serif", FONT_SIZE).into_font().color(&BLACK), + ))?; + + for (shape, rows) in data { + let series = rows.iter().map(|(x, vals)| (*x as i32, selector(vals))); + + // Draw line. + chart.draw_series(LineSeries::new( + series, + shape.color()?.stroke_width(STROKE_WIDTH), + ))?; + + // Draw points on line. + chart + .draw_series(rows.iter().map(|(x, vals)| { + Circle::new( + (*x as i32, selector(vals)), + 5, + shape.color().unwrap().filled(), + ) + })) + .unwrap(); + } + + // Add solid frames + chart + .plotting_area() + .draw(&PathElement::new( + vec![(x_min, y_max), (x_max, y_max)], + BLACK, + )) + .unwrap(); + chart + .plotting_area() + .draw(&PathElement::new( + vec![(x_max, 0_f64), (x_max, y_max)], + BLACK, + )) + .unwrap(); + + // legend as color box + text. + let x_pos = 100; + let y_pos = 5; + let square_side = 20; + root.draw(&Rectangle::new( + [(x_pos, y_pos), (x_pos + square_side, y_pos + square_side)], + shape_in_legend.color().unwrap().filled(), + )) + .unwrap(); + root.draw(&PathElement::new( + vec![(x_pos, y_pos), (x_pos + 20, y_pos)], + BLACK, + )) + .unwrap(); + root.draw(&PathElement::new( + vec![(x_pos + 20, y_pos), (x_pos + 20, y_pos + 20)], + BLACK, + )) + .unwrap(); + root.draw(&PathElement::new( + vec![(x_pos, y_pos), (x_pos, y_pos + 20)], + BLACK, + )) + .unwrap(); + root.draw(&PathElement::new( + vec![(x_pos, y_pos + 20), (x_pos + 20, y_pos + 20)], + BLACK, + )) + .unwrap(); + root.draw(&Text::new( + shape_in_legend.label(), + (x_pos + 30, y_pos + 2), + ("sans-serif", FONT_SIZE).into_font(), + )) + .unwrap(); + + root.present()?; + info!( + "plot_policy_decryption(): generated plot at: {}", + plot_path.display() + ); + Ok(()) + } + + draw_chart( + &plot_dir, + "policy-decryption-encrypt.svg", + "Latency [ms]", + |vals| vals.0, + &averages, + &PolicyShape::Conjunction, + )?; + draw_chart( + &plot_dir, + "policy-decryption-decrypt.svg", + "Latency [ms]", + |vals| vals.1, + &averages, + &PolicyShape::Disjunction, + )?; + + Ok(()) +} + pub fn plot(exp: &Experiment) -> Result<()> { match exp { Experiment::ColdStart { .. } => { @@ -1573,6 +1819,10 @@ pub fn plot(exp: &Experiment) -> Result<()> { let data_files = get_all_data_files(exp)?; plot_escrow_xput(&data_files); } + Experiment::PolicyDecryption { .. } => { + let data_files = get_all_data_files(exp)?; + plot_policy_decryption(&data_files)?; + } Experiment::ScaleUpLatency { .. } => { let data_files = get_all_data_files(exp)?; plot_scale_up_latency("faasm", &data_files); diff --git a/accli/src/tasks/experiments/profile.rs b/accli/src/tasks/experiments/profile.rs new file mode 100644 index 00000000..9b680939 --- /dev/null +++ b/accli/src/tasks/experiments/profile.rs @@ -0,0 +1,179 @@ +use crate::{env::Env, tasks::experiments::Experiment}; +use abe4::{ + Policy, UserAttribute, decrypt_hybrid, encrypt_hybrid, + iota::Iota, + keygen, + scheme::types::{MPK, USK}, + setup, +}; +use anyhow::Result; +use ark_std::rand::{SeedableRng, rngs::StdRng}; +use clap::Args; +use csv::Writer; +use indicatif::{ProgressBar, ProgressStyle}; +use log::{error, info}; +use std::{ + fs::{self, File}, + path::PathBuf, + time::Instant, +}; + +const USER_ID: &str = "policy-decryption-user"; +const WORKFLOW_ID: &str = "wf"; +const NODE_ID: &str = "node"; +const DEFAULT_PLAINTEXT: &[u8] = b"policy-decryption-payload"; +const DEFAULT_AAD: &[u8] = b"policy-decryption-aad"; +const POLICY_SIZES: &[usize] = &[1, 2, 4, 8, 12, 16]; + +#[derive(Debug, Args)] +pub struct ProfileRunArgs { + #[arg(long, default_value_t = 5)] + pub num_warmup_runs: u32, + #[arg(long, default_value_t = 20)] + pub num_runs: u32, +} + +#[derive(Clone, Copy)] +enum PolicyShape { + Conjunction, + Disjunction, +} + +impl PolicyShape { + fn file_stem(&self) -> &'static str { + match self { + PolicyShape::Conjunction => "conjunction", + PolicyShape::Disjunction => "disjunction", + } + } +} + +#[derive(serde::Serialize)] +struct Record { + #[serde(rename = "NumAuthorities")] + num_authorities: usize, + #[serde(rename = "Run")] + run: u32, + #[serde(rename = "EncryptMs")] + encrypt_ms: f64, + #[serde(rename = "DecryptMs")] + decrypt_ms: f64, +} + +fn build_authorities(num: usize) -> Vec { + (0..num).map(|idx| format!("as{idx:02}")).collect() +} + +fn build_user_attributes(authorities: &[String]) -> Result> { + let mut user_attrs = Vec::new(); + for auth in authorities { + user_attrs.push(UserAttribute::parse(&format!("{auth}.wf:{WORKFLOW_ID}"))?); + user_attrs.push(UserAttribute::parse(&format!("{auth}.node:{NODE_ID}"))?); + } + Ok(user_attrs) +} + +fn build_policy(authorities: &[String], shape: PolicyShape) -> Result { + let mut clauses = Vec::new(); + for auth in authorities { + clauses.push(format!("({auth}.wf:{WORKFLOW_ID} & {auth}.node:{NODE_ID})")); + } + + let op = match shape { + PolicyShape::Conjunction => " & ", + PolicyShape::Disjunction => " | ", + }; + let policy_str = clauses.join(op); + Policy::parse(&policy_str) +} + +fn ensure_data_dir() -> Result { + let mut data_dir = Env::experiments_root(); + data_dir.push(Experiment::POLICY_DECRYPTION_NAME); + data_dir.push("data"); + fs::create_dir_all(&data_dir)?; + Ok(data_dir) +} + +fn measure_single_run( + rng: &mut StdRng, + policy: &Policy, + usk: &USK, + mpk: &MPK, +) -> Result<(f64, f64)> { + let enc_start = Instant::now(); + let ct = encrypt_hybrid(rng, mpk, policy, DEFAULT_PLAINTEXT, DEFAULT_AAD)?; + let enc_ms = enc_start.elapsed().as_secs_f64() * 1_000.0; + + let dec_start = Instant::now(); + let pt = decrypt_hybrid(usk, USER_ID, policy, &ct.abe_ct, &ct.sym_ct, DEFAULT_AAD)?; + let dec_ms = dec_start.elapsed().as_secs_f64() * 1_000.0; + + if pt.as_slice() != DEFAULT_PLAINTEXT { + let reason = "decrypt_hybrid() returned unexpected plaintext"; + error!("{reason}"); + anyhow::bail!(reason); + } + + Ok((enc_ms, dec_ms)) +} + +fn run_shape(shape: PolicyShape, args: &ProfileRunArgs) -> Result<()> { + let data_dir = ensure_data_dir()?; + let mut csv_path = data_dir; + csv_path.push(format!("{}.csv", shape.file_stem())); + let mut writer = Writer::from_writer(File::create(csv_path)?); + let total_iters = (POLICY_SIZES.len() as u64) * (args.num_warmup_runs + args.num_runs) as u64; + let pb = ProgressBar::new(total_iters).with_message(shape.file_stem()); + pb.set_style( + ProgressStyle::with_template("{msg} [{bar:40.cyan/blue}] {pos}/{len}") + .unwrap() + .progress_chars("=>-"), + ); + + for &num_auth in POLICY_SIZES { + let authorities = build_authorities(num_auth); + let user_attrs = build_user_attributes(&authorities)?; + let auths_ref: Vec<&str> = authorities.iter().map(|s| s.as_str()).collect(); + + let mut rng = StdRng::seed_from_u64(1337 + num_auth as u64); + let (msk, mpk) = setup(&mut rng, &auths_ref); + + let iota = Iota::new(&user_attrs); + let usk = keygen(&mut rng, USER_ID, &msk, &user_attrs, &iota); + let policy = build_policy(&authorities, shape)?; + + for _ in 0..args.num_warmup_runs { + let _ = measure_single_run(&mut rng, &policy, &usk, &mpk)?; + pb.inc(1); + } + + for run_idx in 0..args.num_runs { + let (encrypt_ms, decrypt_ms) = measure_single_run(&mut rng, &policy, &usk, &mpk)?; + writer.serialize(Record { + num_authorities: num_auth, + run: run_idx, + encrypt_ms, + decrypt_ms, + })?; + pb.inc(1); + } + } + + pb.finish_and_clear(); + writer.flush()?; + + Ok(()) +} + +pub fn run(args: &ProfileRunArgs) -> Result<()> { + info!( + "Running policy-decryption profile (warmups={}, runs={})", + args.num_warmup_runs, args.num_runs + ); + + run_shape(PolicyShape::Conjunction, args)?; + run_shape(PolicyShape::Disjunction, args)?; + + Ok(()) +} From d7c1386c17b3973b532fdf75274025e65edfa1f3 Mon Sep 17 00:00:00 2001 From: Carlos Segarra Date: Mon, 8 Dec 2025 00:33:52 +0000 Subject: [PATCH 4/5] [experiments] E: Track Two Experiments --- .github/workflows/azure.yml | 23 -- experiments/escrow-cost/plots/escrow-cost.svg | 171 +++++++++++++++ experiments/escrow-xput/README.md | 52 +++++ experiments/escrow-xput/data/accless-maa.csv | 1 + .../escrow-xput/data/accless-single-auth.csv | 28 +++ experiments/escrow-xput/data/accless.csv | 28 +++ experiments/escrow-xput/data/managed-hsm.csv | 10 + experiments/escrow-xput/data/trustee.csv | 28 +++ experiments/escrow-xput/plots/escrow-xput.svg | 197 ++++++++++++++++++ .../policy-decryption/data/conjunction.csv | 121 +++++++++++ .../policy-decryption/data/disjunction.csv | 121 +++++++++++ .../plots/policy-decryption-decrypt.svg | 111 ++++++++++ .../plots/policy-decryption-encrypt.svg | 106 ++++++++++ 13 files changed, 974 insertions(+), 23 deletions(-) delete mode 100644 .github/workflows/azure.yml create mode 100644 experiments/escrow-cost/plots/escrow-cost.svg create mode 100644 experiments/escrow-xput/README.md create mode 100644 experiments/escrow-xput/data/accless-maa.csv create mode 100644 experiments/escrow-xput/data/accless-single-auth.csv create mode 100644 experiments/escrow-xput/data/accless.csv create mode 100644 experiments/escrow-xput/data/managed-hsm.csv create mode 100644 experiments/escrow-xput/data/trustee.csv create mode 100644 experiments/escrow-xput/plots/escrow-xput.svg create mode 100644 experiments/policy-decryption/data/conjunction.csv create mode 100644 experiments/policy-decryption/data/disjunction.csv create mode 100644 experiments/policy-decryption/plots/policy-decryption-decrypt.svg create mode 100644 experiments/policy-decryption/plots/policy-decryption-encrypt.svg diff --git a/.github/workflows/azure.yml b/.github/workflows/azure.yml deleted file mode 100644 index f1def1ea..00000000 --- a/.github/workflows/azure.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: "SNP End-to-End Tests" - -on: - push: - branches: [main] - workflow_dispatch: - -defaults: - run: - shell: bash - -# Cancel previous running actions for the same PR -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} - -jobs: - snp-vtpm-test: - runs-on: [self-hosted] - steps: - - name: "Check out the code" - uses: actions/checkout@v4 - diff --git a/experiments/escrow-cost/plots/escrow-cost.svg b/experiments/escrow-cost/plots/escrow-cost.svg new file mode 100644 index 00000000..5e4034d6 --- /dev/null +++ b/experiments/escrow-cost/plots/escrow-cost.svg @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +0 + + + +20 + + + +40 + + + +60 + + + + +2 + + + +4 + + + +6 + + + +8 + + + +10 + + + + +0 + + + +200 + + + +400 + + + +600 + + + +Latency [ms] + + +# of users + + +Op. Cost [$/month] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +accless + + + + +accless-single-auth + + diff --git a/experiments/escrow-xput/README.md b/experiments/escrow-xput/README.md new file mode 100644 index 00000000..0912c9fd --- /dev/null +++ b/experiments/escrow-xput/README.md @@ -0,0 +1,52 @@ +## Escrow Throughput-Latency + +This experiment compares the throughput-latency characteristic of secret-key +release operations of different trusted escrow solutions and Accless. + +As a workload, we run a synthetic function that .. + +### Managed HSM + +```bash +accli azure managed-hsm create +accli azure managed-hsm provision +accli experiments escrow-xput run --baseline managed-hsm +accli azure managed-hsm delete +``` + +### Trustee + +```bash +accli azure trustee create +accli azure trustee provision +accli experiments escrow-xput run --baseline trustee +accli azure trustee delete +``` + +### Accless + +```bash +accli azure accless create +accli azure accless provision +``` + +then run the experiments, which will automatically fetch the results: + +```bash +accli experiments escrow-xput run --baseline accless +accli experiments escrow-xput run --baseline accless-maa +``` + +you may finally delete the resources: + +```bash +accli azure accless delete +``` + +### Plot Results + +To plot the resulting file, run: + +```bash +accli experiments escrow-xput plot +``` diff --git a/experiments/escrow-xput/data/accless-maa.csv b/experiments/escrow-xput/data/accless-maa.csv new file mode 100644 index 00000000..cc7d55bf --- /dev/null +++ b/experiments/escrow-xput/data/accless-maa.csv @@ -0,0 +1 @@ +NumRequests,TimeElapsed diff --git a/experiments/escrow-xput/data/accless-single-auth.csv b/experiments/escrow-xput/data/accless-single-auth.csv new file mode 100644 index 00000000..4a933233 --- /dev/null +++ b/experiments/escrow-xput/data/accless-single-auth.csv @@ -0,0 +1,28 @@ +NumRequests,TimeElapsed +1,0.0242452 +1,0.0243396 +1,0.0246424 +20,0.172184 +20,0.124665 +20,0.126948 +60,0.340566 +60,0.351351 +60,0.344779 +80,0.454695 +80,0.455343 +80,0.456026 +100,0.563064 +100,0.587218 +100,0.560308 +120,0.66587 +120,0.662149 +120,0.687763 +160,0.883199 +160,0.881246 +160,1.30785 +180,0.985442 +180,1.03068 +180,1.00415 +200,1.09067 +200,1.09809 +200,1.13248 diff --git a/experiments/escrow-xput/data/accless.csv b/experiments/escrow-xput/data/accless.csv new file mode 100644 index 00000000..2e5167e3 --- /dev/null +++ b/experiments/escrow-xput/data/accless.csv @@ -0,0 +1,28 @@ +NumRequests,TimeElapsed +1,0.0267917 +1,0.0265605 +1,0.0265999 +20,0.087138 +20,0.072804 +20,0.0642252 +60,0.13568 +60,0.129551 +60,0.129706 +80,0.167159 +80,0.167641 +80,0.190896 +100,0.204224 +100,0.204807 +100,0.21435 +120,0.23868 +120,0.238464 +120,0.240172 +160,0.353183 +160,0.33133 +160,0.315093 +180,0.343078 +180,0.348604 +180,0.344798 +200,0.385426 +200,0.821663 +200,0.385529 diff --git a/experiments/escrow-xput/data/managed-hsm.csv b/experiments/escrow-xput/data/managed-hsm.csv new file mode 100644 index 00000000..23f72702 --- /dev/null +++ b/experiments/escrow-xput/data/managed-hsm.csv @@ -0,0 +1,10 @@ +NumRequests,TimeElapsed +1,1.314269347 +5,0.786503433 +10,1.655451277 +15,2.174563028 +20,6.268938593 +40,7.870532172 +60,7.689575529 +80,11.036012747 +100,15.564368402 diff --git a/experiments/escrow-xput/data/trustee.csv b/experiments/escrow-xput/data/trustee.csv new file mode 100644 index 00000000..84d4d44d --- /dev/null +++ b/experiments/escrow-xput/data/trustee.csv @@ -0,0 +1,28 @@ +NumRequests,TimeElapsed +1,0.044177865 +1,0.042625545 +1,0.04540268 +20,0.184266856 +20,0.191893154 +20,0.182393932 +60,0.527369144 +60,0.549946632 +60,0.497115057 +80,0.670101568 +80,0.67961169 +80,0.677797767 +100,0.878847938 +100,0.829340204 +100,0.84931306 +120,1.001534606 +120,1.004650646 +120,1.017170106 +160,1.337073397 +160,1.331343523 +160,1.335920282 +180,1.496965241 +180,1.504165733 +180,1.492571185 +200,1.6738263020000002 +200,1.663540071 +200,1.697506405 diff --git a/experiments/escrow-xput/plots/escrow-xput.svg b/experiments/escrow-xput/plots/escrow-xput.svg new file mode 100644 index 00000000..98171e34 --- /dev/null +++ b/experiments/escrow-xput/plots/escrow-xput.svg @@ -0,0 +1,197 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +0.0 + + + +0.5 + + + +1.0 + + + +1.5 + + + +2.0 + + + + +0 + + + +50 + + + +100 + + + +150 + + + +200 + + + +Latency [s] + + +Throughput [RPS] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +managed-hsm + + + + + + + +trustee + + diff --git a/experiments/policy-decryption/data/conjunction.csv b/experiments/policy-decryption/data/conjunction.csv new file mode 100644 index 00000000..979dac2f --- /dev/null +++ b/experiments/policy-decryption/data/conjunction.csv @@ -0,0 +1,121 @@ +NumAuthorities,Run,EncryptMs,DecryptMs +1,0,10.295616,6.7347850000000005 +1,1,10.164797,6.723604 +1,2,10.219607,6.727094999999999 +1,3,10.135097,6.724614 +1,4,9.974817,6.718335 +1,5,10.168216000000001,6.729765 +1,6,10.210376,6.719385 +1,7,10.102896999999999,6.718484 +1,8,10.073628,6.720674000000001 +1,9,10.144457000000001,6.734095 +1,10,10.214766000000001,6.736605 +1,11,10.189496,6.726135 +1,12,10.090127,6.732424 +1,13,10.210637,6.730574 +1,14,10.049908,6.719044 +1,15,10.196467,6.748784 +1,16,10.194837,6.724795 +1,17,10.014437,6.715984000000001 +1,18,10.101617000000001,6.7329039999999996 +1,19,10.050127000000002,6.732405 +2,0,17.012681,8.060830999999999 +2,1,16.768901999999997,8.067431 +2,2,16.937262,8.070191000000001 +2,3,16.958541,8.061662 +2,4,16.629701999999998,8.078011 +2,5,16.808291999999998,8.082321 +2,6,16.779122,8.064122 +2,7,17.14685,8.085682 +2,8,16.986141,8.073311 +2,9,16.977601,8.067182 +2,10,16.711872,8.067301 +2,11,17.064201,8.070542 +2,12,17.002921,8.062871 +2,13,17.106890999999997,8.074512 +2,14,16.844071,8.063652 +2,15,17.25877,8.068901 +2,16,16.876242,8.058941 +2,17,16.925141,8.080752 +2,18,16.938702,8.058291 +2,19,16.751062,8.071010999999999 +4,0,30.063302,10.740235 +4,1,30.563779999999998,10.729235000000001 +4,2,30.063302,10.736615 +4,3,30.278831,10.732555 +4,4,30.003881,10.740125999999998 +4,5,30.038001,10.742455 +4,6,30.991149,10.738416 +4,7,29.942491,10.733456 +4,8,30.26421,10.749616 +4,9,29.884371,10.749405999999999 +4,10,30.092441,10.744355 +4,11,29.813622000000002,10.755125000000001 +4,12,30.541951,10.741795 +4,13,30.683259,10.749916 +4,14,30.60642,10.754395 +4,15,30.897619000000002,10.752085999999998 +4,16,30.916869,10.740744999999999 +4,17,30.541671,10.746085 +4,18,30.39506,10.744695 +4,19,30.102552000000003,10.743495000000001 +8,0,57.349059000000004,16.084803 +8,1,55.835522,16.090033000000002 +8,2,56.556981,16.087253 +8,3,57.605379,16.109563 +8,4,58.170276,16.100754 +8,5,58.112246,16.111003 +8,6,56.989079999999994,16.098153 +8,7,56.567240999999996,16.095733000000003 +8,8,57.244398000000004,16.082404 +8,9,57.385028,16.085603 +8,10,58.218477,16.099902999999998 +8,11,57.358939,16.081802999999997 +8,12,57.981887,16.077932999999998 +8,13,57.364329,16.089623 +8,14,57.14905,16.099982999999998 +8,15,57.045889,16.089524 +8,16,56.188382000000004,16.085573 +8,17,57.257509,16.087843 +8,18,57.191129000000004,16.080372999999998 +8,19,57.077859000000004,16.099373 +12,0,85.019285,21.420571 +12,1,83.844678,21.406221 +12,2,84.584496,21.405680999999998 +12,3,84.093408,21.432291 +12,4,85.42332499999999,21.425791 +12,5,84.366127,21.416091 +12,6,82.94523,21.436121 +12,7,84.488016,21.468811000000002 +12,8,83.888608,21.419561 +12,9,84.083397,21.554961000000002 +12,10,85.37246499999999,21.47578 +12,11,85.084586,21.42151 +12,12,83.43490000000001,21.40957 +12,13,84.089528,21.408390999999998 +12,14,84.456957,21.42384 +12,15,85.077486,21.4286 +12,16,85.079906,21.454280999999998 +12,17,84.176158,21.401361 +12,18,83.940058,21.412891 +12,19,85.795264,21.418150999999998 +16,0,111.963183,26.762009 +16,1,110.211848,26.794728 +16,2,112.726702,26.786238 +16,3,111.159606,26.765579000000002 +16,4,110.906176,26.752619 +16,5,111.33185499999999,26.758058000000002 +16,6,108.973571,26.768528 +16,7,111.128476,26.755019 +16,8,110.217647,26.753909 +16,9,113.126481,26.760879000000003 +16,10,111.072315,26.779819 +16,11,112.449912,26.772269 +16,12,112.391613,26.766629000000002 +16,13,111.935863,26.754809 +16,14,112.443962,26.766538999999998 +16,15,111.442855,26.783088 +16,16,110.621437,26.814989 +16,17,108.106912,26.760099 +16,18,110.449877,26.783538999999998 +16,19,111.696774,26.772288000000003 diff --git a/experiments/policy-decryption/data/disjunction.csv b/experiments/policy-decryption/data/disjunction.csv new file mode 100644 index 00000000..32a369a6 --- /dev/null +++ b/experiments/policy-decryption/data/disjunction.csv @@ -0,0 +1,121 @@ +NumAuthorities,Run,EncryptMs,DecryptMs +1,0,10.272066,6.761035000000001 +1,1,10.173216,6.732545 +1,2,10.224046000000001,6.740925 +1,3,10.111397,6.727904 +1,4,9.961828,6.743544 +1,5,10.156777,6.7359849999999994 +1,6,10.231577,6.725175 +1,7,10.114896,6.720645 +1,8,10.081866999999999,6.728574 +1,9,10.145316999999999,6.726405000000001 +1,10,10.231297,6.718965000000001 +1,11,10.200167,6.714644 +1,12,10.107407,6.725835 +1,13,10.220246000000001,6.730725 +1,14,10.044987,6.734064 +1,15,10.221786999999999,6.720674000000001 +1,16,10.207177,6.727964 +1,17,10.006137,6.7972850000000005 +1,18,10.377636,6.731644999999999 +1,19,10.075327,6.724654 +2,0,16.947771,6.729905 +2,1,16.895421000000002,6.737324 +2,2,16.938181,6.744955 +2,3,16.964552,6.755204 +2,4,16.768852000000003,6.743863999999999 +2,5,16.905611,6.747665 +2,6,16.812141,6.730575 +2,7,17.082811,6.763344 +2,8,16.954832,6.755674 +2,9,16.966500999999997,6.730315 +2,10,16.715421000000003,6.727835 +2,11,17.033101,6.732394 +2,12,17.005080999999997,6.730185 +2,13,16.974571,6.728365 +2,14,16.910831,6.728235 +2,15,17.175079999999998,6.745064999999999 +2,16,16.878211,6.727365 +2,17,16.837090999999997,6.744114 +2,18,16.926552,6.735035 +2,19,16.665121000000003,6.737735 +4,0,30.074621,6.749034999999999 +4,1,30.740469,6.7470550000000005 +4,2,30.205111,6.7451550000000005 +4,3,30.582239,6.743435 +4,4,30.073560999999998,6.749683999999999 +4,5,30.047032,6.754215 +4,6,31.157909,6.746045 +4,7,29.864392,6.754194 +4,8,30.186831,6.749535 +4,9,29.851952,6.758084 +4,10,29.926610999999998,6.735125 +4,11,29.944521,6.752885 +4,12,30.449360000000002,6.749325 +4,13,30.51775,6.753534 +4,14,30.61776,6.749245 +4,15,30.51537,6.7523349999999995 +4,16,30.909948999999997,6.754524 +4,17,30.57205,6.749494 +4,18,30.382321,6.745225 +4,19,30.015941,6.754834000000001 +8,0,57.752048,6.771075 +8,1,55.492302,6.779925 +8,2,56.500911,6.774083999999999 +8,3,57.751257,6.771915 +8,4,58.011796999999994,6.780665 +8,5,57.896246999999995,6.766974 +8,6,56.87707,6.785254999999999 +8,7,56.457350000000005,6.7846649999999995 +8,8,57.363609000000004,6.770685 +8,9,57.310398,6.7757249999999996 +8,10,58.415766,6.788145 +8,11,57.201958,6.775565 +8,12,57.905837,6.791415000000001 +8,13,57.610488000000004,6.770594 +8,14,57.160548999999996,6.770255000000001 +8,15,57.625058,6.772254 +8,16,55.948892,6.771285 +8,17,57.434798,6.7702040000000006 +8,18,57.328299,6.775855 +8,19,56.793959,6.771115 +12,0,84.621156,6.790995000000001 +12,1,83.814168,6.806134 +12,2,85.275975,6.807834 +12,3,83.849609,6.796654 +12,4,85.472014,6.812734 +12,5,84.425627,6.801704000000001 +12,6,83.12306,6.798623999999999 +12,7,84.325307,6.808654 +12,8,84.062468,6.8044139999999995 +12,9,83.87920799999999,6.790845 +12,10,84.979475,6.811224999999999 +12,11,85.098884,6.804514999999999 +12,12,83.400199,6.801134 +12,13,82.868781,6.799434 +12,14,84.480826,6.803713999999999 +12,15,85.095956,6.809344 +12,16,85.147765,6.808565 +12,17,83.971338,6.805004 +12,18,83.782078,6.798645 +12,19,86.490712,6.806594 +16,0,111.825404,6.817944000000001 +16,1,110.613697,6.817474 +16,2,112.553892,6.832165000000001 +16,3,111.461404,6.828195 +16,4,110.50786699999999,6.807904 +16,5,111.670234,6.816095000000001 +16,6,109.276079,6.829934000000001 +16,7,110.983226,6.8558639999999995 +16,8,110.099458,6.812984999999999 +16,9,112.787292,6.817985 +16,10,111.06932599999999,6.828385 +16,11,112.452823,6.813164 +16,12,112.261763,6.816985 +16,13,111.834873,6.826944 +16,14,112.702302,6.832694 +16,15,110.987716,6.814065 +16,16,110.64552599999999,6.822005 +16,17,108.469752,6.829835 +16,18,110.779957,6.819214 +16,19,111.890463,6.839435 diff --git a/experiments/policy-decryption/plots/policy-decryption-decrypt.svg b/experiments/policy-decryption/plots/policy-decryption-decrypt.svg new file mode 100644 index 00000000..982f6508 --- /dev/null +++ b/experiments/policy-decryption/plots/policy-decryption-decrypt.svg @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +0.0 + + + +10.0 + + + +20.0 + + + + +5 + + + +10 + + + +15 + + + +Latency [ms] + + +# of attestation services + + + + + + + + + + + + + + + + + + + + + + + +all disjunction policy + + diff --git a/experiments/policy-decryption/plots/policy-decryption-encrypt.svg b/experiments/policy-decryption/plots/policy-decryption-encrypt.svg new file mode 100644 index 00000000..c6b00d70 --- /dev/null +++ b/experiments/policy-decryption/plots/policy-decryption-encrypt.svg @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +0.0 + + + +50.0 + + + +100.0 + + + + +5 + + + +10 + + + +15 + + + +Latency [ms] + + +# of attestation services + + + + + + + + + + + + + + + + + + + + + + + +all conjunction policy + + From 1478bf582e98451d2f71b56e79191448645420dc Mon Sep 17 00:00:00 2001 From: Carlos Date: Mon, 8 Dec 2025 00:37:19 +0000 Subject: [PATCH 5/5] [accless] B: Not Use Expect In Abe4 Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- accless/libs/abe4/src/hybrid.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/accless/libs/abe4/src/hybrid.rs b/accless/libs/abe4/src/hybrid.rs index 75de2c91..d85bad12 100644 --- a/accless/libs/abe4/src/hybrid.rs +++ b/accless/libs/abe4/src/hybrid.rs @@ -37,22 +37,22 @@ impl HybridCiphertext { /// /// gt is, precisely, what we get after a successful call to encrypt of a /// CP-ABEKEM scheme (i.e. the scheme we implement in the `scheme` module). -fn derive_aes128_key_from_gt(gt: &Gt) -> [u8; 16] { +fn derive_aes128_key_from_gt(gt: &Gt) -> Result<[u8; 16]> { let mut gt_bytes = Vec::new(); // This should never fail for a valid group element gt.serialize_compressed(&mut gt_bytes) - .expect("Gt serialization failed"); + .map_err(|e| anyhow::anyhow!("Gt serialization failed: {}", e))?; let hk = Hkdf::::new(Some(ABE4_KDF_SALT), >_bytes); let mut key = [0u8; 16]; hk.expand(ABE4_KDF_INFO, &mut key) - .expect("HKDF expand failed"); + .map_err(|e| anyhow::anyhow!("HKDF expand failed: {}", e))?; // gt_bytes only holds public data, no need to zeroize, but we could: gt_bytes.zeroize(); - key + Ok(key) } /// Encrypt `plaintext` using AES-GCM-128 under a key derived from `gt`. @@ -66,7 +66,7 @@ fn sym_encrypt_gt( plaintext: &[u8], aad: &[u8], ) -> Result> { - let mut key_bytes = derive_aes128_key_from_gt(gt); + let mut key_bytes = derive_aes128_key_from_gt(gt)?; let cipher = Aes128Gcm::new_from_slice(&key_bytes)?; // 96-bit nonce as recommended for GCM. @@ -113,7 +113,7 @@ fn sym_decrypt_gt(gt: &Gt, sym_ct: &[u8], aad: &[u8]) -> Result> { })?; let nonce = Nonce::from(nonce_arr); - let mut key_bytes = derive_aes128_key_from_gt(gt); + let mut key_bytes = derive_aes128_key_from_gt(gt)?; let cipher = Aes128Gcm::new_from_slice(&key_bytes)?; let payload = Payload { msg: ct_bytes, aad };